[vlc-commits] [Git][videolan/vlc][master] 5 commits: aout: modify Makefile.am to build libvlc_pipewire.la
Steve Lhomme (@robUx4)
gitlab at videolan.org
Mon Nov 25 12:08:35 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
5a707fca by Ayush Dey at 2024-11-25T11:54:37+00:00
aout: modify Makefile.am to build libvlc_pipewire.la
libvlc_pipewire.la will be used to build libpipewirelist_plugin.la and libpipewiresrc_plugin.la.
This follows the same approach as using libvlc_pulse.la to build libpulselist_plugin.la.
- - - - -
663f0f09 by Ayush Dey at 2024-11-25T11:54:37+00:00
pipewire: export common helper code
- - - - -
6d491372 by Ayush Dey at 2024-11-25T11:54:37+00:00
pipewire: initial services discovery module
- - - - -
78563bdb by Ayush Dey at 2024-11-25T11:54:37+00:00
pulse: fail if the server is Pipewire
Similar to the way it has been done in d74892967268ba7bb75c62fe4f638e1437e2fc57
- - - - -
445daaa5 by Ayush Dey at 2024-11-25T11:54:37+00:00
pipewire: initial access module
- - - - -
8 changed files:
- modules/access/Makefile.am
- + modules/access/pipewire.c
- modules/audio_output/Makefile.am
- modules/audio_output/vlc_pipewire.c
- modules/audio_output/vlc_pipewire.h
- modules/services_discovery/Makefile.am
- + modules/services_discovery/pipewire.c
- modules/services_discovery/pulse.c
Changes:
=====================================
modules/access/Makefile.am
=====================================
@@ -69,6 +69,13 @@ if HAVE_JACK
access_LTLIBRARIES += libaccess_jack_plugin.la
endif
+libpipewiresrc_plugin_la_SOURCES = access/pipewire.c
+libpipewiresrc_plugin_la_CFLAGS = $(AM_CFLAGS) $(PIPEWIRE_CFLAGS)
+libpipewiresrc_plugin_la_LIBADD = libvlc_pipewire.la $(PIPEWIRE_LIBS)
+if HAVE_PIPEWIRE
+access_LTLIBRARIES += libpipewiresrc_plugin.la
+endif
+
libpulsesrc_plugin_la_SOURCES = access/pulse.c
libpulsesrc_plugin_la_CFLAGS= $(AM_CFLAGS) $(PULSE_CFLAGS)
libpulsesrc_plugin_la_LIBADD = libvlc_pulse.la $(PULSE_LIBS)
=====================================
modules/access/pipewire.c
=====================================
@@ -0,0 +1,644 @@
+/*****************************************************************************
+ * @file pipewire.c
+ * @brief PipeWire input plugin for vlc
+ *****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * Authors: Ayush Dey <deyayush6 at gmail.com>
+ * Thomas Guillem <tguillem at videolan.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser 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 <vlc_common.h>
+#include <vlc_aout.h>
+#include <vlc_demux.h>
+#include <vlc_plugin.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/param/video/format-utils.h>
+#include <pipewire/pipewire.h>
+#include "audio_output/vlc_pipewire.h"
+
+#define HELP_TEXT N_( \
+ "Pass pipewire:// to open the default PipeWire source, " \
+ "or pipewire://SOURCE to open a specific source named SOURCE.")
+
+struct vlc_pw_stream
+{
+ struct vlc_pw_context *context;
+ struct pw_stream *stream;
+ struct spa_hook listener;
+ demux_t *demux;
+ struct spa_audio_info audio_format; /**< Audio information about the stream */
+ struct spa_video_info video_format; /**< Video information about the stream */
+ bool es_out_added; /**< Whether es_format_t structure created */
+};
+
+struct demux_sys_t
+{
+ struct vlc_pw_context *context;
+ struct vlc_pw_stream *stream;
+ struct spa_hook listener;
+ es_out_id_t *es;
+ unsigned framesize; /**< Byte size of a sample */
+ vlc_tick_t caching; /**< Caching value */
+ vlc_tick_t interval;
+ bool discontinuity; /**< The next frame will not follow the last one */
+ enum es_format_category_e media_type;
+ uint8_t chans_table[AOUT_CHAN_MAX]; /**< Channels order table */
+ uint8_t chans_to_reorder; /**< Number of channels to reorder */
+ vlc_fourcc_t format; /**< Sample format */
+};
+
+/** PipeWire audio sample (PCM) format to VLC codec */
+static vlc_fourcc_t spa_audio_format_to_fourcc(enum spa_audio_format spa_format)
+{
+ switch (spa_format)
+ {
+ case SPA_AUDIO_FORMAT_U8:
+ return VLC_CODEC_U8;
+ case SPA_AUDIO_FORMAT_ALAW:
+ return VLC_CODEC_ALAW;
+ case SPA_AUDIO_FORMAT_ULAW:
+ return VLC_CODEC_MULAW;
+ case SPA_AUDIO_FORMAT_S16_LE:
+ return VLC_CODEC_S16L;
+ case SPA_AUDIO_FORMAT_S16_BE:
+ return VLC_CODEC_S16B;
+ case SPA_AUDIO_FORMAT_F32_LE:
+ return VLC_CODEC_F32L;
+ case SPA_AUDIO_FORMAT_F32_BE:
+ return VLC_CODEC_F32B;
+ case SPA_AUDIO_FORMAT_S32_LE:
+ return VLC_CODEC_S32L;
+ case SPA_AUDIO_FORMAT_S32_BE:
+ return VLC_CODEC_S32B;
+ case SPA_AUDIO_FORMAT_S24_LE:
+ return VLC_CODEC_S24L;
+ case SPA_AUDIO_FORMAT_S24_BE:
+ return VLC_CODEC_S24B;
+ case SPA_AUDIO_FORMAT_S24_32_LE:
+ return VLC_CODEC_S24L32;
+ case SPA_AUDIO_FORMAT_S24_32_BE:
+ return VLC_CODEC_S24B32;
+ default:
+ return 0;
+ }
+}
+
+/** PipeWire video sample (PCM) format to VLC codec */
+static vlc_fourcc_t spa_video_format_to_fourcc(enum spa_video_format spa_format)
+{
+ switch (spa_format)
+ {
+ case SPA_VIDEO_FORMAT_YUY2:
+ return VLC_CODEC_YUYV;
+ case SPA_VIDEO_FORMAT_RGB:
+ return VLC_CODEC_RGB24;
+ case SPA_VIDEO_FORMAT_RGBA:
+ return VLC_CODEC_RGBA;
+ case SPA_VIDEO_FORMAT_RGBx:
+ return VLC_CODEC_RGBX;
+ case SPA_VIDEO_FORMAT_BGRx:
+ return VLC_CODEC_BGRX;
+ default:
+ return 0;
+ }
+}
+
+/** PipeWire to VLC channel mapping */
+static const uint16_t vlc_chans[] = {
+ [SPA_AUDIO_CHANNEL_MONO] = AOUT_CHAN_CENTER,
+ [SPA_AUDIO_CHANNEL_FL] = AOUT_CHAN_LEFT,
+ [SPA_AUDIO_CHANNEL_FR] = AOUT_CHAN_RIGHT,
+ [SPA_AUDIO_CHANNEL_RL] = AOUT_CHAN_REARLEFT,
+ [SPA_AUDIO_CHANNEL_RR] = AOUT_CHAN_REARRIGHT,
+ [SPA_AUDIO_CHANNEL_FC] = AOUT_CHAN_CENTER,
+ [SPA_AUDIO_CHANNEL_LFE] = AOUT_CHAN_LFE,
+ [SPA_AUDIO_CHANNEL_SL] = AOUT_CHAN_MIDDLELEFT,
+ [SPA_AUDIO_CHANNEL_SR] = AOUT_CHAN_MIDDLERIGHT,
+ [SPA_AUDIO_CHANNEL_RC] = AOUT_CHAN_REARCENTER,
+};
+
+/**
+ * Initializes the `es_format_t fmt` object (audio properties)
+ */
+static int initialize_audio_format(struct vlc_pw_stream *s, es_format_t *fmt, vlc_fourcc_t format)
+{
+ demux_t *demux = s->demux;
+ struct demux_sys_t *sys = demux->p_sys;
+ es_format_Init(fmt, AUDIO_ES, format);
+ uint32_t channels = s->audio_format.info.raw.channels;
+
+ if (channels == 0)
+ {
+ vlc_pw_error(sys->context, "source should have at least one channel");
+ return -1;
+ }
+
+ uint32_t chans_in[AOUT_CHAN_MAX];
+ for (uint32_t i = 0; i < channels; i++)
+ {
+ uint16_t vlc_chan = 0;
+ uint32_t pos = s->audio_format.info.raw.position[i];
+ if (pos < sizeof (vlc_chans) / sizeof (vlc_chans[0]))
+ vlc_chan = vlc_chans[pos];
+
+ if (vlc_chan == 0)
+ {
+ vlc_pw_error(sys->context, "%s channel %u position %u", "unsupported", i, pos);
+ return -1;
+ }
+ chans_in[i] = vlc_chan;
+ fmt->audio.i_physical_channels |= vlc_chan;
+ }
+
+ sys->chans_to_reorder = aout_CheckChannelReorder(chans_in, NULL, fmt->audio.i_physical_channels,
+ sys->chans_table);
+ sys->format = format;
+ fmt->audio.i_format = format;
+ aout_FormatPrepare(&fmt->audio);
+ fmt->audio.i_rate = s->audio_format.info.raw.rate;
+ fmt->audio.i_blockalign = fmt->audio.i_bitspersample * fmt->audio.i_channels / 8;
+ fmt->i_bitrate = fmt->audio.i_bitspersample * fmt->audio.i_channels * fmt->audio.i_rate;
+ sys->framesize = fmt->audio.i_blockalign;
+ return 0;
+}
+
+/**
+ * Initializes the `es_format_t fmt` object (video properties)
+ */
+static void initialize_video_format(struct vlc_pw_stream *s, es_format_t *fmt, vlc_fourcc_t format)
+{
+ demux_t *demux = s->demux;
+ struct demux_sys_t *sys = demux->p_sys;
+ es_format_Init(fmt, VIDEO_ES, format);
+ fmt->video.i_frame_rate = s->video_format.info.raw.framerate.num;
+ fmt->video.i_frame_rate_base = s->video_format.info.raw.framerate.denom;
+ video_format_Setup(&fmt->video, format, s->video_format.info.raw.size.width,
+ s->video_format.info.raw.size.height, s->video_format.info.raw.size.width,
+ s->video_format.info.raw.size.height,
+ s->video_format.info.raw.pixel_aspect_ratio.num,
+ s->video_format.info.raw.pixel_aspect_ratio.denom);
+ sys->interval = vlc_tick_from_samples(fmt->video.i_frame_rate,
+ fmt->video.i_frame_rate_base);
+}
+
+/**
+ * Parameter change callback.
+ *
+ * This callback is invoked upon stream initialization and
+ * the ES is initialized before the stream starts capturing data.
+ */
+static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct vlc_pw_stream *s = data;
+
+ if(s->es_out_added) /* ES already initialized and added */
+ return;
+
+ demux_t *demux = s->demux;
+ struct demux_sys_t *sys = demux->p_sys;
+ es_format_t fmt;
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ if (sys->media_type == AUDIO_ES)
+ {
+ if (spa_format_parse(param, &s->audio_format.media_type, &s->audio_format.media_subtype) < 0)
+ return;
+
+ if (s->audio_format.media_type != SPA_MEDIA_TYPE_audio ||
+ s->audio_format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
+ return;
+
+ if (spa_format_audio_raw_parse(param, &s->audio_format.info.raw) < 0)
+ return;
+
+ vlc_fourcc_t format = spa_audio_format_to_fourcc(s->audio_format.info.raw.format);
+ if (format == 0)
+ {
+ vlc_pw_error(sys->context, "unsupported PipeWire sample format %u",
+ (unsigned)s->audio_format.info.raw.format);
+ return;
+ }
+
+ if (initialize_audio_format(s, &fmt, format))
+ return;
+ }
+ else
+ {
+ if (spa_format_parse(param, &s->video_format.media_type, &s->video_format.media_subtype) < 0)
+ return;
+
+ if (s->video_format.media_type != SPA_MEDIA_TYPE_video ||
+ s->video_format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
+ return;
+
+ if (spa_format_video_raw_parse(param, &s->video_format.info.raw) < 0)
+ return;
+
+ vlc_fourcc_t format = spa_video_format_to_fourcc(s->video_format.info.raw.format);
+ if (format == 0)
+ {
+ vlc_pw_error(sys->context, "unsupported PipeWire sample format %u",
+ (unsigned)s->video_format.info.raw.format);
+ return;
+ }
+
+ initialize_video_format(s, &fmt, format);
+ }
+
+ sys->es = es_out_Add (demux->out, &fmt);
+ s->es_out_added = true;
+}
+
+/**
+ * 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);
+ 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 vlc_tick_t stream_update_latency(struct vlc_pw_stream *s, vlc_tick_t *now)
+{
+ struct pw_time ts;
+
+#if PW_CHECK_VERSION(1, 1, 0)
+ /* PW monotonic clock, same than vlc_tick_now() */
+ *now = VLC_TICK_FROM_NS(pw_stream_get_nsec(s->stream));
+#else
+ *now = vlc_tick_now();
+#endif
+
+ if (pw_stream_get_time_n(s->stream, &ts, sizeof (ts)) < 0
+ || ts.rate.denom == 0)
+ return VLC_TICK_INVALID;
+
+ return (VLC_TICK_FROM_NS(ts.now) + vlc_tick_from_frac(ts.delay * ts.rate.num, ts.rate.denom));
+}
+
+/**
+ * Stream processing callback.
+ *
+ * This consumes data from the server buffer.
+ */
+static void on_process(void *data)
+{
+ struct vlc_pw_stream *s = data;
+ demux_t *demux = s->demux;
+ struct demux_sys_t *sys = demux->p_sys;
+ vlc_tick_t now;
+ vlc_tick_t val = stream_update_latency(s, &now);
+ struct pw_buffer *b = pw_stream_dequeue_buffer(s->stream);
+
+ if (unlikely(b == NULL))
+ return;
+
+ 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 length = chunk->size;
+
+ /*
+ * If timing data is not available (val == VLC_TICK_INVALID), we can at least
+ * assume that the capture delay is no less than zero.
+ */
+ vlc_tick_t pts = (val != VLC_TICK_INVALID) ? val : now;
+ es_out_SetPCR(demux->out, pts);
+ if (unlikely(sys->es == NULL))
+ goto end;
+
+ vlc_frame_t *frame = vlc_frame_Alloc(length);
+ if (likely(frame != NULL))
+ {
+ memcpy(frame->p_buffer, dst + chunk->offset, length);
+ if (sys->media_type == AUDIO_ES)
+ {
+ frame->i_nb_samples = length / sys->framesize;
+ if (sys->chans_to_reorder != 0)
+ aout_ChannelReorder(frame->p_buffer, length,
+ sys->chans_to_reorder, sys->chans_table,
+ sys->format);
+ }
+ frame->i_dts = frame->i_pts = pts;
+ if (sys->discontinuity)
+ {
+ frame->i_flags |= VLC_FRAME_FLAG_DISCONTINUITY;
+ sys->discontinuity = false;
+ }
+
+ es_out_Send(demux->out, sys->es, frame);
+ }
+ else
+ sys->discontinuity = true;
+
+ end:
+ pw_stream_queue_buffer(s->stream, b);
+}
+
+/**
+ * Disconnects a stream from source and destroys it.
+ */
+static void vlc_pw_stream_destroy(struct vlc_pw_stream *s)
+{
+ vlc_pw_lock(s->context);
+ pw_stream_flush(s->stream, false);
+ pw_stream_disconnect(s->stream);
+ pw_stream_destroy(s->stream);
+ vlc_pw_unlock(s->context);
+ free(s);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+};
+
+static int Control(demux_t *demux, int query, va_list ap)
+{
+ struct demux_sys_t *sys = demux->p_sys;
+ struct vlc_pw_stream *s = sys->stream;
+
+ switch (query)
+ {
+ case DEMUX_GET_TIME:
+ {
+ struct pw_time ts;
+
+ if (pw_stream_get_time_n(s->stream, &ts, sizeof(ts)) < 0)
+ return VLC_EGENERIC;
+ *(va_arg(ap, vlc_tick_t *)) = VLC_TICK_FROM_NS(ts.now);
+ break;
+ }
+
+ case DEMUX_GET_PTS_DELAY:
+ {
+ vlc_tick_t *pd = va_arg(ap, vlc_tick_t *);
+
+ *pd = sys->caching;
+ /* in case of VIDEO_ES, cap at one frame, more than enough */
+ if (sys->media_type == VIDEO_ES && *pd > sys->interval)
+ *pd = sys->interval;
+ break;
+ }
+
+ case DEMUX_HAS_UNSUPPORTED_META:
+ case DEMUX_CAN_RECORD:
+ case DEMUX_CAN_PAUSE:
+ case DEMUX_CAN_CONTROL_PACE:
+ case DEMUX_CAN_CONTROL_RATE:
+ case DEMUX_CAN_SEEK:
+ *(va_arg(ap, bool *)) = false;
+ break;
+
+ default:
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+/**
+ * Global callback
+ * This callback is used to find whether the media_type is AUDIO_ES or VIDEO_ES.
+ */
+static void registry_global(void *data, uint32_t id, uint32_t perms,
+ const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ demux_t *demux = data;
+ struct demux_sys_t *sys = demux->p_sys;
+
+ if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
+ {
+ const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ if (unlikely(name == NULL))
+ return;
+
+ const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (class == NULL)
+ return;
+
+ if (strstr(class, "Source") == NULL)
+ return;
+
+ if (strcmp(name, demux->psz_location) == 0)
+ {
+ if (strstr(class, "Video") != NULL)
+ sys->media_type = VIDEO_ES;
+ }
+ }
+}
+
+static const struct pw_registry_events global_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_global
+};
+
+static int Open(vlc_object_t *obj)
+{
+ demux_t *demux = (demux_t *)obj;
+
+ if (demux->out == NULL)
+ return VLC_EGENERIC;
+
+ struct demux_sys_t *sys = malloc(sizeof (*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ sys->context = vlc_pw_connect(obj, "access");
+ if (sys->context == NULL)
+ {
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ sys->stream = NULL;
+ sys->es = NULL;
+ sys->discontinuity = false;
+ sys->caching = VLC_TICK_FROM_MS( var_InheritInteger(obj, "live-caching") );
+ sys->listener = (struct spa_hook){ };
+ sys->media_type = AUDIO_ES;
+ demux->p_sys = sys;
+
+ /* The `sys->media_type` is set to either `AUDIO_ES` or `VIDEO_ES` during this call,
+ depending on the media type of the capture node. */
+ vlc_pw_lock(sys->context);
+ vlc_pw_registry_listen(sys->context, &sys->listener, &global_events, demux);
+ vlc_pw_roundtrip_unlocked(sys->context);
+ vlc_pw_unlock(sys->context);
+
+ /* Stream parameters */
+ struct spa_audio_info_raw rawaudiofmt = {
+ .rate = 48000,
+ .channels = 2,
+ .format = SPA_AUDIO_FORMAT_S16,
+ .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }
+ };
+
+ /* Assemble the stream format and properties */
+ const struct spa_pod *params[1];
+ unsigned char buf[1024];
+ struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof (buf));
+ struct pw_properties *props;
+ const char *stream_name;
+
+ if (sys->media_type == AUDIO_ES)
+ {
+ params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat,
+ &rawaudiofmt);
+
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Raw",
+ NULL);
+ stream_name = "audio stream";
+ }
+ else
+ {
+ params[0] = spa_pod_builder_add_object(&builder,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+ SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(6,
+ SPA_VIDEO_FORMAT_RGB,
+ SPA_VIDEO_FORMAT_RGB,
+ SPA_VIDEO_FORMAT_RGBA,
+ SPA_VIDEO_FORMAT_RGBx,
+ SPA_VIDEO_FORMAT_BGRx,
+ SPA_VIDEO_FORMAT_YUY2),
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1, 1),
+ &SPA_RECTANGLE(4096, 4096)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
+ &SPA_FRACTION(25, 1),
+ &SPA_FRACTION(0, 1),
+ &SPA_FRACTION(1000, 1)));
+
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Camera",
+ NULL);
+ stream_name = "video stream";
+ }
+
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, demux->psz_location);
+
+ /* Create the stream */
+ struct vlc_pw_stream *s = malloc(sizeof (*s));
+ if (unlikely(s == NULL))
+ {
+ pw_properties_free(props);
+ vlc_pw_disconnect(sys->context);
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ enum pw_stream_flags flags =
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS;
+
+ enum pw_stream_state state;
+
+ s->context = sys->context;
+ s->listener = (struct spa_hook){ };
+ s->es_out_added = false;
+ s->demux = demux;
+
+ vlc_pw_lock(s->context);
+ s->stream = vlc_pw_stream_new(s->context, stream_name, props);
+ if (unlikely(s->stream == NULL))
+ {
+ vlc_pw_unlock(s->context);
+ free(s);
+ pw_properties_free(props);
+ vlc_pw_disconnect(sys->context);
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ sys->stream = s;
+ pw_stream_add_listener(s->stream, &s->listener, &stream_events, s);
+ pw_stream_connect(s->stream, PW_DIRECTION_INPUT, PW_ID_ANY, flags,
+ params, ARRAY_SIZE(params));
+
+ /* Wait for the stream to be ready */
+ while ((state = pw_stream_get_state(s->stream,
+ NULL)) == PW_STREAM_STATE_CONNECTING)
+ 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);
+ vlc_pw_disconnect(sys->context);
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ demux->pf_demux = NULL;
+ demux->pf_control = Control;
+ return VLC_SUCCESS;
+}
+
+static void Close (vlc_object_t *obj)
+{
+ demux_t *demux = (demux_t *)obj;
+ struct demux_sys_t *sys = demux->p_sys;
+ vlc_pw_stream_destroy(sys->stream);
+ vlc_pw_disconnect(sys->context);
+ free(sys);
+}
+
+vlc_module_begin ()
+ set_shortname (N_("PipeWire"))
+ set_description (N_("PipeWire input"))
+ set_capability ("access", 0)
+ set_subcategory (SUBCAT_INPUT_ACCESS)
+ set_help (HELP_TEXT)
+
+ add_shortcut ("pipewire", "pw")
+ set_callbacks (Open, Close)
+vlc_module_end ()
=====================================
modules/audio_output/Makefile.am
=====================================
@@ -61,12 +61,18 @@ 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
+libvlc_pipewire_la_SOURCES = audio_output/vlc_pipewire.c audio_output/vlc_pipewire.h
+libvlc_pipewire_la_CFLAGS = $(AM_CFLAGS) $(PIPEWIRE_CFLAGS)
+libvlc_pipewire_la_LIBADD = $(PIPEWIRE_LIBS) $(LTLIBVLCCORE)
+libvlc_pipewire_la_LDFLAGS = \
+ -no-undefined \
+ -export-symbols-regex ^vlc_pw_ \
+ -version-info 0:0:0
+libaout_pipewire_plugin_la_SOURCES = audio_output/pipewire.c
libaout_pipewire_plugin_la_CFLAGS = $(AM_CFLAGS) $(PIPEWIRE_CFLAGS)
-libaout_pipewire_plugin_la_LIBADD = $(PIPEWIRE_LIBS) $(LIBM)
+libaout_pipewire_plugin_la_LIBADD = libvlc_pipewire.la $(PIPEWIRE_LIBS) $(LIBM)
if HAVE_PIPEWIRE
+pkglib_LTLIBRARIES += libvlc_pipewire.la
aout_LTLIBRARIES += libaout_pipewire_plugin.la
endif
=====================================
modules/audio_output/vlc_pipewire.c
=====================================
@@ -34,6 +34,8 @@
#include <vlc_tick.h>
#include "vlc_pipewire.h"
+const char vlc_module_name[] = "vlcpipewire";
+
struct vlc_pw_context {
struct pw_thread_loop *loop;
struct pw_context *context;
=====================================
modules/audio_output/vlc_pipewire.h
=====================================
@@ -27,10 +27,10 @@ struct spa_hook;
struct pw_properties;
struct pw_registry_events;
-void vlc_pw_log(struct vlc_pw_context *ctx, int prio,
+VLC_EXPORT 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,
+VLC_EXPORT 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, ...) \
@@ -44,18 +44,18 @@ int vlc_pw_perror(struct vlc_pw_context *ctx, const char *file,
#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);
+VLC_EXPORT void vlc_pw_lock(struct vlc_pw_context *ctx);
+VLC_EXPORT void vlc_pw_unlock(struct vlc_pw_context *ctx);
+VLC_EXPORT void vlc_pw_signal(struct vlc_pw_context *ctx);
+VLC_EXPORT void vlc_pw_wait(struct vlc_pw_context *ctx);
-struct pw_stream *vlc_pw_stream_new(struct vlc_pw_context *ctx,
+VLC_EXPORT 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);
+VLC_EXPORT void vlc_pw_roundtrip_unlocked(struct vlc_pw_context *ctx);
-int vlc_pw_registry_listen(struct vlc_pw_context *ctx, struct spa_hook *hook,
+VLC_EXPORT 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);
+VLC_EXPORT void vlc_pw_disconnect(struct vlc_pw_context *ctx);
+VLC_EXPORT struct vlc_pw_context *vlc_pw_connect(vlc_object_t *obj, const char *name);
=====================================
modules/services_discovery/Makefile.am
=====================================
@@ -41,6 +41,13 @@ if HAVE_OSX
libupnp_plugin_la_LDFLAGS += -Wl,-framework,CoreFoundation,-framework,SystemConfiguration
endif
+libpipewirelist_plugin_la_SOURCES = services_discovery/pipewire.c
+libpipewirelist_plugin_la_CFLAGS = $(AM_CFLAGS) $(PIPEWIRE_CFLAGS)
+libpipewirelist_plugin_la_LIBADD = libvlc_pipewire.la $(PIPEWIRE_LIBS)
+if HAVE_PIPEWIRE
+sd_LTLIBRARIES += libpipewirelist_plugin.la
+endif
+
libpulselist_plugin_la_SOURCES = services_discovery/pulse.c
libpulselist_plugin_la_CFLAGS = $(AM_CFLAGS) $(PULSE_CFLAGS)
libpulselist_plugin_la_LIBADD = libvlc_pulse.la $(PULSE_LIBS)
=====================================
modules/services_discovery/pipewire.c
=====================================
@@ -0,0 +1,305 @@
+/*****************************************************************************
+ * @file pipewire.c
+ * @brief List of PipeWire sources for VLC media player
+ *****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * Authors: Ayush Dey <deyayush6 at gmail.com>
+ * Thomas Guillem <tguillem at videolan.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser 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 <search.h>
+#include <assert.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_services_discovery.h>
+#include <pipewire/pipewire.h>
+#include "audio_output/vlc_pipewire.h"
+
+struct services_discovery_sys_t
+{
+ struct vlc_pw_context *context;
+ struct spa_hook listener;
+ void *nodes;
+ void *root_card;
+};
+
+struct vlc_pw_node
+{
+ uint32_t id;
+ uint64_t serial;
+ input_item_t *item;
+ services_discovery_t *sd;
+};
+
+struct card
+{
+ input_item_t *item;
+ services_discovery_t *sd;
+ char name[];
+};
+
+static void DestroySource (void *data)
+{
+ struct vlc_pw_node *node = data;
+
+ services_discovery_RemoveItem (node->sd, node->item);
+ input_item_Release (node->item);
+ free (node);
+}
+
+static void DestroyCard(void *data)
+{
+ struct card *c = data;
+
+ services_discovery_RemoveItem(c->sd, c->item);
+ input_item_Release(c->item);
+ free(c);
+}
+
+/**
+ * Compares two nodes by ID (to support binary search).
+ */
+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 cmpcard (const void *a, const void *b)
+{
+ const struct card *ca = a, *cb = b;
+ return strcmp(ca->name, cb->name);
+}
+
+static input_item_t *AddCard (services_discovery_t *sd, const struct spa_dict *props)
+{
+ struct services_discovery_sys_t *sys = sd->p_sys;
+
+ const char *card_name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ if (unlikely(card_name == NULL))
+ card_name = N_("Generic");
+
+ struct card *c = malloc(sizeof(*c) + strlen(card_name) + 1);
+ if (unlikely(c == NULL))
+ return NULL;
+ strcpy(c->name, card_name);
+
+ void **cp = tsearch(c, &sys->root_card, cmpcard);
+ if (cp == NULL) /* Out-of-memory */
+ {
+ free(c);
+ return NULL;
+ }
+ if (*cp != c)
+ {
+ free(c);
+ c = *cp;
+ assert(c->item != NULL);
+ return c->item;
+ }
+
+ c->item = input_item_NewExt("vlc://nop", c->name,
+ INPUT_DURATION_INDEFINITE,
+ ITEM_TYPE_NODE, ITEM_LOCAL);
+
+ if (unlikely(c->item == NULL))
+ {
+ tdelete(c, &sys->root_card, cmpcard);
+ free(c);
+ return NULL;
+ }
+ services_discovery_AddItem(sd, c->item);
+ c->sd = sd;
+
+ return c->item;
+}
+
+static void registry_node(services_discovery_t *sd, uint32_t id, uint32_t perms,
+ uint32_t version, const struct spa_dict *props)
+{
+ struct services_discovery_sys_t *sys = sd->p_sys;
+ uint64_t serial;
+ char *mrl;
+
+ if (props == NULL)
+ return;
+
+ const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (class == NULL)
+ return;
+
+ if (strstr(class, "Source") == NULL)
+ return; /* Not a source */
+
+ if (!spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &serial, 0))
+ return;
+
+ const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ if (unlikely(name == NULL))
+ return;
+
+ if (unlikely(asprintf (&mrl, "pipewire://%s", name) == -1))
+ return;
+
+ const char *desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
+ if (unlikely(desc == NULL))
+ desc = "???";
+
+ vlc_pw_debug(sys->context, "adding %s (%s)", name, desc);
+ input_item_t *item = input_item_NewCard (mrl, desc);
+ free (mrl);
+ if (unlikely(item == NULL))
+ return;
+
+ struct vlc_pw_node *node = malloc(sizeof (*node));
+ if (unlikely(node == NULL))
+ {
+ input_item_Release (item);
+ return;
+ }
+
+ node->id = id;
+ node->serial = serial;
+ node->item = item;
+
+ struct vlc_pw_node **pp = tsearch(node, &sys->nodes, node_by_id);
+ if (unlikely(pp == NULL))
+ { /* Memory allocation error in the tree */
+ free(node);
+ input_item_Release (item);
+ return;
+ }
+ if (*pp != node)
+ { /* Existing node, update it */
+ free(node);
+ node = *pp;
+ input_item_SetURI (node->item, item->psz_uri);
+ input_item_SetName (node->item, item->psz_name);
+ input_item_Release (item);
+ return;
+ }
+
+ input_item_t *card = AddCard(sd, props);
+ services_discovery_AddSubItem(sd, card, item);
+ node->sd = sd;
+ return;
+}
+
+/**
+ * Global callback.
+ *
+ * This gets called for every initial, then every new object from the PipeWire
+ * server. We can find the usable sources 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)
+{
+ services_discovery_t *sd = data;
+
+ if (strcmp(name, PW_TYPE_INTERFACE_Node) == 0)
+ registry_node(sd, id, perms, version, props);
+}
+
+/**
+ * Global removal callback.
+ *
+ * This gets called when an object disappers. We can detect when a source is unplugged here.
+ */
+static void registry_global_remove(void *data, uint32_t id)
+{
+ services_discovery_t *sd = data;
+ struct services_discovery_sys_t *sys = sd->p_sys;
+ struct vlc_pw_node key = { .id = id };
+ struct vlc_pw_node **pp = tfind(&key, &sys->nodes, node_by_id);
+ if (pp == NULL)
+ return;
+
+ struct vlc_pw_node *node = *pp;
+ tdelete(node, &sys->nodes, node_by_id);
+ DestroySource (node);
+}
+
+static const struct pw_registry_events events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_global,
+ .global_remove = registry_global_remove
+};
+
+static int Open (vlc_object_t *obj)
+{
+ services_discovery_t *sd = (services_discovery_t *)obj;
+
+ struct services_discovery_sys_t *sys = malloc(sizeof (*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ sys->context = vlc_pw_connect(obj, "services discovery");
+ if (sys->context == NULL)
+ {
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ sd->p_sys = sys;
+ sd->description = _("Audio and Video capture");
+ sys->nodes = NULL;
+ sys->root_card = NULL;
+ sys->listener = (struct spa_hook){ };
+
+ vlc_pw_lock(sys->context);
+ /* Subscribe for source events */
+ vlc_pw_registry_listen(sys->context, &sys->listener, &events, sd);
+ vlc_pw_roundtrip_unlocked(sys->context); /* Enumerate existing sources */
+ vlc_pw_unlock(sys->context);
+ return VLC_SUCCESS;
+}
+
+static void Close (vlc_object_t *obj)
+{
+ services_discovery_t *sd = (services_discovery_t *)obj;
+ struct services_discovery_sys_t *sys = sd->p_sys;
+
+ vlc_pw_disconnect(sys->context);
+ tdestroy(sys->nodes, DestroySource);
+ tdestroy(sys->root_card, DestroyCard);
+ free (sys);
+}
+
+VLC_SD_PROBE_HELPER("pipewire", N_("Audio and Video capture"), SD_CAT_DEVICES);
+
+vlc_module_begin ()
+ set_shortname (N_("Audio and Video capture"))
+ set_description (N_("Audio and Video capture (PipeWire)"))
+ set_subcategory (SUBCAT_PLAYLIST_SD)
+ set_capability ("services_discovery", 0)
+ set_callbacks (Open, Close)
+ add_shortcut ("pipewire", "pw")
+
+ VLC_SD_PROBE_SUBMODULE
+vlc_module_end ()
=====================================
modules/services_discovery/pulse.c
=====================================
@@ -36,6 +36,8 @@
static int Open (vlc_object_t *);
static void Close (vlc_object_t *);
+#include <vlc_modules.h>
+
VLC_SD_PROBE_HELPER("pulse", N_("Audio capture"), SD_CAT_DEVICES);
vlc_module_begin ()
@@ -55,12 +57,29 @@ typedef struct
void *root_card;
pa_context *context;
pa_threaded_mainloop *mainloop;
+ bool is_pipewire;
} services_discovery_sys_t;
static void SourceCallback(pa_context *, const pa_source_info *, int, void *);
static void ContextCallback(pa_context *, pa_subscription_event_type_t,
uint32_t, void *);
+static void server_info_cb(pa_context *ctx, const pa_server_info *info,
+ void *userdata)
+{
+ services_discovery_t *sd = userdata;
+
+ msg_Dbg(sd, "server %s version %s on %s@%s", info->server_name,
+ info->server_version, info->user_name, info->host_name);
+
+ services_discovery_sys_t *sys = sd->p_sys;
+
+ sys->is_pipewire = strcasestr(info->server_name, "pipewire") != NULL;
+ pa_threaded_mainloop_signal(sys->mainloop, 0);
+
+ (void) ctx;
+}
+
static int Open (vlc_object_t *obj)
{
services_discovery_t *sd = (services_discovery_t *)obj;
@@ -83,10 +102,27 @@ static int Open (vlc_object_t *obj)
sys->context = ctx;
sys->root = NULL;
sys->root_card = NULL;
+ sys->is_pipewire = false;
+
+ pa_threaded_mainloop_lock(sys->mainloop);
+ op = pa_context_get_server_info(sys->context, server_info_cb, sd);
+ if (likely(op != NULL))
+ {
+ while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(sys->mainloop);
+ if (sys->is_pipewire && module_exists("pipewirelist"))
+ {
+ msg_Dbg(sd, "refusing to use PipeWire");
+ pa_threaded_mainloop_unlock(sys->mainloop);
+ vlc_pa_disconnect(obj, sys->context, sys->mainloop);
+ free(sys);
+ return -ENOTSUP;
+ }
+ pa_operation_unref(op);
+ }
/* Subscribe for source events */
const pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SOURCE;
- pa_threaded_mainloop_lock (sys->mainloop);
pa_context_set_subscribe_callback (ctx, ContextCallback, sd);
op = pa_context_subscribe (ctx, mask, NULL, NULL);
if (likely(op != NULL))
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/64d46c2ba05c591b381306b8c7db1ae72ae0342a...445daaa587e71441b8eaa448639d3da85e327449
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/64d46c2ba05c591b381306b8c7db1ae72ae0342a...445daaa587e71441b8eaa448639d3da85e327449
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