[vlc-commits] [Git][videolan/vlc][master] 15 commits: pipewire: add common helper code
Steve Lhomme (@robUx4)
gitlab at videolan.org
Wed Nov 13 18:55:37 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
9ec7e06a by Rémi Denis-Courmont at 2024-11-13T18:31:21+00:00
pipewire: add common helper code
- - - - -
f1ab721b by Rémi Denis-Courmont at 2024-11-13T18:31:21+00:00
pipewire: initial audio output module
- - - - -
f083c454 by Rémi Denis-Courmont at 2024-11-13T18:31:21+00:00
pipewire: set application properties
- - - - -
ff7ddd8b by Ayush Dey at 2024-11-13T18:31:21+00:00
pipewire: add pass-through support
- - - - -
b1d12540 by Denis Charmet at 2024-11-13T18:31:21+00:00
pipewire: switch from time_get to aout_TimingReport
- - - - -
0364264f by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: use pw_stream_get_nsec()
And set now inside stream_update_latency() since the call must be closed
to pw_stream_get_time_n().
- - - - -
e2ff9317 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: use the last date for startup
This is mainly useful when using the input clock, when the startup date
may change at the beginning.
- - - - -
1cb2b91a by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: ensure the first_pts is valid before sending timings
- - - - -
98126b07 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: initialize next_update to 0
VLC_TICK_O to avoid a possible miss if now is few us before (unlikely).
- - - - -
0d323b50 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: send timing reports earlier
The timing can now be sent just after pw is starting, the timing
audio_ts will very likely be VLC_TICK_0 (or first_pts).
This fixes video glitches on startup.
- - - - -
d7489296 by Rémi Denis-Courmont at 2024-11-13T18:31:21+00:00
pulse: fail if the server is Pipewire
Some distributions allow installing both Pipewire and PulseAudio, leaving
Pipewire to manage only video devices.
On one hand, if we leave the Pipewire output with higher priority, then we
do not get access to any audio output device in this case. On the other hand,
if we simply probe the PulseAudio output first, then we will never use the
native Pipewire protocol, even if the PulseAudio service is not actually
running, since Pipewire emulates PulseAudio.
So this checks the name of the PulseAudio daemon, and uses the PulseAudio
protocol if and only if the server is not Pipewire.
- - - - -
944f0158 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pulse: allow to force pulse if pw is present
- - - - -
dbf3c106 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: add a function to get the stream state
Will be needed by the next commit.
- - - - -
a2bce71a by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: fix pw_stream_get_state() not returning an error
This happens when playing passthrough with a faulty driver, but it may
happen in other cases too.
- - - - -
2ef22bd1 by Thomas Guillem at 2024-11-13T18:31:21+00:00
pipewire: ensure passthrough is possible
- - - - -
7 changed files:
- NEWS
- configure.ac
- modules/audio_output/Makefile.am
- + modules/audio_output/pipewire.c
- modules/audio_output/pulse.c
- + modules/audio_output/vlc_pipewire.c
- + modules/audio_output/vlc_pipewire.h
Changes:
=====================================
NEWS
=====================================
@@ -32,6 +32,7 @@ Core:
* Support of HTML help (via the vlc_plugin.h:set_help_html macro)
Audio output:
+ * PipeWire (native) audio output support
* ALSA: HDMI passthrough support.
Use --alsa-passthrough to configure S/PDIF or HDMI passthrough.
* Remove the DirectSound plugin (API obsolete after Windows 7)
=====================================
configure.ac
=====================================
@@ -3738,6 +3738,28 @@ dnl
EXTEND_HELP_STRING([Audio plugins:])
+dnl
+dnl Pipewire module
+dnl
+AC_ARG_ENABLE([pipewire],
+ AS_HELP_STRING([--enable-pipewire],
+ [use the PipeWire client library (default auto)]))
+have_pipewire="no"
+AS_IF([test "${enable_pipewire}" != "no"], [
+ for v in "0.3 >= 0.3.64" # list of supported package versions
+ do
+ AS_IF([test "${have_pipewire}" = "no"], [
+ PKG_CHECK_MODULES([PIPEWIRE], [libpipewire-${v}], [
+ have_pipewire="yes"
+ ], [true])
+ ])
+ done
+ AS_IF([test "${enable_pipewire}" = "yes" -a -n "${enable_pipewire}"], [
+ AC_MSG_ERROR([$PIPEWIRE_PKG_ERRORS. Pipewire required.])
+ ])
+])
+AM_CONDITIONAL([HAVE_PIPEWIRE], [test "${have_pipewire}" = "yes"])
+
dnl
dnl Pulseaudio module
dnl
=====================================
modules/audio_output/Makefile.am
=====================================
@@ -61,6 +61,15 @@ if HAVE_ALSA
aout_LTLIBRARIES += libalsa_plugin.la
endif
+libaout_pipewire_plugin_la_SOURCES = \
+ audio_output/vlc_pipewire.c audio_output/vlc_pipewire.h \
+ audio_output/pipewire.c
+libaout_pipewire_plugin_la_CFLAGS = $(AM_CFLAGS) $(PIPEWIRE_CFLAGS)
+libaout_pipewire_plugin_la_LIBADD = $(PIPEWIRE_LIBS) $(LIBM)
+if HAVE_PIPEWIRE
+aout_LTLIBRARIES += libaout_pipewire_plugin.la
+endif
+
libvlc_pulse_la_SOURCES = audio_output/vlcpulse.c audio_output/vlcpulse.h
libvlc_pulse_la_CFLAGS = $(AM_CFLAGS) $(PULSE_CFLAGS)
libvlc_pulse_la_LIBADD = $(PULSE_LIBS) $(LTLIBVLCCORE)
=====================================
modules/audio_output/pipewire.c
=====================================
@@ -0,0 +1,920 @@
+/*****************************************************************************
+ * pipewire.c: PipeWire audio output plugin for VLC
+ *****************************************************************************
+ * Copyright (C) 2022 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
+
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <search.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <pipewire/pipewire.h>
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_aout.h>
+#include <vlc_tick.h>
+#include "vlc_pipewire.h"
+
+struct vlc_pw_stream {
+ struct vlc_pw_context *context;
+ struct pw_stream *stream;
+ struct spa_hook listener;
+ size_t stride;
+
+ struct {
+ vlc_frame_t *head;
+ vlc_frame_t **tailp;
+ size_t size;
+ } queue;
+
+ struct {
+ vlc_tick_t pts;
+ ptrdiff_t frames;
+ unsigned int rate;
+ uint64_t injected;
+ vlc_tick_t next_update;
+ } time;
+
+ vlc_tick_t start;
+ vlc_tick_t first_pts;
+ bool starting;
+ bool draining;
+ bool error;
+
+ audio_output_t *aout;
+};
+
+/**
+ * Stream control callback.
+ *
+ * This monitors the stream for control changes and reports them as applicable.
+ */
+static void stream_control_info(void *data, uint32_t id,
+ const struct pw_stream_control *control)
+{
+ struct vlc_pw_stream *s = data;
+
+ vlc_pw_debug(s->context, "control %"PRIu32" %s", id, control->name);
+
+ switch (id) {
+ case SPA_PROP_mute:
+ aout_MuteReport(s->aout, control->values[0] != 0.f);
+ break;
+
+ case SPA_PROP_channelVolumes: {
+ float vol = 0.f;
+
+ for (size_t i = 0; i < control->n_values; i++)
+ vol = fmaxf(vol, control->values[i]);
+
+ aout_VolumeReport(s->aout, vol);
+ break;
+ }
+ }
+}
+
+/**
+ * Stream state callback.
+ *
+ * This monitors the stream for state change, looking out for fatal errors.
+ */
+static void stream_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *err)
+{
+ struct vlc_pw_stream *s = data;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ {
+ vlc_pw_error(s->context, "stream error: %s", err);
+ s->error = true;
+ }
+ else
+ vlc_pw_debug(s->context, "stream %s",
+ pw_stream_state_as_string(state));
+ if (old != state)
+ vlc_pw_signal(s->context);
+}
+
+/**
+ * Retrieve latest timings
+ */
+static int stream_update_latency(struct vlc_pw_stream *s, vlc_tick_t *now)
+{
+ struct pw_time ts;
+
+ /* PW monotonic clock, same than vlc_tick_now() */
+ *now = VLC_TICK_FROM_NS(pw_stream_get_nsec(s->stream));
+
+ if (pw_stream_get_time_n(s->stream, &ts, sizeof (ts)) < 0
+ || ts.rate.denom == 0)
+ return -1;
+
+ s->time.pts = VLC_TICK_FROM_NS(ts.now);
+ s->time.pts += vlc_tick_from_frac(ts.delay * ts.rate.num, ts.rate.denom);
+ s->time.frames = ts.buffered + ts.queued;
+
+ return 0;
+}
+
+/**
+ * Stream processing callback.
+ *
+ * This fills in the next audio buffer.
+ */
+static void stream_process(void *data)
+{
+ struct vlc_pw_stream *s = data;
+ vlc_tick_t now;
+ int val = stream_update_latency(s, &now);
+ struct pw_buffer *b = pw_stream_dequeue_buffer(s->stream);
+
+ if (likely(b != NULL)) {
+ /* One should have more indirection layers than pizza condiments */
+ struct spa_buffer *buf = b->buffer;
+ struct spa_data *d = &buf->datas[0];
+ struct spa_chunk *chunk = d->chunk;
+ unsigned char *dst = d->data;
+ size_t frame_room = d->maxsize / s->stride;
+ size_t room = frame_room * s->stride;
+ vlc_frame_t *block;
+
+ chunk->offset = 0;
+ chunk->stride = s->stride;
+ chunk->size = 0;
+
+ /* Adjust start time */
+ if (s->starting) {
+ /*
+ * If timing data is not available (val != 0), we can at least
+ * assume that the playback delay is no less than zero.
+ */
+ vlc_tick_t pts = (val == 0) ? s->time.pts : now;
+ vlc_tick_t gap = s->start - pts;
+ vlc_tick_t span = vlc_tick_from_samples(frame_room, s->time.rate);
+ size_t skip;
+
+ if (gap >= span) { /* Buffer too early, fill with silence */
+ vlc_pw_debug(s->context, "too early to start, silence");
+ skip = frame_room;
+
+ } else if (gap >= 0) {
+ vlc_pw_debug(s->context, "starting %s time",
+ val ? "without" : "on");
+ skip = samples_from_vlc_tick(gap, s->time.rate);
+ s->starting = false;
+
+ } else {
+ vlc_pw_warn(s->context, "starting late");
+ skip = 0;
+ s->starting = false;
+ }
+
+ skip *= s->stride;
+ assert(skip <= room);
+ memset(dst, 0, skip);
+ dst += skip;
+ room -= skip;
+ }
+
+ if (!s->starting && s->time.pts != VLC_TICK_INVALID
+ && now >= s->time.next_update && s->first_pts != VLC_TICK_INVALID) {
+ vlc_tick_t elapsed = now - s->time.pts;
+ /* A sample injected now would be delayed by the following amount of vlc_tick_t */
+ vlc_tick_t delay = vlc_tick_from_samples(s->time.frames, s->time.rate)
+ - elapsed;
+ vlc_tick_t audio_ts = vlc_tick_from_samples(s->time.injected, s->time.rate)
+ + s->first_pts;
+ aout_TimingReport(s->aout, now + delay, audio_ts);
+ /* Once we have enough points to initiate the clock we can delay the reports */
+ if (now >= s->start + VLC_TICK_FROM_SEC(1))
+ s->time.next_update = now + VLC_TICK_FROM_SEC(1);
+ }
+
+ while ((block = s->queue.head) != NULL) {
+ size_t avail = block->i_buffer;
+ size_t length = (avail < room) ? avail : room;
+
+ memcpy(dst, block->p_buffer, length);
+ block->p_buffer += length;
+ block->i_buffer -= length;
+ dst += length;
+ room -= length;
+ chunk->size += length;
+ assert((length % s->stride) == 0);
+ const size_t written = length / s->stride;
+ s->time.injected += written;
+ s->queue.size -= length;
+
+ if (block->i_buffer > 0) {
+ assert(room == 0);
+ break;
+ }
+
+ s->queue.head = block->p_next;
+ vlc_frame_Release(block);
+ }
+
+ if (s->queue.head == NULL)
+ s->queue.tailp = &s->queue.head;
+
+ b->size = chunk->size / s->stride;
+ pw_stream_queue_buffer(s->stream, b);
+ }
+
+ if (s->queue.head == NULL && s->draining) {
+ s->first_pts = s->start = VLC_TICK_INVALID;
+ s->starting = false;
+ s->draining = false;
+ pw_stream_flush(s->stream, true);
+ }
+}
+
+/**
+ * Stream drain callback.
+ *
+ * This monitors the stream for completion of draining.
+ */
+static void stream_drained(void *data)
+{
+ struct vlc_pw_stream *s = data;
+
+ vlc_pw_debug(s->context, "stream drained");
+ aout_DrainedReport(s->aout);
+}
+
+static void stream_trigger_done(void *data)
+{
+ struct vlc_pw_stream *s = data;
+
+ vlc_pw_debug(s->context, "stream trigger done");
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = stream_state_changed,
+ .control_info = stream_control_info,
+ .process = stream_process,
+ .drained = stream_drained,
+ .trigger_done = stream_trigger_done,
+};
+
+static enum pw_stream_state vlc_pw_stream_get_state(struct vlc_pw_stream *s)
+{
+ /* PW Workaround: The error state can be notified via
+ * stream_state_changed() but never returned by pw_stream_get_state() */
+ return s->error ? PW_STREAM_STATE_ERROR
+ : pw_stream_get_state(s->stream, NULL);
+}
+
+/**
+ * Queues an audio buffer for playback.
+ */
+static void vlc_pw_stream_play(struct vlc_pw_stream *s, vlc_frame_t *block,
+ vlc_tick_t date)
+{
+ assert((block->i_buffer % s->stride) == 0);
+ vlc_pw_lock(s->context);
+ if (vlc_pw_stream_get_state(s) == PW_STREAM_STATE_ERROR) {
+ vlc_frame_Release(block);
+ goto out;
+ }
+
+ if (s->start == VLC_TICK_INVALID) {
+ /* Upon flush or drain, the stream is implicitly inactivated. This
+ * re-activates it. In other cases, this should be a no-op. */
+ pw_stream_set_active(s->stream, true);
+ assert(!s->starting);
+ s->starting = true;
+ s->time.next_update = VLC_TICK_0;
+ s->first_pts = block->i_pts;
+ }
+ if (s->starting)
+ s->start = date
+ - vlc_tick_from_samples(s->queue.size / s->stride, s->time.rate);
+
+ *(s->queue.tailp) = block;
+ s->queue.tailp = &block->p_next;
+ s->queue.size += block->i_buffer;
+out:
+ s->draining = false;
+ vlc_pw_unlock(s->context);
+}
+
+/**
+ * Pauses or resumes the playback.
+ */
+static void vlc_pw_stream_set_pause(struct vlc_pw_stream *s, bool paused,
+ vlc_tick_t date)
+{
+ vlc_pw_lock(s->context);
+ pw_stream_set_active(s->stream, !paused);
+ s->time.pts = VLC_TICK_INVALID;
+ if (unlikely(s->starting)) {
+ assert(s->start != VLC_TICK_INVALID);
+ if (paused)
+ s->start -= date;
+ else
+ s->start += date;
+ }
+ vlc_pw_unlock(s->context);
+}
+
+/**
+ * Flushes (discards) all pending audio buffers.
+ */
+static void vlc_pw_stream_flush(struct vlc_pw_stream *s)
+{
+ vlc_pw_lock(s->context);
+ vlc_frame_ChainRelease(s->queue.head);
+ s->queue.head = NULL;
+ s->queue.tailp = &s->queue.head;
+ s->queue.size = 0;
+ s->time.pts = VLC_TICK_INVALID;
+ s->time.injected = 0;
+ s->first_pts = s->start = VLC_TICK_INVALID;
+ s->starting = false;
+ s->draining = false;
+ s->error = false;
+ pw_stream_flush(s->stream, false);
+ vlc_pw_unlock(s->context);
+}
+
+/**
+ * Starts draining.
+ *
+ * This flags the start of draining. stream_drained() will be called by the
+ * thread loop whence draining completes.
+ */
+static void vlc_pw_stream_drain(struct vlc_pw_stream *s)
+{
+ vlc_pw_lock(s->context);
+ s->first_pts = s->start = VLC_TICK_INVALID;
+ if (vlc_pw_stream_get_state(s) == PW_STREAM_STATE_ERROR)
+ stream_drained(s); /* Don't wait on a failed stream */
+ else if (s->queue.head == NULL)
+ pw_stream_flush(s->stream, true); /* Drain now */
+ else
+ s->draining = true; /* Let ->process() drain */
+ vlc_pw_unlock(s->context);
+}
+
+static void vlc_pw_stream_set_volume(struct vlc_pw_stream *s, float vol)
+{
+ const struct pw_stream_control *old;
+
+ vlc_pw_lock(s->context);
+ old = pw_stream_get_control(s->stream, SPA_PROP_channelVolumes);
+ if (old != NULL) {
+ float values[SPA_AUDIO_MAX_CHANNELS];
+ float oldvol = 0.f;
+
+ assert(old->n_values <= ARRAY_SIZE(values));
+ /* Try to preserve the balance */
+ for (size_t i = 0; i < old->n_values; i++)
+ oldvol = fmaxf(oldvol, old->values[i]);
+
+ float delta = vol - oldvol;
+
+ for (size_t i = 0; i < old->n_values; i++)
+ values[i] = fmaxf(0.f, old->values[i] + delta);
+
+ pw_stream_set_control(s->stream, SPA_PROP_channelVolumes,
+ old->n_values, values, 0);
+ }
+ vlc_pw_unlock(s->context);
+}
+
+static void vlc_pw_stream_set_mute(struct vlc_pw_stream *s, bool mute)
+{
+ float value = mute ? 1.f : 0.f;
+
+ vlc_pw_lock(s->context);
+ pw_stream_set_control(s->stream, SPA_PROP_mute, 1, &value, 0);
+ vlc_pw_unlock(s->context);
+}
+
+static void vlc_pw_stream_select_device(struct vlc_pw_stream *s,
+ const char *name)
+{
+ struct spa_dict_item items[] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, name),
+ };
+ struct spa_dict dict = SPA_DICT_INIT(items, ARRAY_SIZE(items));
+
+ vlc_pw_debug(s->context, "setting object serial: %s", name);
+ vlc_pw_lock(s->context);
+ pw_stream_update_properties(s->stream, &dict);
+ vlc_pw_unlock(s->context);
+}
+
+static void vlc_pw_stream_destroy(struct vlc_pw_stream *s)
+{
+ vlc_pw_stream_flush(s);
+ vlc_pw_lock(s->context);
+ pw_stream_disconnect(s->stream);
+ pw_stream_destroy(s->stream);
+ vlc_pw_unlock(s->context);
+ free(s);
+}
+
+struct vlc_pw_aout {
+ struct vlc_pw_context *context;
+ struct vlc_pw_stream *stream;
+ struct spa_hook listener;
+ void *nodes;
+ struct {
+ uint64_t target;
+ float volume;
+ signed char mute;
+ } initial;
+};
+
+static struct vlc_pw_stream *vlc_pw_stream_create(audio_output_t *aout,
+ audio_sample_format_t *restrict fmt)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ struct vlc_pw_context *ctx = sys->context;
+ struct spa_audio_info_raw rawfmt = {
+ .rate = fmt->i_rate,
+ .channels = fmt->i_channels,
+ };
+
+ enum spa_audio_iec958_codec encoding = SPA_AUDIO_IEC958_CODEC_PCM; /* PCM by default */
+
+ /* Map possible audio output formats */
+ switch (fmt->i_format) {
+ case VLC_CODEC_FL64:
+ rawfmt.format = SPA_AUDIO_FORMAT_F64;
+ break;
+ case VLC_CODEC_FL32:
+ rawfmt.format = SPA_AUDIO_FORMAT_F32;
+ break;
+ case VLC_CODEC_S32N:
+ rawfmt.format = SPA_AUDIO_FORMAT_S32;
+ break;
+ case VLC_CODEC_S16N:
+ rawfmt.format = SPA_AUDIO_FORMAT_S16;
+ break;
+ case VLC_CODEC_U8:
+ rawfmt.format = SPA_AUDIO_FORMAT_U8;
+ break;
+ case VLC_CODEC_A52:
+ fmt->i_format = VLC_CODEC_SPDIFL;
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_frame_length = 1;
+ fmt->i_physical_channels = AOUT_CHANS_2_0;
+ fmt->i_channels = 2;
+ encoding = SPA_AUDIO_IEC958_CODEC_AC3;
+ break;
+ case VLC_CODEC_EAC3:
+ fmt->i_format = VLC_CODEC_SPDIFL;
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_frame_length = 1;
+ fmt->i_physical_channels = AOUT_CHANS_2_0;
+ fmt->i_channels = 2;
+ encoding = SPA_AUDIO_IEC958_CODEC_EAC3;
+ break;
+ case VLC_CODEC_DTS:
+ fmt->i_format = VLC_CODEC_SPDIFL;
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_frame_length = 1;
+ fmt->i_physical_channels = AOUT_CHANS_2_0;
+ fmt->i_channels = 2;
+ encoding = SPA_AUDIO_IEC958_CODEC_DTS;
+ break;
+ default:
+ vlc_pw_error(sys->context, "unknown format");
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ /* Map audio channels */
+ if (encoding == SPA_AUDIO_IEC958_CODEC_PCM)
+ {
+ size_t mapped = 0;
+
+ if (fmt->i_channels > SPA_AUDIO_MAX_CHANNELS) {
+ vlc_pw_error(sys->context, "too many channels");
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ static const unsigned char map[] = {
+ SPA_AUDIO_CHANNEL_FC,
+ SPA_AUDIO_CHANNEL_FL,
+ SPA_AUDIO_CHANNEL_FR,
+ 0 /* unassigned */,
+ SPA_AUDIO_CHANNEL_RC,
+ SPA_AUDIO_CHANNEL_RL,
+ SPA_AUDIO_CHANNEL_RR,
+ 0 /* unassigned */,
+ SPA_AUDIO_CHANNEL_SL,
+ SPA_AUDIO_CHANNEL_SR,
+ 0 /* unassigned */,
+ 0 /* unassigned */,
+ SPA_AUDIO_CHANNEL_LFE,
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(map); i++)
+ if ((fmt->i_physical_channels >> i) & 1)
+ rawfmt.position[mapped++] = map[i];
+
+ while (mapped < fmt->i_channels) {
+ rawfmt.position[mapped] = SPA_AUDIO_CHANNEL_START_Aux + mapped;
+ mapped++;
+ }
+ }
+
+ /* Assemble the stream format */
+ const struct spa_pod *params[1];
+ unsigned char buf[1024];
+ struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof (buf));
+
+ if (encoding == SPA_AUDIO_IEC958_CODEC_PCM)
+ {
+ params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat,
+ &rawfmt);
+ }
+ else /* pass-through */
+ {
+ struct spa_audio_info_iec958 iecfmt = {
+ .rate = fmt->i_rate,
+ .codec = encoding
+ };
+ params[0] = spa_format_audio_iec958_build(&builder, SPA_PARAM_EnumFormat,
+ &iecfmt);
+ }
+
+ /* Assemble stream properties */
+ struct pw_properties *props;
+
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ NULL);
+
+ if (likely(props != NULL)) {
+ char *role = var_InheritString(aout, "role");
+ if (role != NULL) { /* Capitalise the first character */
+ pw_properties_setf(props, PW_KEY_MEDIA_ROLE, "%c%s",
+ toupper((unsigned char)role[0]), role + 1);
+ free(role);
+ }
+ }
+
+ /* Create the stream */
+ struct vlc_pw_stream *s = malloc(sizeof (*s));
+ if (unlikely(s == NULL))
+ return NULL;
+
+ enum pw_stream_flags flags =
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS;
+ /*PW_STREAM_FLAG_EXCLUSIVE TODO*/
+ enum pw_stream_state state;
+
+ s->context = ctx;
+ s->listener = (struct spa_hook){ };
+ s->stride = fmt->i_bytes_per_frame;
+ s->queue.head = NULL;
+ s->queue.tailp = &s->queue.head;
+ s->queue.size = 0;
+ s->time.pts = VLC_TICK_INVALID;
+ s->time.rate = fmt->i_rate;
+ s->time.injected = 0;
+ s->first_pts = s->start = VLC_TICK_INVALID;
+ s->starting = false;
+ s->draining = false;
+ s->error = false;
+ s->aout = aout;
+
+ vlc_pw_lock(s->context);
+ s->stream = vlc_pw_stream_new(s->context, "audio stream", props);
+ if (unlikely(s->stream == NULL)) {
+ vlc_pw_unlock(s->context);
+ free(s);
+ return NULL;
+ }
+
+ if (sys->initial.target != SPA_ID_INVALID) {
+ char target[21];
+ struct spa_dict_item items[] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, target),
+ };
+ struct spa_dict dict = SPA_DICT_INIT(items, ARRAY_SIZE(items));
+
+ snprintf(target, sizeof (target), "%"PRIu64, sys->initial.target);
+ pw_stream_update_properties(s->stream, &dict);
+ }
+
+ pw_stream_add_listener(s->stream, &s->listener, &stream_events, s);
+ pw_stream_connect(s->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, flags,
+ params, ARRAY_SIZE(params));
+
+ if (encoding == SPA_AUDIO_IEC958_CODEC_PCM)
+ {
+ /* Wait for the PCM stream to be ready */
+ while ((state = vlc_pw_stream_get_state(s)) == PW_STREAM_STATE_CONNECTING)
+ vlc_pw_wait(s->context);
+ }
+ else
+ {
+ /* In case of passthrough, an error can be triggered just after the
+ * CONNECTING -> PAUSED state, so wait for the STREAMING state or any
+ * errors. */
+ while ((state = vlc_pw_stream_get_state(s)) == PW_STREAM_STATE_CONNECTING
+ || state == PW_STREAM_STATE_PAUSED)
+ vlc_pw_wait(s->context);
+ }
+ vlc_pw_unlock(s->context);
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ default:
+ vlc_pw_stream_destroy(s);
+ errno = ENOBUFS;
+ return NULL;
+ }
+
+ /* Apply initial controls */
+ sys->initial.target = SPA_ID_INVALID;
+
+ if (sys->initial.mute >= 0) {
+ vlc_pw_stream_set_mute(s, sys->initial.mute);
+ sys->initial.mute = -1;
+ }
+ if (!isnan(sys->initial.volume)) {
+ vlc_pw_stream_set_volume(s, sys->initial.volume);
+ sys->initial.volume = NAN;
+ }
+ return s;
+}
+
+static void Play(audio_output_t *aout, vlc_frame_t *block, vlc_tick_t date)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ vlc_pw_stream_play(sys->stream, block, date);
+}
+
+static void Pause(audio_output_t *aout, bool paused, vlc_tick_t date)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ vlc_pw_stream_set_pause(sys->stream, paused, date);
+}
+
+static void Flush(audio_output_t *aout)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ vlc_pw_stream_flush(sys->stream);
+}
+
+static void Drain(audio_output_t *aout)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ vlc_pw_stream_drain(sys->stream);
+}
+
+static void Stop(audio_output_t *aout)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ vlc_pw_stream_destroy(sys->stream);
+ sys->stream = NULL;
+}
+
+static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+
+ sys->stream = vlc_pw_stream_create(aout, fmt);
+ return (sys->stream != NULL) ? VLC_SUCCESS : -errno;
+}
+
+static int VolumeSet(audio_output_t *aout, float volume)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+
+ if (sys->stream != NULL) {
+ vlc_pw_stream_set_volume(sys->stream, volume);
+ return 0;
+ }
+
+ sys->initial.volume = volume;
+ aout_VolumeReport(aout, volume);
+ return 0;
+}
+
+static int MuteSet(audio_output_t *aout, bool mute)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+
+ if (sys->stream != NULL) {
+ vlc_pw_stream_set_mute(sys->stream, mute);
+ return 0;
+ }
+
+ sys->initial.mute = mute;
+ aout_MuteReport(aout, mute);
+ return 0;
+}
+
+struct vlc_pw_node {
+ uint32_t id;
+ uint64_t serial;
+};
+
+static int node_by_id(const void *a, const void *b)
+{
+ const struct vlc_pw_node *na = a, *nb = b;
+
+ if (na->id > nb->id)
+ return +1;
+ if (na->id < nb->id)
+ return -1;
+ return 0;
+}
+
+static int DeviceSelect(audio_output_t *aout, const char *name)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+
+ if (sys->stream != NULL) {
+ vlc_pw_stream_select_device(sys->stream, name);
+ return 0;
+ }
+
+ sys->initial.target = strtoul(name, NULL, 10);
+ aout_DeviceReport(aout, name);
+ return 0;
+}
+
+static void registry_node(audio_output_t *aout, uint32_t id, uint32_t perms,
+ uint32_t version, const struct spa_dict *props)
+{
+ struct vlc_pw_aout *sys = aout->sys;
+ char serialstr[21];
+ uint64_t serial;
+
+ if (props == NULL)
+ return;
+
+ const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (class == NULL)
+ return;
+ if (strcmp(class, "Audio/Sink") && strcmp(class, "Audio/Duplex"))
+ return; /* Not an audio output */
+ if (!spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &serial, 0))
+ return;
+
+ const char *desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
+ if (unlikely(desc == NULL))
+ desc = "???";
+
+ struct vlc_pw_node *node = malloc(sizeof (*node));
+ if (unlikely(node == NULL))
+ return;
+
+ node->id = id;
+ node->serial = serial;
+
+ struct vlc_pw_node **pp = tsearch(node, &sys->nodes, node_by_id);
+ if (unlikely(pp == NULL)) { /* Memory allocation error in the tree */
+ free(node);
+ return;
+ }
+ if (*pp != node) { /* Existing node, update it */
+ free(*pp);
+ *pp = node;
+ }
+
+ snprintf(serialstr, sizeof (serialstr), "%"PRIu64, serial);
+ aout_HotplugReport(aout, serialstr, desc);
+ (void) perms; (void) version;
+}
+
+/**
+ * Global callback.
+ *
+ * This gets called for every initial, then every new, object from the PipeWire
+ * server. We can find the usable playback devices through this.
+ */
+static void registry_global(void *data, uint32_t id, uint32_t perms,
+ const char *name, uint32_t version,
+ const struct spa_dict *props)
+{
+ audio_output_t *aout = data;
+
+ if (strcmp(name, PW_TYPE_INTERFACE_Node) == 0)
+ registry_node(aout, id, perms, version, props);
+}
+
+/**
+ * Global removal callback.
+ *
+ * This gets called when an object disappers. We can detect when a playback
+ * device is unplugged here.
+ */
+static void registry_global_remove(void *data, uint32_t id)
+{
+ audio_output_t *aout = data;
+ struct vlc_pw_aout *sys = aout->sys;
+ struct vlc_pw_node key = { .id = id };
+ struct vlc_pw_node **pp = tfind(&key, &sys->nodes, node_by_id);
+
+ if (pp != NULL) { /* A known device has been removed */
+ struct vlc_pw_node *node = *pp;
+ char serialstr[11];
+
+ snprintf(serialstr, sizeof (serialstr), "%"PRIu64, node->serial);
+ aout_HotplugReport(aout, serialstr, NULL);
+ tdelete(node, &sys->nodes, node_by_id);
+ free(node);
+ }
+}
+
+static const struct pw_registry_events events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_global,
+ .global_remove = registry_global_remove
+};
+
+static void Close(vlc_object_t *obj)
+{
+ audio_output_t *aout = (audio_output_t *)obj;
+ struct vlc_pw_aout *sys = aout->sys;
+
+ vlc_pw_disconnect(sys->context);
+ tdestroy(sys->nodes, free);
+ free(sys);
+}
+
+static int Open(vlc_object_t *obj)
+{
+ audio_output_t *aout = (audio_output_t *)obj;
+
+ struct vlc_pw_aout *sys = malloc(sizeof (*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ sys->context = vlc_pw_connect(obj, "audio output");
+ if (sys->context == NULL) {
+ free(sys);
+ return -errno;
+ }
+
+ sys->initial.target = SPA_ID_INVALID;
+ sys->initial.volume = NAN;
+ sys->initial.mute = -1;
+ sys->nodes = NULL;
+
+ aout->sys = sys;
+ aout->start = Start;
+ aout->stop = Stop;
+ aout->time_get = NULL;
+ aout->play = Play;
+ aout->pause = Pause;
+ aout->flush = Flush;
+ aout->drain = Drain;
+ aout->volume_set = VolumeSet;
+ aout->mute_set = MuteSet;
+ aout->device_select = DeviceSelect;
+
+ vlc_pw_lock(sys->context);
+ vlc_pw_registry_listen(sys->context, &sys->listener, &events, aout);
+ vlc_pw_roundtrip_unlocked(sys->context); /* Enumerate device nodes */
+ vlc_pw_unlock(sys->context);
+ return VLC_SUCCESS;
+}
+
+vlc_module_begin()
+ set_shortname("PipeWire")
+ set_description(N_("PipeWire audio output"))
+ set_capability("audio output", 200)
+ set_subcategory(SUBCAT_AUDIO_AOUT)
+ add_shortcut("pipewire", "pw")
+ set_callbacks(Open, Close)
+vlc_module_end()
=====================================
modules/audio_output/pulse.c
=====================================
@@ -38,10 +38,13 @@
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
+#define DISTRO_KLUDGE 50
+#include <vlc_modules.h>
+
vlc_module_begin ()
set_shortname( "PulseAudio" )
set_description( N_("Pulseaudio audio output") )
- set_capability( "audio output", 160 )
+ set_capability( "audio output", 160 + DISTRO_KLUDGE )
set_subcategory( SUBCAT_AUDIO_AOUT )
add_shortcut( "pulseaudio", "pa" )
set_callbacks( Open, Close )
@@ -67,6 +70,9 @@ typedef struct
pa_threaded_mainloop *mainloop; /**< PulseAudio thread */
pa_time_event *drain_trigger; /**< Drain stream trigger */
bool draining;
+#if DISTRO_KLUDGE > 0
+ bool is_pipewire;
+#endif
pa_cvolume cvolume; /**< actual sink input volume */
bool start_date_reached;
@@ -1163,6 +1169,13 @@ static void server_info_cb(pa_context *ctx, const pa_server_info *info,
msg_Dbg(aout, "server %s version %s on %s@%s", info->server_name,
info->server_version, info->user_name, info->host_name);
+#if DISTRO_KLUDGE > 0
+ aout_sys_t *sys = aout->sys;
+
+ sys->is_pipewire = strcasestr(info->server_name, "pipewire") != NULL;
+ pa_threaded_mainloop_signal(sys->mainloop, 0);
+#endif
+
(void) ctx;
}
@@ -1231,10 +1244,29 @@ static int Open(vlc_object_t *obj)
aout->mute_set = MuteSet;
aout->device_select = StreamMove;
+#if DISTRO_KLUDGE > 0
+ sys->is_pipewire = false;
+#endif
pa_threaded_mainloop_lock(sys->mainloop);
op = pa_context_get_server_info(sys->context, server_info_cb, aout);
if (likely(op != NULL))
+ {
+#if DISTRO_KLUDGE > 0
+ if (!obj->force)
+ {
+ while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(sys->mainloop);
+ if (sys->is_pipewire && module_exists("aout_pipewire")) {
+ msg_Dbg(aout, "refusing to use PipeWire");
+ pa_threaded_mainloop_unlock(sys->mainloop);
+ vlc_pa_disconnect(obj, sys->context, sys->mainloop);
+ free(sys);
+ return -ENOTSUP;
+ }
+ }
+#endif
pa_operation_unref(op);
+ }
/* Sinks (output devices) list */
op = pa_context_get_sink_info_list(sys->context, sink_add_cb, aout);
=====================================
modules/audio_output/vlc_pipewire.c
=====================================
@@ -0,0 +1,318 @@
+/*****************************************************************************
+ * vlc_pipewire.c: common PipeWire code
+ *****************************************************************************
+ * Copyright (C) 2022 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <locale.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <pipewire/pipewire.h>
+#include <vlc_common.h>
+#include <vlc_fs.h>
+#include <vlc_tick.h>
+#include "vlc_pipewire.h"
+
+struct vlc_pw_context {
+ struct pw_thread_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct pw_registry *registry;
+ struct vlc_logger *logger;
+ const char *type;
+};
+
+static
+void vlc_pw_vlog(struct vlc_pw_context *ctx, int prio,
+ const char *file, unsigned int line, const char *func,
+ const char *fmt, va_list ap)
+{
+ vlc_vaLog(&ctx->logger, prio, ctx->type, "pipewire", file, line, func, fmt,
+ ap);
+}
+
+void (vlc_pw_log)(struct vlc_pw_context *ctx, int prio,
+ const char *file, unsigned int line, const char *func,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlc_pw_vlog(ctx, prio, file, line, func, fmt, ap);
+ va_end(ap);
+}
+
+int (vlc_pw_perror)(struct vlc_pw_context *ctx, const char *file,
+ unsigned int line, const char *func, const char *desc)
+{
+ int err = errno;
+ (vlc_pw_log)(ctx, VLC_MSG_ERR, file, line, func,
+ "PipeWire %s error: %s", desc, vlc_strerror_c(err));
+ errno = err;
+ return err;
+}
+
+void vlc_pw_lock(struct vlc_pw_context *ctx)
+{
+ pw_thread_loop_lock(ctx->loop);
+}
+
+void vlc_pw_unlock(struct vlc_pw_context *ctx)
+{
+ pw_thread_loop_unlock(ctx->loop);
+}
+
+void vlc_pw_signal(struct vlc_pw_context *ctx)
+{
+ pw_thread_loop_signal(ctx->loop, false);
+}
+
+void vlc_pw_wait(struct vlc_pw_context *ctx)
+{
+ pw_thread_loop_wait(ctx->loop);
+}
+
+struct pw_stream *vlc_pw_stream_new(struct vlc_pw_context *ctx,
+ const char *name,
+ struct pw_properties *props)
+{
+ return pw_stream_new(ctx->core, name, props);
+}
+
+struct vlc_pw_rt {
+ struct vlc_pw_context *context;
+ int seq;
+ bool done;
+};
+
+static void roundtrip_done(void *data, uint32_t id, int seq)
+{
+ struct vlc_pw_rt *rt = data;
+
+ if (id == PW_ID_CORE && seq == rt->seq) {
+ rt->done = true;
+ vlc_pw_signal(rt->context);
+ }
+}
+
+void vlc_pw_roundtrip_unlocked(struct vlc_pw_context *ctx)
+{
+ static const struct pw_core_events events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = roundtrip_done,
+ };
+ struct spa_hook listener = { };
+ struct vlc_pw_rt rt;
+
+ rt.context = ctx;
+ rt.done = false;
+
+ pw_core_add_listener(ctx->core, &listener, &events, &rt);
+ rt.seq = pw_core_sync(ctx->core, PW_ID_CORE, 0);
+ while (!rt.done)
+ vlc_pw_wait(ctx);
+ spa_hook_remove(&listener);
+}
+
+int vlc_pw_registry_listen(struct vlc_pw_context *ctx, struct spa_hook *hook,
+ const struct pw_registry_events *evts, void *data)
+{
+ if (ctx->registry == NULL) {
+ ctx->registry = pw_core_get_registry(ctx->core, PW_VERSION_REGISTRY,
+ 0);
+ if (unlikely(ctx->registry == NULL))
+ return -errno;
+ }
+
+ *hook = (struct spa_hook){ };
+ pw_registry_add_listener(ctx->registry, hook, evts, data);
+ return 0;
+}
+
+void vlc_pw_disconnect(struct vlc_pw_context *ctx)
+{
+ pw_thread_loop_stop(ctx->loop);
+
+ if (ctx->registry != NULL)
+ pw_proxy_destroy((struct pw_proxy *)ctx->registry);
+
+ pw_core_disconnect(ctx->core);
+ pw_context_destroy(ctx->context);
+ pw_thread_loop_destroy(ctx->loop);
+ pw_deinit();
+ free(ctx);
+}
+
+static int vlc_pw_properties_set_var(struct pw_properties *props,
+ const char *name,
+ vlc_object_t *obj, const char *varname)
+{
+ char *str = var_InheritString(obj, varname);
+ int ret = -1;
+
+ if (str != NULL) {
+ ret = pw_properties_set(props, name, str);
+ free(str);
+ }
+ return ret;
+}
+
+static int vlc_pw_properties_set_env(struct pw_properties *props,
+ const char *name, const char *varname)
+{
+ const char *str = getenv(varname);
+ int ret = -1;
+
+ if (str != NULL)
+ ret = pw_properties_set(props, name, str);
+
+ return ret;
+}
+
+static int getusername(uid_t uid, char *restrict buf, size_t buflen)
+{
+ struct passwd pwbuf, *pw;
+
+ if (getpwuid_r(uid, &pwbuf, buf, buflen, &pw))
+ return -1;
+
+ memmove(buf, pw->pw_name, strlen(pw->pw_name) + 1);
+ return 0;
+}
+
+static int getmachineid(char *restrict buf, size_t buflen)
+{
+ if (buflen <= 32) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ FILE *stream = vlc_fopen("/var/lib/dbus/machine-id", "rt");
+ if (stream == NULL)
+ return -1;
+
+ int ret;
+
+ if (fread(buf, 1, 32, stream) == 32) {
+ buf[32] = '\0';
+ ret = 0;
+ } else {
+ errno = ENXIO;
+ ret = -1;
+ }
+ fclose(stream);
+ return ret;
+}
+
+struct vlc_pw_context *vlc_pw_connect(vlc_object_t *obj, const char *name)
+{
+ struct vlc_logger *logger = obj->logger;
+ const char *version = pw_get_library_version();
+ int err;
+
+ vlc_debug(logger, "using PipeWire run-time v%s (built v%s)", version,
+ pw_get_headers_version());
+
+ /* Safe use of pw_deinit() requires 0.3.49 */
+ if (strverscmp(version, "0.3.49") < 0) {
+ vlc_error(logger, "PipeWire version %s required, %s detected",
+ "0.3.49", version);
+ errno = ENOSYS;
+ return NULL;
+ }
+
+ struct vlc_pw_context *ctx = malloc(sizeof (*ctx));
+ if (unlikely(ctx == NULL))
+ return NULL;
+
+ pw_init(NULL, NULL);
+
+ ctx->logger = logger;
+ ctx->type = name;
+ ctx->loop = pw_thread_loop_new(name, NULL);
+ ctx->registry = NULL;
+
+ if (likely(ctx->loop != NULL)) {
+ struct spa_dict empty = SPA_DICT_INIT(NULL, 0);
+ struct pw_properties *props = pw_properties_new_dict(&empty);
+
+ if (likely(props != NULL)) {
+ char buf[256];
+
+ vlc_pw_properties_set_var(props, PW_KEY_APP_NAME, obj,
+ "user-agent");
+ vlc_pw_properties_set_var(props, PW_KEY_APP_ID, obj, "app-id");
+ vlc_pw_properties_set_var(props, PW_KEY_APP_VERSION, obj,
+ "app-version");
+ vlc_pw_properties_set_var(props, PW_KEY_APP_ICON_NAME, obj,
+ "app-icon-name");
+ pw_properties_set(props, PW_KEY_APP_LANGUAGE,
+ setlocale(LC_MESSAGES, NULL));
+ pw_properties_setf(props, PW_KEY_APP_PROCESS_ID, "%d",
+ (int)getpid());
+ /*PW_KEY_APP_PROCESS_BINARY*/
+
+ if (getusername(getuid(), buf, sizeof (buf)) == 0)
+ pw_properties_set(props, PW_KEY_APP_PROCESS_USER, buf);
+ if (gethostname(buf, sizeof (buf)) == 0)
+ pw_properties_set(props, PW_KEY_APP_PROCESS_HOST, buf);
+ if (getmachineid(buf, sizeof (buf)) == 0)
+ pw_properties_set(props, PW_KEY_APP_PROCESS_MACHINE_ID, buf);
+
+ vlc_pw_properties_set_env(props, PW_KEY_APP_PROCESS_SESSION_ID,
+ "XDG_SESSION_ID");
+ vlc_pw_properties_set_env(props, PW_KEY_WINDOW_X11_DISPLAY,
+ "DISPLAY");
+ }
+
+ ctx->context = pw_context_new(pw_thread_loop_get_loop(ctx->loop),
+ props, 0);
+
+ if (likely(ctx->context != NULL)) {
+ ctx->core = pw_context_connect(ctx->context, NULL, 0);
+
+ if (ctx->core != NULL) {
+ if (likely(pw_thread_loop_start(ctx->loop) == 0))
+ return ctx;
+
+ err = errno;
+ pw_core_disconnect(ctx->core);
+ } else {
+ err = errno;
+ vlc_pw_perror(ctx, "context connection");
+ }
+
+ pw_context_destroy(ctx->context);
+ } else
+ err = errno;
+
+ pw_thread_loop_destroy(ctx->loop);
+ } else
+ err = errno;
+
+ pw_deinit();
+ errno = err;
+ free(ctx);
+ return NULL;
+}
=====================================
modules/audio_output/vlc_pipewire.h
=====================================
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * vlc_pipewire.h: common PipeWire code
+ *****************************************************************************
+ * Copyright (C) 2022 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include <stdint.h>
+
+struct vlc_pw_context;
+struct vlc_logger;
+struct spa_dict;
+struct spa_hook;
+struct pw_properties;
+struct pw_registry_events;
+
+void vlc_pw_log(struct vlc_pw_context *ctx, int prio,
+ const char *file, unsigned int line, const char *func,
+ const char *fmt, ...);
+int vlc_pw_perror(struct vlc_pw_context *ctx, const char *file,
+ unsigned int line, const char *func, const char *desc);
+
+#define vlc_pw_log(ctx, prio, ...) \
+ vlc_pw_log(ctx, prio, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define vlc_pw_error(ctx, ...) \
+ vlc_pw_log(ctx, VLC_MSG_ERR, __VA_ARGS__)
+#define vlc_pw_warn(ctx, ...) \
+ vlc_pw_log(ctx, VLC_MSG_WARN, __VA_ARGS__)
+#define vlc_pw_debug(ctx, ...) \
+ vlc_pw_log(ctx, VLC_MSG_DBG, __VA_ARGS__)
+#define vlc_pw_perror(ctx, desc) \
+ vlc_pw_perror(ctx, __FILE__, __LINE__, __func__, desc)
+
+void vlc_pw_lock(struct vlc_pw_context *ctx);
+void vlc_pw_unlock(struct vlc_pw_context *ctx);
+void vlc_pw_signal(struct vlc_pw_context *ctx);
+void vlc_pw_wait(struct vlc_pw_context *ctx);
+
+struct pw_stream *vlc_pw_stream_new(struct vlc_pw_context *ctx,
+ const char *name, struct pw_properties *);
+
+void vlc_pw_roundtrip_unlocked(struct vlc_pw_context *ctx);
+
+int vlc_pw_registry_listen(struct vlc_pw_context *ctx, struct spa_hook *hook,
+ const struct pw_registry_events *, void *);
+
+void vlc_pw_disconnect(struct vlc_pw_context *ctx);
+struct vlc_pw_context *vlc_pw_connect(vlc_object_t *obj, const char *name);
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/933ecc78a1f3a4a363e70adf3151c6aae7fbe4af...2ef22bd1df5074d1cee1763630f9549311e0e0e2
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/933ecc78a1f3a4a363e70adf3151c6aae7fbe4af...2ef22bd1df5074d1cee1763630f9549311e0e0e2
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list