mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-06 06:46:29 -04:00
For encoded formats, buffer size is the size of the actual data in the buffer and may change for each frame depending on the content. Thus, configuring the buffer pool of the pipewiresrc with the size of the first buffer may be insufficient for later buffers. Configure the buffer pool to the maximum size of the first upstream buffer and assume that the following buffers will be allocated with the same size as the first buffer.
927 lines
28 KiB
C
927 lines
28 KiB
C
/* GStreamer */
|
|
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
/**
|
|
* SECTION:element-pipewiresink
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch -v videotestsrc ! pipewiresink
|
|
* ]| Sends a test video source to PipeWire
|
|
* </refsect2>
|
|
*/
|
|
|
|
#define PW_ENABLE_DEPRECATED
|
|
|
|
#include "config.h"
|
|
#include "gstpipewiresink.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <spa/pod/builder.h>
|
|
#include <spa/utils/result.h>
|
|
|
|
#include <gst/video/video.h>
|
|
|
|
#include "gstpipewireformat.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug);
|
|
#define GST_CAT_DEFAULT pipewire_sink_debug
|
|
|
|
#define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT
|
|
|
|
#define MIN_BUFFERS 8u
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PATH,
|
|
PROP_TARGET_OBJECT,
|
|
PROP_CLIENT_NAME,
|
|
PROP_CLIENT_PROPERTIES,
|
|
PROP_STREAM_PROPERTIES,
|
|
PROP_MODE,
|
|
PROP_FD
|
|
};
|
|
|
|
GType
|
|
gst_pipewire_sink_mode_get_type (void)
|
|
{
|
|
static gsize mode_type = 0;
|
|
static const GEnumValue mode[] = {
|
|
{GST_PIPEWIRE_SINK_MODE_DEFAULT, "GST_PIPEWIRE_SINK_MODE_DEFAULT", "default"},
|
|
{GST_PIPEWIRE_SINK_MODE_RENDER, "GST_PIPEWIRE_SINK_MODE_RENDER", "render"},
|
|
{GST_PIPEWIRE_SINK_MODE_PROVIDE, "GST_PIPEWIRE_SINK_MODE_PROVIDE", "provide"},
|
|
{0, NULL, NULL},
|
|
};
|
|
|
|
if (g_once_init_enter (&mode_type)) {
|
|
GType tmp =
|
|
g_enum_register_static ("GstPipeWireSinkMode", mode);
|
|
g_once_init_leave (&mode_type, tmp);
|
|
}
|
|
|
|
return (GType) mode_type;
|
|
}
|
|
|
|
|
|
static GstStaticPadTemplate gst_pipewire_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS_ANY
|
|
);
|
|
|
|
#define gst_pipewire_sink_parent_class parent_class
|
|
G_DEFINE_TYPE (GstPipeWireSink, gst_pipewire_sink, GST_TYPE_BASE_SINK);
|
|
|
|
static void gst_pipewire_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_pipewire_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static GstStateChangeReturn
|
|
gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition);
|
|
|
|
static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps);
|
|
static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink,
|
|
GstCaps * caps);
|
|
|
|
static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink,
|
|
GstBuffer * buffer);
|
|
static gboolean gst_pipewire_sink_start (GstBaseSink * basesink);
|
|
static gboolean gst_pipewire_sink_stop (GstBaseSink * basesink);
|
|
|
|
static void
|
|
gst_pipewire_sink_finalize (GObject * object)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
|
|
|
|
g_object_unref (pwsink->pool);
|
|
|
|
if (pwsink->stream_properties)
|
|
gst_structure_free (pwsink->stream_properties);
|
|
if (pwsink->client_properties)
|
|
gst_structure_free (pwsink->client_properties);
|
|
g_free (pwsink->path);
|
|
g_free (pwsink->target_object);
|
|
g_free (pwsink->client_name);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink);
|
|
|
|
gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->pool), 0, 0, 0);
|
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstBaseSinkClass *gstbasesink_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstbasesink_class = (GstBaseSinkClass *) klass;
|
|
|
|
gobject_class->finalize = gst_pipewire_sink_finalize;
|
|
gobject_class->set_property = gst_pipewire_sink_set_property;
|
|
gobject_class->get_property = gst_pipewire_sink_get_property;
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_PATH,
|
|
g_param_spec_string ("path",
|
|
"Path",
|
|
"The sink path to connect to (NULL = default)",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_DEPRECATED));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_TARGET_OBJECT,
|
|
g_param_spec_string ("target-object",
|
|
"Target object",
|
|
"The sink name/serial to connect to (NULL = default)",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CLIENT_NAME,
|
|
g_param_spec_string ("client-name",
|
|
"Client Name",
|
|
"The client name to use (NULL = default)",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CLIENT_PROPERTIES,
|
|
g_param_spec_boxed ("client-properties",
|
|
"Client properties",
|
|
"List of PipeWire client properties",
|
|
GST_TYPE_STRUCTURE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_STREAM_PROPERTIES,
|
|
g_param_spec_boxed ("stream-properties",
|
|
"Stream properties",
|
|
"List of PipeWire stream properties",
|
|
GST_TYPE_STRUCTURE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_MODE,
|
|
g_param_spec_enum ("mode",
|
|
"Mode",
|
|
"The mode to operate in",
|
|
GST_TYPE_PIPEWIRE_SINK_MODE,
|
|
DEFAULT_PROP_MODE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_FD,
|
|
g_param_spec_int ("fd",
|
|
"Fd",
|
|
"The fd to connect with",
|
|
-1, G_MAXINT, -1,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
gstelement_class->change_state = gst_pipewire_sink_change_state;
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class,
|
|
"PipeWire sink", "Sink/Video",
|
|
"Send video to PipeWire", "Wim Taymans <wim.taymans@gmail.com>");
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_pipewire_sink_template));
|
|
|
|
gstbasesink_class->set_caps = gst_pipewire_sink_setcaps;
|
|
gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate;
|
|
gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation;
|
|
gstbasesink_class->start = gst_pipewire_sink_start;
|
|
gstbasesink_class->stop = gst_pipewire_sink_stop;
|
|
gstbasesink_class->render = gst_pipewire_sink_render;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0,
|
|
"PipeWire Sink");
|
|
}
|
|
|
|
static void
|
|
pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink)
|
|
{
|
|
GstStructure *config;
|
|
GstCaps *caps;
|
|
guint size;
|
|
guint min_buffers;
|
|
guint max_buffers;
|
|
const struct spa_pod *port_params[3];
|
|
struct spa_pod_builder b = { NULL };
|
|
uint8_t buffer[1024];
|
|
struct spa_pod_frame f;
|
|
|
|
config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool));
|
|
gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
|
|
|
|
spa_pod_builder_init (&b, buffer, sizeof (buffer));
|
|
spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers);
|
|
if (size == 0)
|
|
spa_pod_builder_add (&b,
|
|
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
|
|
0);
|
|
else
|
|
spa_pod_builder_add (&b,
|
|
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX),
|
|
0);
|
|
|
|
spa_pod_builder_add (&b,
|
|
SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
|
|
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(
|
|
SPA_MAX(MIN_BUFFERS, min_buffers),
|
|
SPA_MAX(MIN_BUFFERS, min_buffers),
|
|
max_buffers ? max_buffers : INT32_MAX),
|
|
SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(
|
|
(1<<SPA_DATA_MemFd) |
|
|
(1<<SPA_DATA_MemPtr)),
|
|
0);
|
|
port_params[0] = spa_pod_builder_pop (&b, &f);
|
|
|
|
port_params[1] = spa_pod_builder_add_object (&b,
|
|
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
|
|
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
|
|
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header)));
|
|
|
|
port_params[2] = spa_pod_builder_add_object (&b,
|
|
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
|
|
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
|
|
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region)));
|
|
|
|
pw_thread_loop_lock (sink->core->loop);
|
|
pw_stream_update_params (sink->stream, port_params, 3);
|
|
pw_thread_loop_unlock (sink->core->loop);
|
|
}
|
|
|
|
static void
|
|
gst_pipewire_sink_init (GstPipeWireSink * sink)
|
|
{
|
|
sink->pool = gst_pipewire_pool_new ();
|
|
sink->client_name = g_strdup(pw_get_client_name());
|
|
sink->mode = DEFAULT_PROP_MODE;
|
|
sink->fd = -1;
|
|
|
|
g_signal_connect (sink->pool, "activated", G_CALLBACK (pool_activated), sink);
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstStructure *structure;
|
|
|
|
caps = gst_caps_make_writable (caps);
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
if (gst_structure_has_name (structure, "video/x-raw")) {
|
|
gst_structure_fixate_field_nearest_int (structure, "width", 320);
|
|
gst_structure_fixate_field_nearest_int (structure, "height", 240);
|
|
gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
|
|
|
|
if (gst_structure_has_field (structure, "pixel-aspect-ratio"))
|
|
gst_structure_fixate_field_nearest_fraction (structure,
|
|
"pixel-aspect-ratio", 1, 1);
|
|
else
|
|
gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
|
|
NULL);
|
|
|
|
if (gst_structure_has_field (structure, "colorimetry"))
|
|
gst_structure_fixate_field_string (structure, "colorimetry", "bt601");
|
|
if (gst_structure_has_field (structure, "chroma-site"))
|
|
gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2");
|
|
|
|
if (gst_structure_has_field (structure, "interlace-mode"))
|
|
gst_structure_fixate_field_string (structure, "interlace-mode",
|
|
"progressive");
|
|
else
|
|
gst_structure_set (structure, "interlace-mode", G_TYPE_STRING,
|
|
"progressive", NULL);
|
|
} else if (gst_structure_has_name (structure, "audio/x-raw")) {
|
|
gst_structure_fixate_field_string (structure, "format", "S16LE");
|
|
gst_structure_fixate_field_nearest_int (structure, "channels", 2);
|
|
gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
|
|
} else if (gst_structure_has_name (structure, "audio/mpeg")) {
|
|
gst_structure_fixate_field_string (structure, "format", "Encoded");
|
|
gst_structure_fixate_field_nearest_int (structure, "channels", 2);
|
|
gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
|
|
} else if (gst_structure_has_name (structure, "audio/x-flac")) {
|
|
gst_structure_fixate_field_string (structure, "format", "Encoded");
|
|
gst_structure_fixate_field_nearest_int (structure, "channels", 2);
|
|
gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
|
|
}
|
|
|
|
caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps);
|
|
|
|
return caps;
|
|
}
|
|
|
|
static void
|
|
gst_pipewire_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_free (pwsink->path);
|
|
pwsink->path = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_TARGET_OBJECT:
|
|
g_free (pwsink->target_object);
|
|
pwsink->target_object = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_CLIENT_NAME:
|
|
g_free (pwsink->client_name);
|
|
pwsink->client_name = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_CLIENT_PROPERTIES:
|
|
if (pwsink->client_properties)
|
|
gst_structure_free (pwsink->client_properties);
|
|
pwsink->client_properties =
|
|
gst_structure_copy (gst_value_get_structure (value));
|
|
break;
|
|
|
|
case PROP_STREAM_PROPERTIES:
|
|
if (pwsink->stream_properties)
|
|
gst_structure_free (pwsink->stream_properties);
|
|
pwsink->stream_properties =
|
|
gst_structure_copy (gst_value_get_structure (value));
|
|
break;
|
|
|
|
case PROP_MODE:
|
|
pwsink->mode = g_value_get_enum (value);
|
|
break;
|
|
|
|
case PROP_FD:
|
|
pwsink->fd = g_value_get_int (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_pipewire_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_value_set_string (value, pwsink->path);
|
|
break;
|
|
|
|
case PROP_TARGET_OBJECT:
|
|
g_value_set_string (value, pwsink->target_object);
|
|
break;
|
|
|
|
case PROP_CLIENT_NAME:
|
|
g_value_set_string (value, pwsink->client_name);
|
|
break;
|
|
|
|
case PROP_CLIENT_PROPERTIES:
|
|
gst_value_set_structure (value, pwsink->client_properties);
|
|
break;
|
|
|
|
case PROP_STREAM_PROPERTIES:
|
|
gst_value_set_structure (value, pwsink->stream_properties);
|
|
break;
|
|
|
|
case PROP_MODE:
|
|
g_value_set_enum (value, pwsink->mode);
|
|
break;
|
|
|
|
case PROP_FD:
|
|
g_value_set_int (value, pwsink->fd);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_add_buffer (void *_data, struct pw_buffer *b)
|
|
{
|
|
GstPipeWireSink *pwsink = _data;
|
|
gst_pipewire_pool_wrap_buffer (pwsink->pool, b);
|
|
}
|
|
|
|
static void
|
|
on_remove_buffer (void *_data, struct pw_buffer *b)
|
|
{
|
|
GstPipeWireSink *pwsink = _data;
|
|
GstPipeWirePoolData *data = b->user_data;
|
|
|
|
GST_LOG_OBJECT (pwsink, "remove buffer");
|
|
|
|
gst_buffer_unref (data->buf);
|
|
}
|
|
|
|
static void
|
|
do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer)
|
|
{
|
|
GstPipeWirePoolData *data;
|
|
gboolean res;
|
|
guint i;
|
|
struct spa_buffer *b;
|
|
|
|
data = gst_pipewire_pool_get_data(buffer);
|
|
|
|
b = data->b->buffer;
|
|
|
|
if (data->header) {
|
|
data->header->seq = GST_BUFFER_OFFSET (buffer);
|
|
data->header->pts = GST_BUFFER_PTS (buffer);
|
|
data->header->dts_offset = GST_BUFFER_DTS (buffer);
|
|
}
|
|
if (data->crop) {
|
|
GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta (buffer);
|
|
if (meta) {
|
|
data->crop->region.position.x = meta->x;
|
|
data->crop->region.position.y = meta->y;
|
|
data->crop->region.size.width = meta->width;
|
|
data->crop->region.size.height = meta->width;
|
|
}
|
|
}
|
|
for (i = 0; i < b->n_datas; i++) {
|
|
struct spa_data *d = &b->datas[i];
|
|
GstMemory *mem = gst_buffer_peek_memory (buffer, i);
|
|
d->chunk->offset = mem->offset;
|
|
d->chunk->size = mem->size;
|
|
d->chunk->stride = pwsink->pool->video_info.stride[i];
|
|
}
|
|
|
|
GstVideoMeta *meta = gst_buffer_get_video_meta (buffer);
|
|
if (meta) {
|
|
if (meta->n_planes == b->n_datas) {
|
|
gsize video_size = 0;
|
|
for (i = 0; i < meta->n_planes; i++) {
|
|
struct spa_data *d = &b->datas[i];
|
|
d->chunk->offset += meta->offset[i] - video_size;
|
|
d->chunk->stride = meta->stride[i];
|
|
|
|
video_size += d->chunk->size;
|
|
}
|
|
} else {
|
|
GST_ERROR ("plane num not matching, meta:%u buffer:%u", meta->n_planes, b->n_datas);
|
|
}
|
|
}
|
|
|
|
if ((res = pw_stream_queue_buffer (pwsink->stream, data->b)) < 0) {
|
|
g_warning ("can't send buffer %s", spa_strerror(res));
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
on_process (void *data)
|
|
{
|
|
GstPipeWireSink *pwsink = data;
|
|
GST_DEBUG ("signal");
|
|
g_cond_signal (&pwsink->pool->cond);
|
|
}
|
|
|
|
static void
|
|
on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error)
|
|
{
|
|
GstPipeWireSink *pwsink = data;
|
|
|
|
GST_DEBUG ("got stream state %d", state);
|
|
|
|
switch (state) {
|
|
case PW_STREAM_STATE_UNCONNECTED:
|
|
case PW_STREAM_STATE_CONNECTING:
|
|
case PW_STREAM_STATE_PAUSED:
|
|
break;
|
|
case PW_STREAM_STATE_STREAMING:
|
|
if (pw_stream_is_driving (pwsink->stream))
|
|
pw_stream_trigger_process (pwsink->stream);
|
|
break;
|
|
case PW_STREAM_STATE_ERROR:
|
|
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
|
|
("stream error: %s", error), (NULL));
|
|
break;
|
|
}
|
|
pw_thread_loop_signal (pwsink->core->loop, FALSE);
|
|
}
|
|
|
|
static void
|
|
on_param_changed (void *data, uint32_t id, const struct spa_pod *param)
|
|
{
|
|
GstPipeWireSink *pwsink = data;
|
|
|
|
if (param == NULL || id != SPA_PARAM_Format)
|
|
return;
|
|
|
|
if (gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool)))
|
|
pool_activated (pwsink->pool, pwsink);
|
|
}
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstPipeWireSink *pwsink;
|
|
GPtrArray *possible;
|
|
enum pw_stream_state state;
|
|
const char *error = NULL;
|
|
gboolean res = FALSE;
|
|
GstStructure *config;
|
|
guint size;
|
|
guint min_buffers;
|
|
guint max_buffers;
|
|
struct timespec abstime;
|
|
|
|
pwsink = GST_PIPEWIRE_SINK (bsink);
|
|
|
|
possible = gst_caps_to_format_all (caps, SPA_PARAM_EnumFormat);
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
state = pw_stream_get_state (pwsink->stream, &error);
|
|
|
|
if (state == PW_STREAM_STATE_ERROR)
|
|
goto start_error;
|
|
|
|
if (state == PW_STREAM_STATE_UNCONNECTED) {
|
|
enum pw_stream_flags flags;
|
|
uint32_t target_id;
|
|
|
|
flags = PW_STREAM_FLAG_ASYNC;
|
|
if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE)
|
|
flags |= PW_STREAM_FLAG_AUTOCONNECT;
|
|
else
|
|
flags |= PW_STREAM_FLAG_DRIVER;
|
|
|
|
target_id = pwsink->path ? (uint32_t)atoi(pwsink->path) : PW_ID_ANY;
|
|
|
|
if (pwsink->target_object) {
|
|
struct spa_dict_item items[2] = {
|
|
SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, pwsink->target_object),
|
|
/* XXX deprecated but the portal and some example apps only
|
|
* provide the object id */
|
|
SPA_DICT_ITEM_INIT(PW_KEY_NODE_TARGET, NULL),
|
|
};
|
|
struct spa_dict dict = SPA_DICT_INIT_ARRAY(items);
|
|
uint64_t serial;
|
|
|
|
/* If target.object is a name, set it also to node.target */
|
|
if (spa_atou64(pwsink->target_object, &serial, 0)) {
|
|
dict.n_items = 1;
|
|
} else {
|
|
target_id = PW_ID_ANY;
|
|
items[1].value = pwsink->target_object;
|
|
}
|
|
|
|
pw_stream_update_properties (pwsink->stream, &dict);
|
|
}
|
|
|
|
pw_stream_connect (pwsink->stream,
|
|
PW_DIRECTION_OUTPUT,
|
|
target_id,
|
|
flags,
|
|
(const struct spa_pod **) possible->pdata,
|
|
possible->len);
|
|
|
|
pw_thread_loop_get_time (pwsink->core->loop, &abstime,
|
|
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
|
|
|
|
while (TRUE) {
|
|
state = pw_stream_get_state (pwsink->stream, &error);
|
|
|
|
if (state >= PW_STREAM_STATE_PAUSED)
|
|
break;
|
|
|
|
if (state == PW_STREAM_STATE_ERROR)
|
|
goto start_error;
|
|
|
|
if (pw_thread_loop_timed_wait_full (pwsink->core->loop, &abstime) < 0) {
|
|
error = "timeout";
|
|
goto start_error;
|
|
}
|
|
}
|
|
}
|
|
res = TRUE;
|
|
|
|
config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->pool));
|
|
gst_buffer_pool_config_get_params (config, NULL, &size, &min_buffers, &max_buffers);
|
|
gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers);
|
|
gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->pool), config);
|
|
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
pwsink->negotiated = res;
|
|
|
|
return res;
|
|
|
|
start_error:
|
|
{
|
|
GST_ERROR ("could not start stream: %s", error);
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
g_ptr_array_unref (possible);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
|
|
{
|
|
GstPipeWireSink *pwsink;
|
|
GstFlowReturn res = GST_FLOW_OK;
|
|
const char *error = NULL;
|
|
gboolean unref_buffer = FALSE;
|
|
|
|
pwsink = GST_PIPEWIRE_SINK (bsink);
|
|
|
|
if (!pwsink->negotiated)
|
|
goto not_negotiated;
|
|
|
|
if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool) &&
|
|
!gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool))) {
|
|
GstStructure *config;
|
|
GstCaps *caps;
|
|
guint size, min_buffers, max_buffers;
|
|
|
|
config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->pool));
|
|
gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
|
|
|
|
if (size == 0) {
|
|
gsize maxsize;
|
|
gst_buffer_get_sizes (buffer, NULL, &maxsize);
|
|
size = maxsize;
|
|
}
|
|
|
|
gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers);
|
|
gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->pool), config);
|
|
|
|
gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->pool), TRUE);
|
|
}
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
if (pw_stream_get_state (pwsink->stream, &error) != PW_STREAM_STATE_STREAMING)
|
|
goto done_unlock;
|
|
|
|
if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool)) {
|
|
GstBuffer *b = NULL;
|
|
GstMapInfo info = { 0, };
|
|
GstBufferPoolAcquireParams params = { 0, };
|
|
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->pool), &b, ¶ms)) != GST_FLOW_OK)
|
|
goto done;
|
|
|
|
gst_buffer_map (b, &info, GST_MAP_WRITE);
|
|
gst_buffer_extract (buffer, 0, info.data, info.maxsize);
|
|
gst_buffer_unmap (b, &info);
|
|
gst_buffer_resize (b, 0, gst_buffer_get_size (buffer));
|
|
gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1);
|
|
buffer = b;
|
|
unref_buffer = TRUE;
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
if (pw_stream_get_state (pwsink->stream, &error) != PW_STREAM_STATE_STREAMING)
|
|
goto done_unlock;
|
|
}
|
|
|
|
GST_DEBUG ("push buffer");
|
|
do_send_buffer (pwsink, buffer);
|
|
if (unref_buffer)
|
|
gst_buffer_unref (buffer);
|
|
|
|
if (pw_stream_is_driving (pwsink->stream))
|
|
pw_stream_trigger_process (pwsink->stream);
|
|
|
|
done_unlock:
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
done:
|
|
return res;
|
|
|
|
not_negotiated:
|
|
{
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
copy_properties (GQuark field_id,
|
|
const GValue *value,
|
|
gpointer user_data)
|
|
{
|
|
struct pw_properties *properties = user_data;
|
|
GValue dst = { 0 };
|
|
|
|
if (g_value_type_transformable (G_VALUE_TYPE(value), G_TYPE_STRING)) {
|
|
g_value_init(&dst, G_TYPE_STRING);
|
|
if (g_value_transform(value, &dst)) {
|
|
pw_properties_set (properties,
|
|
g_quark_to_string (field_id),
|
|
g_value_get_string (&dst));
|
|
}
|
|
g_value_unset(&dst);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static const struct pw_stream_events stream_events = {
|
|
PW_VERSION_STREAM_EVENTS,
|
|
.state_changed = on_state_changed,
|
|
.param_changed = on_param_changed,
|
|
.add_buffer = on_add_buffer,
|
|
.remove_buffer = on_remove_buffer,
|
|
.process = on_process,
|
|
};
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_start (GstBaseSink * basesink)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
|
|
struct pw_properties *props;
|
|
|
|
pwsink->negotiated = FALSE;
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
|
|
props = pw_properties_new (NULL, NULL);
|
|
if (pwsink->client_name) {
|
|
pw_properties_set (props, PW_KEY_NODE_NAME, pwsink->client_name);
|
|
pw_properties_set (props, PW_KEY_NODE_DESCRIPTION, pwsink->client_name);
|
|
}
|
|
if (pwsink->stream_properties) {
|
|
gst_structure_foreach (pwsink->stream_properties, copy_properties, props);
|
|
}
|
|
|
|
if ((pwsink->stream = pw_stream_new (pwsink->core->core, pwsink->client_name, props)) == NULL)
|
|
goto no_stream;
|
|
|
|
pwsink->pool->stream = pwsink->stream;
|
|
|
|
pw_stream_add_listener(pwsink->stream,
|
|
&pwsink->stream_listener,
|
|
&stream_events,
|
|
pwsink);
|
|
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
return TRUE;
|
|
|
|
no_stream:
|
|
{
|
|
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, ("can't create stream"), (NULL));
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_stop (GstBaseSink * basesink)
|
|
{
|
|
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
if (pwsink->stream) {
|
|
pw_stream_destroy (pwsink->stream);
|
|
pwsink->stream = NULL;
|
|
pwsink->pool->stream = NULL;
|
|
}
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
pwsink->negotiated = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_open (GstPipeWireSink * pwsink)
|
|
{
|
|
struct pw_properties *props;
|
|
|
|
GST_DEBUG_OBJECT (pwsink, "open");
|
|
|
|
pwsink->core = gst_pipewire_core_get(pwsink->fd);
|
|
if (pwsink->core == NULL)
|
|
goto connect_error;
|
|
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
|
|
props = pw_properties_new (NULL, NULL);
|
|
if (pwsink->client_properties) {
|
|
gst_structure_foreach (pwsink->client_properties, copy_properties, props);
|
|
pw_core_update_properties (pwsink->core->core, &props->dict);
|
|
}
|
|
pw_properties_free(props);
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
connect_error:
|
|
{
|
|
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
|
|
("Failed to connect"), (NULL));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_pipewire_sink_close (GstPipeWireSink * pwsink)
|
|
{
|
|
pw_thread_loop_lock (pwsink->core->loop);
|
|
if (pwsink->stream) {
|
|
pw_stream_destroy (pwsink->stream);
|
|
pwsink->stream = NULL;
|
|
}
|
|
pw_thread_loop_unlock (pwsink->core->loop);
|
|
|
|
if (pwsink->core) {
|
|
gst_pipewire_core_release (pwsink->core);
|
|
pwsink->core = NULL;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
GstPipeWireSink *this = GST_PIPEWIRE_SINK_CAST (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
if (!gst_pipewire_sink_open (this))
|
|
goto open_failed;
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
/* uncork and start play */
|
|
pw_thread_loop_lock (this->core->loop);
|
|
pw_stream_set_active(this->stream, true);
|
|
pw_thread_loop_unlock (this->core->loop);
|
|
gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->pool), FALSE);
|
|
break;
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
/* stop play ASAP by corking */
|
|
pw_thread_loop_lock (this->core->loop);
|
|
pw_stream_set_active(this->stream, false);
|
|
pw_thread_loop_unlock (this->core->loop);
|
|
gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->pool), TRUE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_buffer_pool_set_active(GST_BUFFER_POOL_CAST(this->pool), FALSE);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
gst_pipewire_sink_close (this);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
open_failed:
|
|
{
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
}
|
|
}
|