You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
21 KiB
644 lines
21 KiB
/*****************************************************************************
|
|
* @file pipewire.c
|
|
* @brief PipeWire input plugin for vlc
|
|
*****************************************************************************
|
|
* Copyright (C) 2024 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Ayush Dey <deyayush6@gmail.com>
|
|
* Thomas Guillem <tguillem@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 ()
|
|
|