mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-10 13:30:05 -05:00
move things around
This commit is contained in:
parent
847cef83b6
commit
d1655196c3
130 changed files with 363 additions and 335 deletions
64
src/gst/gstpipewire.c
Normal file
64
src/gst/gstpipewire.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-pipewiresrc
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch -v pipewiresrc ! ximagesink
|
||||
* ]| Shows PipeWire output in an X window.
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstpipewiresrc.h"
|
||||
#include "gstpipewiresink.h"
|
||||
#include "gstpipewiredeviceprovider.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (pipewire_debug);
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin *plugin)
|
||||
{
|
||||
pw_init (NULL, NULL);
|
||||
|
||||
gst_element_register (plugin, "pipewiresrc", GST_RANK_PRIMARY + 1,
|
||||
GST_TYPE_PIPEWIRE_SRC);
|
||||
gst_element_register (plugin, "pipewiresink", GST_RANK_NONE,
|
||||
GST_TYPE_PIPEWIRE_SINK);
|
||||
|
||||
if (!gst_device_provider_register (plugin, "pipewiredeviceprovider",
|
||||
GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
|
||||
return FALSE;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWirie elements");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
pipewire,
|
||||
"Uses PipeWire to handle media streams",
|
||||
plugin_init, VERSION, "LGPL", "pipewire", "pipewire.org")
|
||||
92
src/gst/gstpipewireclock.c
Normal file
92
src/gst/gstpipewireclock.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstpipewireclock.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_pipewire_clock_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_pipewire_clock_debug_category
|
||||
|
||||
G_DEFINE_TYPE (GstPipeWireClock, gst_pipewire_clock, GST_TYPE_SYSTEM_CLOCK);
|
||||
|
||||
GstClock *
|
||||
gst_pipewire_clock_new (struct pw_stream *stream)
|
||||
{
|
||||
GstPipeWireClock *clock;
|
||||
|
||||
clock = g_object_new (GST_TYPE_PIPEWIRE_CLOCK, NULL);
|
||||
clock->stream = stream;
|
||||
|
||||
return GST_CLOCK_CAST (clock);
|
||||
}
|
||||
|
||||
static GstClockTime
|
||||
gst_pipewire_clock_get_internal_time (GstClock * clock)
|
||||
{
|
||||
GstPipeWireClock *pclock = (GstPipeWireClock *) clock;
|
||||
GstClockTime result;
|
||||
struct pw_time t;
|
||||
|
||||
pw_stream_get_time (pclock->stream, &t);
|
||||
|
||||
if (t.rate)
|
||||
result = gst_util_uint64_scale_int (t.ticks, GST_SECOND, t.rate);
|
||||
else
|
||||
result = GST_CLOCK_TIME_NONE;
|
||||
|
||||
GST_DEBUG ("%"PRId64", %d %"PRId64, t.ticks, t.rate, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_pipewire_clock_finalize (GObject * object)
|
||||
{
|
||||
GstPipeWireClock *clock = GST_PIPEWIRE_CLOCK (object);
|
||||
|
||||
GST_DEBUG_OBJECT (clock, "finalize");
|
||||
|
||||
G_OBJECT_CLASS (gst_pipewire_clock_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_clock_class_init (GstPipeWireClockClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = gst_pipewire_clock_finalize;
|
||||
|
||||
gstclock_class->get_internal_time = gst_pipewire_clock_get_internal_time;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_pipewire_clock_debug_category, "pipewireclock", 0,
|
||||
"debug category for pipewireclock object");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_clock_init (GstPipeWireClock * clock)
|
||||
{
|
||||
GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER);
|
||||
}
|
||||
62
src/gst/gstpipewireclock.h
Normal file
62
src/gst/gstpipewireclock.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PIPEWIRE_CLOCK_H__
|
||||
#define __GST_PIPEWIRE_CLOCK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_CLOCK \
|
||||
(gst_pipewire_clock_get_type())
|
||||
#define GST_PIPEWIRE_CLOCK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClock))
|
||||
#define GST_PIPEWIRE_CLOCK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClockClass))
|
||||
#define GST_IS_PIPEWIRE_CLOCK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_CLOCK))
|
||||
#define GST_IS_PIPEWIRE_CLOCK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_CLOCK))
|
||||
#define GST_PIPEWIRE_CLOCK_GET_CLASS(klass) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_CLOCK, GstPipeWireClockClass))
|
||||
|
||||
typedef struct _GstPipeWireClock GstPipeWireClock;
|
||||
typedef struct _GstPipeWireClockClass GstPipeWireClockClass;
|
||||
|
||||
struct _GstPipeWireClock {
|
||||
GstSystemClock parent;
|
||||
|
||||
struct pw_stream *stream;
|
||||
};
|
||||
|
||||
struct _GstPipeWireClockClass {
|
||||
GstSystemClockClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_clock_get_type (void);
|
||||
|
||||
GstClock * gst_pipewire_clock_new (struct pw_stream *stream);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PIPEWIRE_CLOCK_H__ */
|
||||
647
src/gst/gstpipewiredeviceprovider.c
Normal file
647
src/gst/gstpipewiredeviceprovider.c
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
|
||||
* (C) 2015 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* pipewiredeviceprovider.c: PipeWire device probing and monitoring
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstpipewireformat.h"
|
||||
#include "gstpipewiredeviceprovider.h"
|
||||
#include "gstpipewiresrc.h"
|
||||
#include "gstpipewiresink.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (pipewire_debug);
|
||||
#define GST_CAT_DEFAULT pipewire_debug
|
||||
|
||||
G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_ID = 1,
|
||||
};
|
||||
|
||||
static GstDevice *
|
||||
gst_pipewire_device_new (uint32_t id, const gchar * device_name,
|
||||
GstCaps * caps, const gchar *klass,
|
||||
GstPipeWireDeviceType type, GstStructure *props)
|
||||
{
|
||||
GstPipeWireDevice *gstdev;
|
||||
const gchar *element = NULL;
|
||||
|
||||
g_return_val_if_fail (device_name, NULL);
|
||||
g_return_val_if_fail (caps, NULL);
|
||||
|
||||
switch (type) {
|
||||
case GST_PIPEWIRE_DEVICE_TYPE_SOURCE:
|
||||
element = "pipewiresrc";
|
||||
break;
|
||||
case GST_PIPEWIRE_DEVICE_TYPE_SINK:
|
||||
element = "pipewiresink";
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE,
|
||||
"display-name", device_name, "caps", caps, "device-class", klass,
|
||||
"id", id, "properties", props, NULL);
|
||||
|
||||
gstdev->id = id;
|
||||
gstdev->type = type;
|
||||
gstdev->element = element;
|
||||
|
||||
return GST_DEVICE (gstdev);
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
gst_pipewire_device_create_element (GstDevice * device, const gchar * name)
|
||||
{
|
||||
GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
|
||||
GstElement *elem;
|
||||
gchar *str;
|
||||
|
||||
elem = gst_element_factory_make (pipewire_dev->element, name);
|
||||
str = g_strdup_printf ("%u", pipewire_dev->id);
|
||||
g_object_set (elem, "path", str, NULL);
|
||||
g_free (str);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element)
|
||||
{
|
||||
GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
|
||||
gchar *str;
|
||||
|
||||
if (!strcmp (pipewire_dev->element, "pipewiresrc")) {
|
||||
if (!GST_IS_PIPEWIRE_SRC (element))
|
||||
return FALSE;
|
||||
} else if (!strcmp (pipewire_dev->element, "pipewiresink")) {
|
||||
if (!GST_IS_PIPEWIRE_SINK (element))
|
||||
return FALSE;
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
str = g_strdup_printf ("%u", pipewire_dev->id);
|
||||
g_object_set (element, "path", str, NULL);
|
||||
g_free (str);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_pipewire_device_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstPipeWireDevice *device;
|
||||
|
||||
device = GST_PIPEWIRE_DEVICE_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ID:
|
||||
g_value_set_uint (value, device->id);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstPipeWireDevice *device;
|
||||
|
||||
device = GST_PIPEWIRE_DEVICE_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ID:
|
||||
device->id = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_finalize (GObject * object)
|
||||
{
|
||||
G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass)
|
||||
{
|
||||
GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
dev_class->create_element = gst_pipewire_device_create_element;
|
||||
dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element;
|
||||
|
||||
object_class->get_property = gst_pipewire_device_get_property;
|
||||
object_class->set_property = gst_pipewire_device_set_property;
|
||||
object_class->finalize = gst_pipewire_device_finalize;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_ID,
|
||||
g_param_spec_uint ("id", "Id",
|
||||
"The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID,
|
||||
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_init (GstPipeWireDevice * device)
|
||||
{
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider,
|
||||
GST_TYPE_DEVICE_PROVIDER);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_CLIENT_NAME,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GstDevice *
|
||||
new_node (GstPipeWireDeviceProvider *self, const struct pw_node_info *info)
|
||||
{
|
||||
GstCaps *caps = NULL;
|
||||
GstStructure *props;
|
||||
const gchar *klass = NULL;
|
||||
struct spa_dict_item *item;
|
||||
GstPipeWireDeviceType type;
|
||||
int i;
|
||||
|
||||
caps = gst_caps_new_empty ();
|
||||
if (info->max_input_ports > 0 && info->max_output_ports == 0) {
|
||||
type = GST_PIPEWIRE_DEVICE_TYPE_SINK;
|
||||
|
||||
for (i = 0; i < info->n_input_formats; i++) {
|
||||
GstCaps *c1 = gst_caps_from_format (info->input_formats[i], self->core->type.map);
|
||||
if (c1)
|
||||
gst_caps_append (caps, c1);
|
||||
}
|
||||
}
|
||||
else if (info->max_output_ports > 0 && info->max_input_ports == 0) {
|
||||
type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE;
|
||||
for (i = 0; i < info->n_output_formats; i++) {
|
||||
GstCaps *c1 = gst_caps_from_format (info->output_formats[i], self->core->type.map);
|
||||
if (c1)
|
||||
gst_caps_append (caps, c1);
|
||||
}
|
||||
} else {
|
||||
gst_caps_unref(caps);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
props = gst_structure_new_empty ("pipewire-proplist");
|
||||
if (info->props) {
|
||||
spa_dict_for_each (item, info->props)
|
||||
gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL);
|
||||
|
||||
klass = spa_dict_lookup (info->props, "media.class");
|
||||
}
|
||||
if (klass == NULL)
|
||||
klass = "unknown/unknown";
|
||||
|
||||
return gst_pipewire_device_new (info->id,
|
||||
info->name,
|
||||
caps,
|
||||
klass,
|
||||
type,
|
||||
props);
|
||||
}
|
||||
|
||||
static GstPipeWireDevice *
|
||||
find_device (GstDeviceProvider *provider, uint32_t id)
|
||||
{
|
||||
GList *item;
|
||||
GstPipeWireDevice *dev = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (provider);
|
||||
for (item = provider->devices; item; item = item->next) {
|
||||
dev = item->data;
|
||||
if (dev->id == id) {
|
||||
gst_object_ref (dev);
|
||||
break;
|
||||
}
|
||||
dev = NULL;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (provider);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void
|
||||
get_core_info (struct pw_remote *remote,
|
||||
void *user_data)
|
||||
{
|
||||
GstDeviceProvider *provider = user_data;
|
||||
struct pw_core_info *info = remote->info;
|
||||
const gchar *value;
|
||||
|
||||
if (info == NULL || info->props == NULL)
|
||||
return;
|
||||
|
||||
value = spa_dict_lookup (info->props, "monitors");
|
||||
if (value) {
|
||||
gchar **monitors = g_strsplit (value, ",", -1);
|
||||
gint i;
|
||||
|
||||
GST_DEBUG_OBJECT (provider, "have hidden providers: %s", value);
|
||||
|
||||
for (i = 0; monitors[i]; i++) {
|
||||
if (strcmp (monitors[i], "v4l2") == 0)
|
||||
gst_device_provider_hide_provider (provider, "v4l2deviceprovider");
|
||||
else if (strcmp (monitors[i], "alsa") == 0)
|
||||
gst_device_provider_hide_provider (provider, "pulsedeviceprovider");
|
||||
}
|
||||
g_strfreev (monitors);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_sync_reply (struct pw_listener *listener, struct pw_remote *remote, uint32_t seq)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, on_sync_reply);
|
||||
if (seq == 1)
|
||||
pw_core_do_sync(self->registry->remote->core_proxy, 2);
|
||||
else if (seq == 2)
|
||||
self->end = true;
|
||||
}
|
||||
|
||||
static void node_event_info(void *object, struct pw_node_info *info)
|
||||
{
|
||||
struct pw_proxy *proxy = object;
|
||||
GstPipeWireDeviceProvider *self = proxy->object;
|
||||
GstDevice *dev;
|
||||
|
||||
dev = new_node (self, info);
|
||||
if (dev) {
|
||||
if(self->list_only)
|
||||
*self->devices = g_list_prepend (*self->devices, gst_object_ref_sink (dev));
|
||||
else
|
||||
gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pw_node_events node_events = {
|
||||
&node_event_info
|
||||
};
|
||||
|
||||
static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version)
|
||||
{
|
||||
struct pw_proxy *registry = object;
|
||||
GstPipeWireDeviceProvider *self = registry->object;
|
||||
struct pw_remote *remote = registry->remote;
|
||||
struct pw_core *core = remote->core;
|
||||
struct pw_proxy *proxy = NULL;
|
||||
|
||||
if (strcmp(type, PIPEWIRE_TYPE__Node))
|
||||
return;
|
||||
|
||||
proxy = pw_proxy_new(remote, SPA_ID_INVALID, core->type.node, 0);
|
||||
if (proxy == NULL)
|
||||
goto no_mem;
|
||||
|
||||
pw_proxy_set_implementation(proxy, self, PW_VERSION_NODE, &node_events, NULL);
|
||||
pw_registry_do_bind(registry, id, version, proxy->id);
|
||||
return;
|
||||
|
||||
no_mem:
|
||||
GST_ERROR_OBJECT(self, "failed to create proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
static void registry_event_global_remove(void *object, uint32_t id)
|
||||
{
|
||||
struct pw_proxy *registry = object;
|
||||
GstPipeWireDeviceProvider *self = registry->object;
|
||||
GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
|
||||
GstPipeWireDevice *dev;
|
||||
|
||||
dev = find_device (provider, id);
|
||||
if (dev != NULL) {
|
||||
gst_device_provider_device_remove (provider, GST_DEVICE (dev));
|
||||
gst_object_unref (dev);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pw_registry_events registry_events = {
|
||||
registry_event_global,
|
||||
registry_event_global_remove,
|
||||
};
|
||||
|
||||
static GList *
|
||||
gst_pipewire_device_provider_probe (GstDeviceProvider * provider)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
|
||||
struct pw_loop *l = NULL;
|
||||
struct pw_core *c = NULL;
|
||||
struct pw_remote *r = NULL;
|
||||
struct pw_proxy *reg = NULL;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "starting probe");
|
||||
|
||||
if (!(l = pw_loop_new ()))
|
||||
return NULL;
|
||||
|
||||
if (!(c = pw_core_new (l, NULL)))
|
||||
return NULL;
|
||||
|
||||
if (!(r = pw_remote_new (c, NULL)))
|
||||
goto failed;
|
||||
|
||||
pw_signal_add(&r->sync_reply, &self->on_sync_reply, on_sync_reply);
|
||||
|
||||
pw_remote_connect (r);
|
||||
|
||||
for (;;) {
|
||||
enum pw_remote_state state;
|
||||
|
||||
state = r->state;
|
||||
|
||||
if (state <= 0) {
|
||||
GST_ERROR_OBJECT (self, "Failed to connect: %s", r->error);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (state == PW_REMOTE_STATE_CONNECTED)
|
||||
break;
|
||||
|
||||
/* Wait until something happens */
|
||||
pw_loop_iterate (l, -1);
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "connected");
|
||||
|
||||
get_core_info (r, self);
|
||||
|
||||
self->end = FALSE;
|
||||
self->list_only = TRUE;
|
||||
self->devices = NULL;
|
||||
|
||||
reg = pw_proxy_new(r, SPA_ID_INVALID, c->type.registry, 0);
|
||||
pw_proxy_set_implementation(reg, self, PW_VERSION_REGISTRY, ®istry_events, NULL);
|
||||
pw_core_do_get_registry(r->core_proxy, reg->id);
|
||||
pw_core_do_sync(r->core_proxy, 1);
|
||||
|
||||
for (;;) {
|
||||
if (r->state <= 0)
|
||||
break;
|
||||
if (self->end)
|
||||
break;
|
||||
pw_loop_iterate (l, -1);
|
||||
}
|
||||
|
||||
pw_remote_disconnect (r);
|
||||
pw_remote_destroy (r);
|
||||
pw_core_destroy (c);
|
||||
pw_loop_destroy (l);
|
||||
|
||||
return *self->devices;
|
||||
|
||||
failed:
|
||||
pw_loop_destroy (l);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_remote_state_changed (struct pw_listener *listener,
|
||||
struct pw_remote *remote)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, remote_state_changed);
|
||||
enum pw_remote_state state;
|
||||
|
||||
state= remote->state;
|
||||
|
||||
GST_DEBUG ("got remote state %d", state);
|
||||
|
||||
switch (state) {
|
||||
case PW_REMOTE_STATE_CONNECTING:
|
||||
break;
|
||||
case PW_REMOTE_STATE_UNCONNECTED:
|
||||
case PW_REMOTE_STATE_CONNECTED:
|
||||
break;
|
||||
case PW_REMOTE_STATE_ERROR:
|
||||
GST_ERROR_OBJECT (self, "remote error: %s", remote->error);
|
||||
break;
|
||||
}
|
||||
pw_thread_loop_signal (self->main_loop, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_device_provider_start (GstDeviceProvider * provider)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "starting provider");
|
||||
|
||||
self->loop = pw_loop_new ();
|
||||
self->list_only = FALSE;
|
||||
|
||||
if (!(self->main_loop = pw_thread_loop_new (self->loop, "pipewire-device-monitor"))) {
|
||||
GST_ERROR_OBJECT (self, "Could not create PipeWire mainloop");
|
||||
goto failed_main_loop;
|
||||
}
|
||||
|
||||
if (!(self->core = pw_core_new (self->loop, NULL))) {
|
||||
GST_ERROR_OBJECT (self, "Could not create PipeWire core");
|
||||
goto failed_core;
|
||||
}
|
||||
|
||||
if (pw_thread_loop_start (self->main_loop) != SPA_RESULT_OK) {
|
||||
GST_ERROR_OBJECT (self, "Could not start PipeWire mainloop");
|
||||
goto failed_start;
|
||||
}
|
||||
|
||||
pw_thread_loop_lock (self->main_loop);
|
||||
|
||||
if (!(self->remote = pw_remote_new (self->core, NULL))) {
|
||||
GST_ERROR_OBJECT (self, "Failed to create remote");
|
||||
goto failed_remote;
|
||||
}
|
||||
|
||||
pw_signal_add (&self->remote->state_changed,
|
||||
&self->remote_state_changed,
|
||||
on_remote_state_changed);
|
||||
|
||||
pw_remote_connect (self->remote);
|
||||
for (;;) {
|
||||
enum pw_remote_state state;
|
||||
|
||||
state = self->remote->state;
|
||||
|
||||
if (state <= 0) {
|
||||
GST_WARNING_OBJECT (self, "Failed to connect: %s", self->remote->error);
|
||||
goto not_running;
|
||||
}
|
||||
|
||||
if (state == PW_REMOTE_STATE_CONNECTED)
|
||||
break;
|
||||
|
||||
/* Wait until something happens */
|
||||
pw_thread_loop_wait (self->main_loop);
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "connected");
|
||||
get_core_info (self->remote, self);
|
||||
|
||||
self->registry = pw_proxy_new(self->remote, SPA_ID_INVALID, self->core->type.registry, 0);
|
||||
pw_proxy_set_implementation(self->registry, self, PW_VERSION_REGISTRY, ®istry_events, NULL);
|
||||
pw_core_do_get_registry(self->remote->core_proxy, self->registry->id);
|
||||
pw_core_do_sync(self->remote->core_proxy, 1);
|
||||
|
||||
pw_thread_loop_unlock (self->main_loop);
|
||||
|
||||
return TRUE;
|
||||
|
||||
not_running:
|
||||
pw_remote_destroy (self->remote);
|
||||
self->remote = NULL;
|
||||
failed_remote:
|
||||
pw_thread_loop_unlock (self->main_loop);
|
||||
failed_start:
|
||||
pw_core_destroy (self->core);
|
||||
self->core = NULL;
|
||||
failed_core:
|
||||
pw_thread_loop_destroy (self->main_loop);
|
||||
self->main_loop = NULL;
|
||||
failed_main_loop:
|
||||
pw_loop_destroy (self->loop);
|
||||
self->loop = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_stop (GstDeviceProvider * provider)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
|
||||
|
||||
if (self->remote) {
|
||||
pw_remote_disconnect (self->remote);
|
||||
pw_remote_destroy (self->remote);
|
||||
self->remote = NULL;
|
||||
}
|
||||
if (self->main_loop) {
|
||||
pw_thread_loop_destroy (self->main_loop);
|
||||
self->main_loop = NULL;
|
||||
}
|
||||
if (self->loop) {
|
||||
pw_loop_destroy (self->loop);
|
||||
self->loop = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_set_property (GObject * object,
|
||||
guint prop_id, const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CLIENT_NAME:
|
||||
g_free (self->client_name);
|
||||
if (!g_value_get_string (value)) {
|
||||
GST_WARNING_OBJECT (self,
|
||||
"Empty PipeWire client name not allowed. "
|
||||
"Resetting to default value");
|
||||
self->client_name = pw_get_client_name ();
|
||||
} else
|
||||
self->client_name = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_get_property (GObject * object,
|
||||
guint prop_id, GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CLIENT_NAME:
|
||||
g_value_set_string (value, self->client_name);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_finalize (GObject * object)
|
||||
{
|
||||
GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
|
||||
|
||||
g_free (self->client_name);
|
||||
|
||||
G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
|
||||
gchar *client_name;
|
||||
|
||||
gobject_class->set_property = gst_pipewire_device_provider_set_property;
|
||||
gobject_class->get_property = gst_pipewire_device_provider_get_property;
|
||||
gobject_class->finalize = gst_pipewire_device_provider_finalize;
|
||||
|
||||
dm_class->probe = gst_pipewire_device_provider_probe;
|
||||
dm_class->start = gst_pipewire_device_provider_start;
|
||||
dm_class->stop = gst_pipewire_device_provider_stop;
|
||||
|
||||
client_name = pw_get_client_name ();
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_CLIENT_NAME,
|
||||
g_param_spec_string ("client-name", "Client Name",
|
||||
"The PipeWire client_name_to_use", client_name,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
||||
GST_PARAM_MUTABLE_READY));
|
||||
g_free (client_name);
|
||||
|
||||
gst_device_provider_class_set_static_metadata (dm_class,
|
||||
"PipeWire Device Provider", "Sink/Source/Audio/Video",
|
||||
"List and provide PipeWire source and sink devices",
|
||||
"Wim Taymans <wim.taymans@gmail.com>");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self)
|
||||
{
|
||||
self->client_name = pw_get_client_name ();
|
||||
}
|
||||
105
src/gst/gstpipewiredeviceprovider.h
Normal file
105
src/gst/gstpipewiredeviceprovider.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
|
||||
* (C) 2015 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* pipewiredeviceprovider.h: Device probing and monitoring
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_PIPEWIRE_DEVICE_PROVIDER_H__
|
||||
#define __GST_PIPEWIRE_DEVICE_PROVIDER_H__
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GstPipeWireDevice GstPipeWireDevice;
|
||||
typedef struct _GstPipeWireDeviceClass GstPipeWireDeviceClass;
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_DEVICE (gst_pipewire_device_get_type())
|
||||
#define GST_IS_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE))
|
||||
#define GST_IS_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE))
|
||||
#define GST_PIPEWIRE_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDeviceClass))
|
||||
#define GST_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDevice))
|
||||
#define GST_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstPipeWireDeviceClass))
|
||||
#define GST_PIPEWIRE_DEVICE_CAST(obj) ((GstPipeWireDevice *)(obj))
|
||||
|
||||
typedef enum {
|
||||
GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN,
|
||||
GST_PIPEWIRE_DEVICE_TYPE_SOURCE,
|
||||
GST_PIPEWIRE_DEVICE_TYPE_SINK,
|
||||
} GstPipeWireDeviceType;
|
||||
|
||||
struct _GstPipeWireDevice {
|
||||
GstDevice parent;
|
||||
|
||||
GstPipeWireDeviceType type;
|
||||
uint32_t id;
|
||||
const gchar *element;
|
||||
};
|
||||
|
||||
struct _GstPipeWireDeviceClass {
|
||||
GstDeviceClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_device_get_type (void);
|
||||
|
||||
typedef struct _GstPipeWireDeviceProvider GstPipeWireDeviceProvider;
|
||||
typedef struct _GstPipeWireDeviceProviderClass GstPipeWireDeviceProviderClass;
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_DEVICE_PROVIDER (gst_pipewire_device_provider_get_type())
|
||||
#define GST_IS_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
|
||||
#define GST_IS_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
|
||||
#define GST_PIPEWIRE_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
|
||||
#define GST_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProvider))
|
||||
#define GST_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
|
||||
#define GST_PIPEWIRE_DEVICE_PROVIDER_CAST(obj) ((GstPipeWireDeviceProvider *)(obj))
|
||||
|
||||
struct _GstPipeWireDeviceProvider {
|
||||
GstDeviceProvider parent;
|
||||
|
||||
gchar *client_name;
|
||||
|
||||
struct pw_loop *loop;
|
||||
struct pw_thread_loop *main_loop;
|
||||
|
||||
struct pw_core *core;
|
||||
struct pw_remote *remote;
|
||||
struct pw_proxy *registry;
|
||||
gboolean end;
|
||||
gboolean list_only;
|
||||
GList **devices;
|
||||
struct pw_listener remote_state_changed;
|
||||
struct pw_listener on_sync_reply;
|
||||
};
|
||||
|
||||
struct _GstPipeWireDeviceProviderClass {
|
||||
GstDeviceProviderClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_device_provider_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PIPEWIRE_DEVICE_PROVIDER_H__ */
|
||||
852
src/gst/gstpipewireformat.c
Normal file
852
src/gst/gstpipewireformat.c
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/audio/audio.h>
|
||||
|
||||
#include <spa/format-builder.h>
|
||||
#include <spa/video/format-utils.h>
|
||||
#include <spa/audio/format-utils.h>
|
||||
|
||||
#include "gstpipewireformat.h"
|
||||
|
||||
struct media_type {
|
||||
const char *name;
|
||||
uint32_t *media_type;
|
||||
uint32_t *media_subtype;
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct spa_type_map *map;
|
||||
uint32_t format;
|
||||
struct spa_type_media_type media_type;
|
||||
struct spa_type_media_subtype media_subtype;
|
||||
struct spa_type_media_subtype_video media_subtype_video;
|
||||
struct spa_type_media_subtype_audio media_subtype_audio;
|
||||
struct spa_type_format_video format_video;
|
||||
struct spa_type_format_audio format_audio;
|
||||
struct spa_type_video_format video_format;
|
||||
struct spa_type_audio_format audio_format;
|
||||
} type = { NULL, };
|
||||
|
||||
static void
|
||||
ensure_types (struct spa_type_map *map)
|
||||
{
|
||||
type.map = map;
|
||||
|
||||
type.format = spa_type_map_get_id (map, SPA_TYPE__Format);
|
||||
spa_type_media_type_map (map, &type.media_type);
|
||||
spa_type_media_subtype_map (map, &type.media_subtype);
|
||||
spa_type_media_subtype_video_map (map, &type.media_subtype_video);
|
||||
spa_type_media_subtype_audio_map (map, &type.media_subtype_audio);
|
||||
spa_type_format_video_map (map, &type.format_video);
|
||||
spa_type_format_audio_map (map, &type.format_audio);
|
||||
spa_type_video_format_map (map, &type.video_format);
|
||||
spa_type_audio_format_map (map, &type.audio_format);
|
||||
}
|
||||
|
||||
static const struct media_type media_type_map[] = {
|
||||
{ "video/x-raw", &type.media_type.video, &type.media_subtype.raw },
|
||||
{ "audio/x-raw", &type.media_type.audio, &type.media_subtype.raw },
|
||||
{ "image/jpeg", &type.media_type.video, &type.media_subtype_video.mjpg },
|
||||
{ "video/x-h264", &type.media_type.video, &type.media_subtype_video.h264 },
|
||||
{ NULL, }
|
||||
};
|
||||
|
||||
static const uint32_t *video_format_map[] = {
|
||||
&type.video_format.UNKNOWN,
|
||||
&type.video_format.ENCODED,
|
||||
&type.video_format.I420,
|
||||
&type.video_format.YV12,
|
||||
&type.video_format.YUY2,
|
||||
&type.video_format.UYVY,
|
||||
&type.video_format.AYUV,
|
||||
&type.video_format.RGBx,
|
||||
&type.video_format.BGRx,
|
||||
&type.video_format.xRGB,
|
||||
&type.video_format.xBGR,
|
||||
&type.video_format.RGBA,
|
||||
&type.video_format.BGRA,
|
||||
&type.video_format.ARGB,
|
||||
&type.video_format.ABGR,
|
||||
&type.video_format.RGB,
|
||||
&type.video_format.BGR,
|
||||
&type.video_format.Y41B,
|
||||
&type.video_format.Y42B,
|
||||
&type.video_format.YVYU,
|
||||
&type.video_format.Y444,
|
||||
&type.video_format.v210,
|
||||
&type.video_format.v216,
|
||||
&type.video_format.NV12,
|
||||
&type.video_format.NV21,
|
||||
&type.video_format.GRAY8,
|
||||
&type.video_format.GRAY16_BE,
|
||||
&type.video_format.GRAY16_LE,
|
||||
&type.video_format.v308,
|
||||
&type.video_format.RGB16,
|
||||
&type.video_format.BGR16,
|
||||
&type.video_format.RGB15,
|
||||
&type.video_format.BGR15,
|
||||
&type.video_format.UYVP,
|
||||
&type.video_format.A420,
|
||||
&type.video_format.RGB8P,
|
||||
&type.video_format.YUV9,
|
||||
&type.video_format.YVU9,
|
||||
&type.video_format.IYU1,
|
||||
&type.video_format.ARGB64,
|
||||
&type.video_format.AYUV64,
|
||||
&type.video_format.r210,
|
||||
&type.video_format.I420_10BE,
|
||||
&type.video_format.I420_10LE,
|
||||
&type.video_format.I422_10BE,
|
||||
&type.video_format.I422_10LE,
|
||||
&type.video_format.Y444_10BE,
|
||||
&type.video_format.Y444_10LE,
|
||||
&type.video_format.GBR,
|
||||
&type.video_format.GBR_10BE,
|
||||
&type.video_format.GBR_10LE,
|
||||
&type.video_format.NV16,
|
||||
&type.video_format.NV24,
|
||||
&type.video_format.NV12_64Z32,
|
||||
&type.video_format.A420_10BE,
|
||||
&type.video_format.A420_10LE,
|
||||
&type.video_format.A422_10BE,
|
||||
&type.video_format.A422_10LE,
|
||||
&type.video_format.A444_10BE,
|
||||
&type.video_format.A444_10LE,
|
||||
&type.video_format.NV61,
|
||||
&type.video_format.P010_10BE,
|
||||
&type.video_format.P010_10LE,
|
||||
&type.video_format.IYU2,
|
||||
&type.video_format.VYUY,
|
||||
&type.video_format.GBRA,
|
||||
&type.video_format.GBRA_10BE,
|
||||
&type.video_format.GBRA_10LE,
|
||||
&type.video_format.GBR_12BE,
|
||||
&type.video_format.GBR_12LE,
|
||||
&type.video_format.GBRA_12BE,
|
||||
&type.video_format.GBRA_12LE,
|
||||
&type.video_format.I420_12BE,
|
||||
&type.video_format.I420_12LE,
|
||||
&type.video_format.I422_12BE,
|
||||
&type.video_format.I422_12LE,
|
||||
&type.video_format.Y444_12BE,
|
||||
&type.video_format.Y444_12LE,
|
||||
};
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define _FORMAT_LE(fmt) &type.audio_format. fmt ## _OE
|
||||
#define _FORMAT_BE(fmt) &type.audio_format. fmt
|
||||
#elif __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define _FORMAT_LE(fmt) &type.audio_format. fmt
|
||||
#define _FORMAT_BE(fmt) &type.audio_format. fmt ## _OE
|
||||
#endif
|
||||
|
||||
static const uint32_t *audio_format_map[] = {
|
||||
&type.audio_format.UNKNOWN,
|
||||
&type.audio_format.ENCODED,
|
||||
&type.audio_format.S8,
|
||||
&type.audio_format.U8,
|
||||
_FORMAT_LE (S16),
|
||||
_FORMAT_BE (S16),
|
||||
_FORMAT_LE (U16),
|
||||
_FORMAT_BE (U16),
|
||||
_FORMAT_LE (S24_32),
|
||||
_FORMAT_BE (S24_32),
|
||||
_FORMAT_LE (U24_32),
|
||||
_FORMAT_BE (U24_32),
|
||||
_FORMAT_LE (S32),
|
||||
_FORMAT_BE (S32),
|
||||
_FORMAT_LE (U32),
|
||||
_FORMAT_BE (U32),
|
||||
_FORMAT_LE (S24),
|
||||
_FORMAT_BE (S24),
|
||||
_FORMAT_LE (U24),
|
||||
_FORMAT_BE (U24),
|
||||
_FORMAT_LE (S20),
|
||||
_FORMAT_BE (S20),
|
||||
_FORMAT_LE (U20),
|
||||
_FORMAT_BE (U20),
|
||||
_FORMAT_LE (S18),
|
||||
_FORMAT_BE (S18),
|
||||
_FORMAT_LE (U18),
|
||||
_FORMAT_BE (U18),
|
||||
_FORMAT_LE (F32),
|
||||
_FORMAT_BE (F32),
|
||||
_FORMAT_LE (F64),
|
||||
_FORMAT_BE (F64),
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct spa_pod_builder b;
|
||||
const struct media_type *type;
|
||||
const GstCapsFeatures *cf;
|
||||
const GstStructure *cs;
|
||||
} ConvertData;
|
||||
|
||||
static const struct media_type *
|
||||
find_media_types (const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; media_type_map[i].name; i++) {
|
||||
if (!strcmp (media_type_map[i].name, name))
|
||||
return &media_type_map[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_nth_string (const GValue *val, int idx)
|
||||
{
|
||||
const GValue *v = NULL;
|
||||
GType type = G_VALUE_TYPE (val);
|
||||
|
||||
if (type == G_TYPE_STRING && idx == 0)
|
||||
v = val;
|
||||
else if (type == GST_TYPE_LIST) {
|
||||
GArray *array = g_value_peek_pointer (val);
|
||||
if (idx < array->len + 1) {
|
||||
v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
|
||||
}
|
||||
}
|
||||
if (v)
|
||||
return g_value_get_string (v);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
get_nth_int (const GValue *val, int idx, int *res)
|
||||
{
|
||||
const GValue *v = NULL;
|
||||
GType type = G_VALUE_TYPE (val);
|
||||
|
||||
if (type == G_TYPE_INT && idx == 0) {
|
||||
v = val;
|
||||
} else if (type == GST_TYPE_INT_RANGE) {
|
||||
if (idx == 0 || idx == 1) {
|
||||
*res = gst_value_get_int_range_min (val);
|
||||
return true;
|
||||
} else if (idx == 2) {
|
||||
*res = gst_value_get_int_range_max (val);
|
||||
return true;
|
||||
}
|
||||
} else if (type == GST_TYPE_LIST) {
|
||||
GArray *array = g_value_peek_pointer (val);
|
||||
if (idx < array->len + 1) {
|
||||
v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
|
||||
}
|
||||
}
|
||||
if (v) {
|
||||
*res = g_value_get_int (v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
get_nth_fraction (const GValue *val, int idx, struct spa_fraction *f)
|
||||
{
|
||||
const GValue *v = NULL;
|
||||
GType type = G_VALUE_TYPE (val);
|
||||
|
||||
if (type == GST_TYPE_FRACTION && idx == 0) {
|
||||
v = val;
|
||||
} else if (type == GST_TYPE_FRACTION_RANGE) {
|
||||
if (idx == 0 || idx == 1) {
|
||||
v = gst_value_get_fraction_range_min (val);
|
||||
} else if (idx == 2) {
|
||||
v = gst_value_get_fraction_range_max (val);
|
||||
}
|
||||
} else if (type == GST_TYPE_LIST) {
|
||||
GArray *array = g_value_peek_pointer (val);
|
||||
if (idx < array->len + 1) {
|
||||
v = &g_array_index (array, GValue, SPA_MAX (idx-1, 0));
|
||||
}
|
||||
}
|
||||
if (v) {
|
||||
f->num = gst_value_get_fraction_numerator (v);
|
||||
f->denom = gst_value_get_fraction_denominator (v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
get_nth_rectangle (const GValue *width, const GValue *height, int idx, struct spa_rectangle *r)
|
||||
{
|
||||
const GValue *w = NULL, *h = NULL;
|
||||
GType wt = G_VALUE_TYPE (width);
|
||||
GType ht = G_VALUE_TYPE (height);
|
||||
|
||||
if (wt == G_TYPE_INT && ht == G_TYPE_INT && idx == 0) {
|
||||
w = width;
|
||||
h = height;
|
||||
} else if (wt == GST_TYPE_INT_RANGE && ht == GST_TYPE_INT_RANGE) {
|
||||
if (idx == 0 || idx == 1) {
|
||||
r->width = gst_value_get_int_range_min (width);
|
||||
r->height = gst_value_get_int_range_min (height);
|
||||
return true;
|
||||
} else if (idx == 2) {
|
||||
r->width = gst_value_get_int_range_max (width);
|
||||
r->height = gst_value_get_int_range_max (height);
|
||||
return true;
|
||||
}
|
||||
} else if (wt == GST_TYPE_LIST && ht == GST_TYPE_LIST) {
|
||||
GArray *wa = g_value_peek_pointer (width);
|
||||
GArray *ha = g_value_peek_pointer (height);
|
||||
if (idx < wa->len + 1)
|
||||
w = &g_array_index (wa, GValue, SPA_MAX (idx-1, 0));
|
||||
if (idx < ha->len + 1)
|
||||
h = &g_array_index (ha, GValue, SPA_MAX (idx-1, 0));
|
||||
}
|
||||
if (w && h) {
|
||||
r->width = g_value_get_int (w);
|
||||
r->height = g_value_get_int (h);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const uint32_t
|
||||
get_range_type (const GValue *val)
|
||||
{
|
||||
GType type = G_VALUE_TYPE (val);
|
||||
|
||||
if (type == GST_TYPE_LIST)
|
||||
return SPA_POD_PROP_RANGE_ENUM;
|
||||
if (type == GST_TYPE_DOUBLE_RANGE || type == GST_TYPE_FRACTION_RANGE)
|
||||
return SPA_POD_PROP_RANGE_MIN_MAX;
|
||||
if (type == GST_TYPE_INT_RANGE) {
|
||||
if (gst_value_get_int_range_step (val) == 1)
|
||||
return SPA_POD_PROP_RANGE_MIN_MAX;
|
||||
else
|
||||
return SPA_POD_PROP_RANGE_STEP;
|
||||
}
|
||||
if (type == GST_TYPE_INT64_RANGE) {
|
||||
if (gst_value_get_int64_range_step (val) == 1)
|
||||
return SPA_POD_PROP_RANGE_MIN_MAX;
|
||||
else
|
||||
return SPA_POD_PROP_RANGE_STEP;
|
||||
}
|
||||
return SPA_POD_PROP_RANGE_NONE;
|
||||
}
|
||||
|
||||
static const uint32_t
|
||||
get_range_type2 (const GValue *v1, const GValue *v2)
|
||||
{
|
||||
uint32_t r1, r2;
|
||||
|
||||
r1 = get_range_type (v1);
|
||||
r2 = get_range_type (v2);
|
||||
|
||||
if (r1 == r2)
|
||||
return r1;
|
||||
if (r1 == SPA_POD_PROP_RANGE_STEP || r2 == SPA_POD_PROP_RANGE_STEP)
|
||||
return SPA_POD_PROP_RANGE_STEP;
|
||||
if (r1 == SPA_POD_PROP_RANGE_MIN_MAX || r2 == SPA_POD_PROP_RANGE_MIN_MAX)
|
||||
return SPA_POD_PROP_RANGE_MIN_MAX;
|
||||
return SPA_POD_PROP_RANGE_MIN_MAX;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_video_fields (ConvertData *d)
|
||||
{
|
||||
struct spa_pod_frame f;
|
||||
const GValue *value, *value2;
|
||||
int i;
|
||||
|
||||
value = gst_structure_get_value (d->cs, "format");
|
||||
if (value) {
|
||||
const char *v;
|
||||
int idx;
|
||||
for (i = 0; (v = get_nth_string (value, i)); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_video.format,
|
||||
get_range_type (value));
|
||||
|
||||
idx = gst_video_format_from_string (v);
|
||||
if (idx < SPA_N_ELEMENTS (video_format_map))
|
||||
spa_pod_builder_id (&d->b, *video_format_map[idx]);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
value = gst_structure_get_value (d->cs, "width");
|
||||
value2 = gst_structure_get_value (d->cs, "height");
|
||||
if (value || value2) {
|
||||
struct spa_rectangle v;
|
||||
for (i = 0; get_nth_rectangle (value, value2, i, &v); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_video.size,
|
||||
get_range_type2 (value, value2));
|
||||
|
||||
spa_pod_builder_rectangle (&d->b, v.width, v.height);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
|
||||
value = gst_structure_get_value (d->cs, "framerate");
|
||||
if (value) {
|
||||
struct spa_fraction v;
|
||||
for (i = 0; get_nth_fraction (value, i, &v); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_video.framerate,
|
||||
get_range_type (value));
|
||||
|
||||
spa_pod_builder_fraction (&d->b, v.num, v.denom);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
|
||||
value = gst_structure_get_value (d->cs, "max-framerate");
|
||||
if (value) {
|
||||
struct spa_fraction v;
|
||||
for (i = 0; get_nth_fraction (value, i, &v); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_video.max_framerate,
|
||||
get_range_type (value));
|
||||
|
||||
spa_pod_builder_fraction (&d->b, v.num, v.denom);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_audio_fields (ConvertData *d)
|
||||
{
|
||||
struct spa_pod_frame f;
|
||||
const GValue *value;
|
||||
int i = 0;
|
||||
|
||||
value = gst_structure_get_value (d->cs, "format");
|
||||
if (value) {
|
||||
const char *v;
|
||||
int idx;
|
||||
for (i = 0; (v = get_nth_string (value, i)); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_audio.format,
|
||||
get_range_type (value));
|
||||
|
||||
idx = gst_audio_format_from_string (v);
|
||||
if (idx < SPA_N_ELEMENTS (audio_format_map))
|
||||
spa_pod_builder_id (&d->b, *audio_format_map[idx]);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
|
||||
value = gst_structure_get_value (d->cs, "layout");
|
||||
if (value) {
|
||||
const char *v;
|
||||
for (i = 0; (v = get_nth_string (value, i)); i++) {
|
||||
enum spa_audio_layout layout;
|
||||
|
||||
if (!strcmp (v, "interleaved"))
|
||||
layout = SPA_AUDIO_LAYOUT_INTERLEAVED;
|
||||
else if (!strcmp (v, "non-interleaved"))
|
||||
layout = SPA_AUDIO_LAYOUT_NON_INTERLEAVED;
|
||||
else
|
||||
break;
|
||||
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_audio.layout,
|
||||
get_range_type (value));
|
||||
|
||||
spa_pod_builder_int (&d->b, layout);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
value = gst_structure_get_value (d->cs, "rate");
|
||||
if (value) {
|
||||
int v;
|
||||
for (i = 0; get_nth_int (value, i, &v); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_audio.rate,
|
||||
get_range_type (value));
|
||||
|
||||
spa_pod_builder_int (&d->b, v);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
value = gst_structure_get_value (d->cs, "channels");
|
||||
if (value) {
|
||||
int v;
|
||||
for (i = 0; get_nth_int (value, i, &v); i++) {
|
||||
if (i == 0)
|
||||
spa_pod_builder_push_prop (&d->b, &f,
|
||||
type.format_audio.channels,
|
||||
get_range_type (value));
|
||||
|
||||
spa_pod_builder_int (&d->b, v);
|
||||
}
|
||||
if (i > 1)
|
||||
SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
|
||||
spa_pod_builder_pop (&d->b, &f);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
write_pod (struct spa_pod_builder *b, uint32_t ref, const void *data, uint32_t size)
|
||||
{
|
||||
if (ref == -1)
|
||||
ref = b->offset;
|
||||
|
||||
if (b->size <= b->offset) {
|
||||
b->size = SPA_ROUND_UP_N (b->offset + size, 512);
|
||||
b->data = realloc (b->data, b->size);
|
||||
}
|
||||
memcpy (b->data + ref, data, size);
|
||||
return ref;
|
||||
}
|
||||
|
||||
static struct spa_format *
|
||||
convert_1 (GstCapsFeatures *cf, GstStructure *cs)
|
||||
{
|
||||
ConvertData d;
|
||||
struct spa_pod_frame f;
|
||||
|
||||
spa_zero (d);
|
||||
d.cf = cf;
|
||||
d.cs = cs;
|
||||
|
||||
if (!(d.type = find_media_types (gst_structure_get_name (cs))))
|
||||
return NULL;
|
||||
|
||||
d.b.write = write_pod;
|
||||
|
||||
spa_pod_builder_push_format (&d.b, &f, type.format,
|
||||
*d.type->media_type,
|
||||
*d.type->media_subtype);
|
||||
|
||||
if (*d.type->media_type == type.media_type.video)
|
||||
handle_video_fields (&d);
|
||||
else if (*d.type->media_type == type.media_type.audio)
|
||||
handle_audio_fields (&d);
|
||||
|
||||
spa_pod_builder_pop (&d.b, &f);
|
||||
|
||||
return SPA_MEMBER (d.b.data, 0, struct spa_format);
|
||||
}
|
||||
|
||||
struct spa_format *
|
||||
gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map)
|
||||
{
|
||||
GstCapsFeatures *f;
|
||||
GstStructure *s;
|
||||
struct spa_format *res;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CAPS (caps), NULL);
|
||||
g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
|
||||
|
||||
ensure_types(map);
|
||||
|
||||
f = gst_caps_get_features (caps, index);
|
||||
s = gst_caps_get_structure (caps, index);
|
||||
|
||||
res = convert_1 (f, s);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
foreach_func (GstCapsFeatures *features,
|
||||
GstStructure *structure,
|
||||
GPtrArray *array)
|
||||
{
|
||||
struct spa_format *fmt;
|
||||
|
||||
if ((fmt = convert_1 (features, structure)))
|
||||
g_ptr_array_insert (array, -1, fmt);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
GPtrArray *
|
||||
gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map)
|
||||
{
|
||||
GPtrArray *res;
|
||||
|
||||
ensure_types(map);
|
||||
|
||||
res = g_ptr_array_new_full (gst_caps_get_size (caps), (GDestroyNotify)g_free);
|
||||
gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_id_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
|
||||
{
|
||||
const char * str;
|
||||
uint32_t *id = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
|
||||
uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
|
||||
uint32_t flags;
|
||||
|
||||
flags = prop->body.flags;
|
||||
if (!(flags & SPA_POD_PROP_FLAG_UNSET))
|
||||
flags &= ~SPA_POD_PROP_RANGE_MASK;
|
||||
|
||||
switch (flags & SPA_POD_PROP_RANGE_MASK) {
|
||||
case SPA_POD_PROP_RANGE_NONE:
|
||||
if (!(str = spa_type_map_get_type (type.map, id[0])))
|
||||
return;
|
||||
gst_caps_set_simple (res, key, G_TYPE_STRING, rindex (str, ':') + 1, NULL);
|
||||
break;
|
||||
case SPA_POD_PROP_RANGE_ENUM:
|
||||
{
|
||||
GValue list = { 0 }, v = { 0 };
|
||||
|
||||
g_value_init (&list, GST_TYPE_LIST);
|
||||
for (i = 1; i < n_items; i++) {
|
||||
if (!(str = spa_type_map_get_type (type.map, id[i])))
|
||||
continue;
|
||||
|
||||
g_value_init (&v, G_TYPE_STRING);
|
||||
g_value_set_string (&v, rindex (str, ':') + 1);
|
||||
gst_value_list_append_and_take_value (&list, &v);
|
||||
}
|
||||
gst_caps_set_value (res, key, &list);
|
||||
g_value_unset (&list);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_int_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
|
||||
{
|
||||
uint32_t *val = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
|
||||
uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
|
||||
uint32_t flags;
|
||||
|
||||
flags = prop->body.flags;
|
||||
if (!(flags & SPA_POD_PROP_FLAG_UNSET))
|
||||
flags &= ~SPA_POD_PROP_RANGE_MASK;
|
||||
|
||||
switch (flags & SPA_POD_PROP_RANGE_MASK) {
|
||||
case SPA_POD_PROP_RANGE_NONE:
|
||||
gst_caps_set_simple (res, key, G_TYPE_INT, val[0], NULL);
|
||||
break;
|
||||
case SPA_POD_PROP_RANGE_MIN_MAX:
|
||||
case SPA_POD_PROP_RANGE_STEP:
|
||||
{
|
||||
if (n_items < 3)
|
||||
return;
|
||||
gst_caps_set_simple (res, key, GST_TYPE_INT_RANGE, val[1], val[2], NULL);
|
||||
break;
|
||||
}
|
||||
case SPA_POD_PROP_RANGE_ENUM:
|
||||
{
|
||||
GValue list = { 0 }, v = { 0 };
|
||||
|
||||
g_value_init (&list, GST_TYPE_LIST);
|
||||
for (i = 1; i < n_items; i++) {
|
||||
g_value_init (&v, G_TYPE_INT);
|
||||
g_value_set_int (&v, val[i]);
|
||||
gst_value_list_append_and_take_value (&list, &v);
|
||||
}
|
||||
gst_caps_set_value (res, key, &list);
|
||||
g_value_unset (&list);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_rect_prop (struct spa_pod_prop *prop, const char *width, const char *height, GstCaps *res)
|
||||
{
|
||||
struct spa_rectangle *rect = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
|
||||
uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
|
||||
uint32_t flags;
|
||||
|
||||
flags = prop->body.flags;
|
||||
if (!(flags & SPA_POD_PROP_FLAG_UNSET))
|
||||
flags &= ~SPA_POD_PROP_RANGE_MASK;
|
||||
|
||||
switch (flags & SPA_POD_PROP_RANGE_MASK) {
|
||||
case SPA_POD_PROP_RANGE_NONE:
|
||||
gst_caps_set_simple (res, width, G_TYPE_INT, rect[0].width,
|
||||
height, G_TYPE_INT, rect[0].height, NULL);
|
||||
break;
|
||||
case SPA_POD_PROP_RANGE_MIN_MAX:
|
||||
case SPA_POD_PROP_RANGE_STEP:
|
||||
{
|
||||
if (n_items < 3)
|
||||
return;
|
||||
gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width,
|
||||
height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL);
|
||||
break;
|
||||
}
|
||||
case SPA_POD_PROP_RANGE_ENUM:
|
||||
{
|
||||
GValue l1 = { 0 }, l2 = { 0 }, v1 = { 0 }, v2 = { 0 };
|
||||
|
||||
g_value_init (&l1, GST_TYPE_LIST);
|
||||
g_value_init (&l2, GST_TYPE_LIST);
|
||||
for (i = 1; i < n_items; i++) {
|
||||
g_value_init (&v1, G_TYPE_INT);
|
||||
g_value_set_int (&v1, rect[i].width);
|
||||
gst_value_list_append_and_take_value (&l1, &v1);
|
||||
|
||||
g_value_init (&v2, G_TYPE_INT);
|
||||
g_value_set_int (&v2, rect[i].height);
|
||||
gst_value_list_append_and_take_value (&l2, &v2);
|
||||
}
|
||||
gst_caps_set_value (res, width, &l1);
|
||||
gst_caps_set_value (res, height, &l2);
|
||||
g_value_unset (&l1);
|
||||
g_value_unset (&l2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_fraction_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
|
||||
{
|
||||
struct spa_fraction *fract = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
|
||||
uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
|
||||
uint32_t flags;
|
||||
|
||||
flags = prop->body.flags;
|
||||
if (!(flags & SPA_POD_PROP_FLAG_UNSET))
|
||||
flags &= ~SPA_POD_PROP_RANGE_MASK;
|
||||
|
||||
switch (flags & SPA_POD_PROP_RANGE_MASK) {
|
||||
case SPA_POD_PROP_RANGE_NONE:
|
||||
gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[0].num, fract[0].denom, NULL);
|
||||
break;
|
||||
case SPA_POD_PROP_RANGE_MIN_MAX:
|
||||
case SPA_POD_PROP_RANGE_STEP:
|
||||
{
|
||||
if (n_items < 3)
|
||||
return;
|
||||
gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom,
|
||||
fract[2].num, fract[2].denom, NULL);
|
||||
break;
|
||||
}
|
||||
case SPA_POD_PROP_RANGE_ENUM:
|
||||
{
|
||||
GValue l1 = { 0 }, v1 = { 0 };
|
||||
|
||||
g_value_init (&l1, GST_TYPE_LIST);
|
||||
for (i = 1; i < n_items; i++) {
|
||||
g_value_init (&v1, GST_TYPE_FRACTION);
|
||||
gst_value_set_fraction (&v1, fract[i].num, fract[i].denom);
|
||||
gst_value_list_append_and_take_value (&l1, &v1);
|
||||
}
|
||||
gst_caps_set_value (res, key, &l1);
|
||||
g_value_unset (&l1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
GstCaps *
|
||||
gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map)
|
||||
{
|
||||
GstCaps *res = NULL;
|
||||
uint32_t media_type, media_subtype;
|
||||
struct spa_pod_prop *prop;
|
||||
|
||||
ensure_types(map);
|
||||
|
||||
media_type = SPA_FORMAT_MEDIA_TYPE (format);
|
||||
media_subtype = SPA_FORMAT_MEDIA_SUBTYPE (format);
|
||||
|
||||
if (media_type == type.media_type.video) {
|
||||
if (media_subtype == type.media_subtype.raw) {
|
||||
res = gst_caps_new_empty_simple ("video/x-raw");
|
||||
if ((prop = spa_format_find_prop (format, type.format_video.format))) {
|
||||
handle_id_prop (prop, "format", res);
|
||||
}
|
||||
}
|
||||
else if (media_subtype == type.media_subtype_video.mjpg) {
|
||||
res = gst_caps_new_empty_simple ("image/jpeg");
|
||||
}
|
||||
else if (media_subtype == type.media_subtype_video.h264) {
|
||||
res = gst_caps_new_simple ("video/x-h264",
|
||||
"stream-format", G_TYPE_STRING, "byte-stream",
|
||||
"alignment", G_TYPE_STRING, "au",
|
||||
NULL);
|
||||
}
|
||||
if ((prop = spa_format_find_prop (format, type.format_video.size))) {
|
||||
handle_rect_prop (prop, "width", "height", res);
|
||||
}
|
||||
if ((prop = spa_format_find_prop (format, type.format_video.framerate))) {
|
||||
handle_fraction_prop (prop, "framerate", res);
|
||||
}
|
||||
if ((prop = spa_format_find_prop (format, type.format_video.max_framerate))) {
|
||||
handle_fraction_prop (prop, "max-framerate", res);
|
||||
}
|
||||
} else if (media_type == type.media_type.audio) {
|
||||
if (media_subtype == type.media_subtype.raw) {
|
||||
res = gst_caps_new_simple ("audio/x-raw",
|
||||
"layout", G_TYPE_STRING, "interleaved",
|
||||
NULL);
|
||||
if ((prop = spa_format_find_prop (format, type.format_audio.format))) {
|
||||
handle_id_prop (prop, "format", res);
|
||||
}
|
||||
if ((prop = spa_format_find_prop (format, type.format_audio.rate))) {
|
||||
handle_int_prop (prop, "rate", res);
|
||||
}
|
||||
if ((prop = spa_format_find_prop (format, type.format_audio.channels))) {
|
||||
handle_int_prop (prop, "channels", res);
|
||||
}
|
||||
}
|
||||
else if (media_subtype == type.media_subtype_audio.aac) {
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
37
src/gst/gstpipewireformat.h
Normal file
37
src/gst/gstpipewireformat.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _GST_PIPEWIRE_FORMAT_H_
|
||||
#define _GST_PIPEWIRE_FORMAT_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <spa/type-map.h>
|
||||
#include <spa/format.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct spa_format * gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map);
|
||||
GPtrArray * gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map);
|
||||
|
||||
GstCaps * gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
178
src/gst/gstpipewirepool.c
Normal file
178
src/gst/gstpipewirepool.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstpipewirepool.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_pipewire_pool_debug_category
|
||||
|
||||
G_DEFINE_TYPE (GstPipeWirePool, gst_pipewire_pool, GST_TYPE_BUFFER_POOL);
|
||||
|
||||
enum
|
||||
{
|
||||
ACTIVATED,
|
||||
/* FILL ME */
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
|
||||
static guint pool_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
GstPipeWirePool *
|
||||
gst_pipewire_pool_new (void)
|
||||
{
|
||||
GstPipeWirePool *pool;
|
||||
|
||||
pool = g_object_new (GST_TYPE_PIPEWIRE_POOL, NULL);
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
|
||||
{
|
||||
g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
|
||||
g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
|
||||
|
||||
GST_OBJECT_LOCK (pool);
|
||||
g_queue_push_tail (&pool->available, buffer);
|
||||
g_cond_signal (&pool->cond);
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
|
||||
{
|
||||
gboolean res;
|
||||
|
||||
g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
|
||||
g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
|
||||
|
||||
GST_OBJECT_LOCK (pool);
|
||||
res = g_queue_remove (&pool->available, buffer);
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer,
|
||||
GstBufferPoolAcquireParams * params)
|
||||
{
|
||||
GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
|
||||
|
||||
GST_OBJECT_LOCK (pool);
|
||||
while (TRUE) {
|
||||
if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool)))
|
||||
goto flushing;
|
||||
|
||||
if (p->available.length > 0)
|
||||
break;
|
||||
|
||||
GST_WARNING ("queue empty");
|
||||
g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool));
|
||||
}
|
||||
*buffer = g_queue_pop_head (&p->available);
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
GST_DEBUG ("acquire buffer %p", *buffer);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
flushing:
|
||||
{
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
return GST_FLOW_FLUSHING;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flush_start (GstBufferPool * pool)
|
||||
{
|
||||
GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
|
||||
|
||||
GST_DEBUG ("flush start");
|
||||
GST_OBJECT_LOCK (pool);
|
||||
g_cond_signal (&p->cond);
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
}
|
||||
|
||||
static void
|
||||
release_buffer (GstBufferPool * pool, GstBuffer *buffer)
|
||||
{
|
||||
GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
|
||||
|
||||
GST_DEBUG ("release buffer %p", buffer);
|
||||
GST_OBJECT_LOCK (pool);
|
||||
g_queue_push_tail (&p->available, buffer);
|
||||
g_cond_signal (&p->cond);
|
||||
GST_OBJECT_UNLOCK (pool);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_start (GstBufferPool * pool)
|
||||
{
|
||||
g_signal_emit (pool, pool_signals[ACTIVATED], 0, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_pool_finalize (GObject * object)
|
||||
{
|
||||
GstPipeWirePool *pool = GST_PIPEWIRE_POOL (object);
|
||||
|
||||
GST_DEBUG_OBJECT (pool, "finalize");
|
||||
|
||||
G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_pool_class_init (GstPipeWirePoolClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = gst_pipewire_pool_finalize;
|
||||
|
||||
bufferpool_class->start = do_start;
|
||||
bufferpool_class->flush_start = flush_start;
|
||||
bufferpool_class->acquire_buffer = acquire_buffer;
|
||||
bufferpool_class->release_buffer = release_buffer;
|
||||
|
||||
pool_signals[ACTIVATED] =
|
||||
g_signal_new ("activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_pipewire_pool_debug_category, "pipewirepool", 0,
|
||||
"debug category for pipewirepool object");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_pool_init (GstPipeWirePool * pool)
|
||||
{
|
||||
g_cond_init (&pool->cond);
|
||||
g_queue_init (&pool->available);
|
||||
}
|
||||
66
src/gst/gstpipewirepool.h
Normal file
66
src/gst/gstpipewirepool.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PIPEWIRE_POOL_H__
|
||||
#define __GST_PIPEWIRE_POOL_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_POOL \
|
||||
(gst_pipewire_pool_get_type())
|
||||
#define GST_PIPEWIRE_POOL(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePool))
|
||||
#define GST_PIPEWIRE_POOL_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePoolClass))
|
||||
#define GST_IS_PIPEWIRE_POOL(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_POOL))
|
||||
#define GST_IS_PIPEWIRE_POOL_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_POOL))
|
||||
#define GST_PIPEWIRE_POOL_GET_CLASS(klass) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_POOL, GstPipeWirePoolClass))
|
||||
|
||||
typedef struct _GstPipeWirePool GstPipeWirePool;
|
||||
typedef struct _GstPipeWirePoolClass GstPipeWirePoolClass;
|
||||
|
||||
struct _GstPipeWirePool {
|
||||
GstBufferPool parent;
|
||||
|
||||
struct pw_stream *stream;
|
||||
GQueue available;
|
||||
GCond cond;
|
||||
};
|
||||
|
||||
struct _GstPipeWirePoolClass {
|
||||
GstBufferPoolClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_pool_get_type (void);
|
||||
|
||||
GstPipeWirePool * gst_pipewire_pool_new (void);
|
||||
|
||||
gboolean gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
|
||||
gboolean gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PIPEWIRE_POOL_H__ */
|
||||
941
src/gst/gstpipewiresink.c
Normal file
941
src/gst/gstpipewiresink.c
Normal file
|
|
@ -0,0 +1,941 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-pipewiresink
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch -v videotestsrc ! pipewiresink
|
||||
* ]| Sends a test video source to PipeWire
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include "gstpipewiresink.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gio/gunixfdmessage.h>
|
||||
#include <gst/allocators/gstfdmemory.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <spa/buffer.h>
|
||||
|
||||
#include "gstpipewireformat.h"
|
||||
|
||||
static GQuark process_mem_data_quark;
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug);
|
||||
#define GST_CAT_DEFAULT pipewire_sink_debug
|
||||
|
||||
#define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_PATH,
|
||||
PROP_CLIENT_NAME,
|
||||
PROP_STREAM_PROPERTIES,
|
||||
PROP_MODE
|
||||
};
|
||||
|
||||
GType
|
||||
gst_pipewire_sink_mode_get_type (void)
|
||||
{
|
||||
static volatile 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);
|
||||
|
||||
pw_thread_loop_destroy (pwsink->main_loop);
|
||||
pwsink->main_loop = NULL;
|
||||
|
||||
pw_loop_destroy (pwsink->loop);
|
||||
pwsink->loop = NULL;
|
||||
|
||||
if (pwsink->properties)
|
||||
gst_structure_free (pwsink->properties);
|
||||
g_object_unref (pwsink->allocator);
|
||||
g_free (pwsink->path);
|
||||
g_free (pwsink->client_name);
|
||||
g_hash_table_unref (pwsink->buf_ids);
|
||||
|
||||
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);
|
||||
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_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_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));
|
||||
|
||||
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");
|
||||
|
||||
process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSinkProcessMemQuark");
|
||||
}
|
||||
|
||||
|
||||
#define PROP(f,key,type,...) \
|
||||
SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
|
||||
#define PROP_R(f,key,type,...) \
|
||||
SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_READONLY,type,1,__VA_ARGS__)
|
||||
#define PROP_MM(f,key,type,...) \
|
||||
SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
|
||||
#define PROP_U_MM(f,key,type,...) \
|
||||
SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
|
||||
SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
|
||||
#define PROP_EN(f,key,type,n,...) \
|
||||
SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__)
|
||||
#define PROP_U_EN(f,key,type,n,...) \
|
||||
SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
|
||||
SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__)
|
||||
static void
|
||||
pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink)
|
||||
{
|
||||
struct pw_remote *remote = sink->stream->remote;
|
||||
struct pw_core *core = remote->core;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
guint size;
|
||||
guint min_buffers;
|
||||
guint max_buffers;
|
||||
struct spa_param *port_params[3];
|
||||
struct spa_pod_builder b = { NULL };
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_frame f[2];
|
||||
|
||||
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[0], 0, core->type.param_alloc_buffers.Buffers);
|
||||
if (size == 0)
|
||||
spa_pod_builder_add (&b,
|
||||
PROP_U_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), 0);
|
||||
else
|
||||
spa_pod_builder_add (&b,
|
||||
PROP_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, size, size, INT32_MAX), 0);
|
||||
|
||||
spa_pod_builder_add (&b,
|
||||
PROP_MM (&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, 0, 0, INT32_MAX),
|
||||
PROP_U_MM (&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, min_buffers, min_buffers, max_buffers ? max_buffers : INT32_MAX),
|
||||
PROP (&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, 16),
|
||||
0);
|
||||
spa_pod_builder_pop (&b, &f[0]);
|
||||
port_params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
|
||||
|
||||
spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Header),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_header)));
|
||||
port_params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
|
||||
|
||||
spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Ringbuffer),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_ringbuffer)),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferSize, SPA_POD_TYPE_INT,
|
||||
size * SPA_MAX (4,
|
||||
SPA_MAX (min_buffers, max_buffers))),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferStride, SPA_POD_TYPE_INT, 0),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferBlocks, SPA_POD_TYPE_INT, 1),
|
||||
PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferAlign, SPA_POD_TYPE_INT, 16));
|
||||
port_params[2] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
|
||||
|
||||
pw_thread_loop_lock (sink->main_loop);
|
||||
pw_stream_finish_format (sink->stream, SPA_RESULT_OK, port_params, 2);
|
||||
pw_thread_loop_unlock (sink->main_loop);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pipewire_sink_init (GstPipeWireSink * sink)
|
||||
{
|
||||
sink->allocator = gst_fd_allocator_new ();
|
||||
sink->pool = gst_pipewire_pool_new ();
|
||||
sink->client_name = pw_get_client_name();
|
||||
sink->mode = DEFAULT_PROP_MODE;
|
||||
|
||||
g_signal_connect (sink->pool, "activated", G_CALLBACK (pool_activated), sink);
|
||||
|
||||
sink->buf_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
||||
(GDestroyNotify) gst_buffer_unref);
|
||||
|
||||
g_queue_init (&sink->queue);
|
||||
|
||||
sink->loop = pw_loop_new ();
|
||||
sink->main_loop = pw_thread_loop_new (sink->loop, "pipewire-sink-loop");
|
||||
sink->core = pw_core_new (sink->loop, NULL);
|
||||
GST_DEBUG ("loop %p %p", sink->loop, sink->main_loop);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_CLIENT_NAME:
|
||||
g_free (pwsink->client_name);
|
||||
pwsink->client_name = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
case PROP_STREAM_PROPERTIES:
|
||||
if (pwsink->properties)
|
||||
gst_structure_free (pwsink->properties);
|
||||
pwsink->properties =
|
||||
gst_structure_copy (gst_value_get_structure (value));
|
||||
break;
|
||||
|
||||
case PROP_MODE:
|
||||
pwsink->mode = g_value_get_enum (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_CLIENT_NAME:
|
||||
g_value_set_string (value, pwsink->client_name);
|
||||
break;
|
||||
|
||||
case PROP_STREAM_PROPERTIES:
|
||||
gst_value_set_structure (value, pwsink->properties);
|
||||
break;
|
||||
|
||||
case PROP_MODE:
|
||||
g_value_set_enum (value, pwsink->mode);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GstPipeWireSink *sink;
|
||||
guint id;
|
||||
struct spa_buffer *buf;
|
||||
struct spa_meta_header *header;
|
||||
guint flags;
|
||||
goffset offset;
|
||||
} ProcessMemData;
|
||||
|
||||
static void
|
||||
process_mem_data_destroy (gpointer user_data)
|
||||
{
|
||||
ProcessMemData *data = user_data;
|
||||
|
||||
gst_object_unref (data->sink);
|
||||
g_slice_free (ProcessMemData, data);
|
||||
}
|
||||
|
||||
static void
|
||||
on_add_buffer (struct pw_listener *listener,
|
||||
struct pw_stream *stream,
|
||||
uint32_t id)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_add_buffer);
|
||||
struct spa_buffer *b;
|
||||
GstBuffer *buf;
|
||||
uint32_t i;
|
||||
ProcessMemData data;
|
||||
struct pw_core *core = pwsink->remote->core;
|
||||
|
||||
GST_LOG_OBJECT (pwsink, "add buffer");
|
||||
|
||||
if (!(b = pw_stream_peek_buffer (pwsink->stream, id))) {
|
||||
g_warning ("failed to peek buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
buf = gst_buffer_new ();
|
||||
|
||||
data.sink = gst_object_ref (pwsink);
|
||||
data.id = id;
|
||||
data.buf = b;
|
||||
data.header = spa_buffer_find_meta (b, core->type.meta.Header);
|
||||
|
||||
for (i = 0; i < b->n_datas; i++) {
|
||||
struct spa_data *d = &b->datas[i];
|
||||
GstMemory *gmem = NULL;
|
||||
|
||||
if (d->type == core->type.data.MemFd ||
|
||||
d->type == core->type.data.DmaBuf) {
|
||||
gmem = gst_fd_allocator_alloc (pwsink->allocator, dup (d->fd),
|
||||
d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE);
|
||||
gst_memory_resize (gmem, d->chunk->offset + d->mapoffset, d->chunk->size);
|
||||
data.offset = d->mapoffset;
|
||||
}
|
||||
else if (d->type == core->type.data.MemPtr) {
|
||||
gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, d->chunk->offset,
|
||||
d->chunk->size, NULL, NULL);
|
||||
data.offset = 0;
|
||||
}
|
||||
if (gmem)
|
||||
gst_buffer_append_memory (buf, gmem);
|
||||
}
|
||||
data.flags = GST_BUFFER_FLAGS (buf);
|
||||
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf),
|
||||
process_mem_data_quark,
|
||||
g_slice_dup (ProcessMemData, &data),
|
||||
process_mem_data_destroy);
|
||||
|
||||
gst_pipewire_pool_add_buffer (pwsink->pool, buf);
|
||||
g_hash_table_insert (pwsink->buf_ids, GINT_TO_POINTER (id), buf);
|
||||
|
||||
pw_thread_loop_signal (pwsink->main_loop, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
on_remove_buffer (struct pw_listener *listener,
|
||||
struct pw_stream *stream,
|
||||
uint32_t id)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_remove_buffer);
|
||||
GstBuffer *buf;
|
||||
|
||||
GST_LOG_OBJECT (pwsink, "remove buffer");
|
||||
buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id));
|
||||
if (buf) {
|
||||
GST_MINI_OBJECT_CAST (buf)->dispose = NULL;
|
||||
if (!gst_pipewire_pool_remove_buffer (pwsink->pool, buf))
|
||||
gst_buffer_ref (buf);
|
||||
if (g_queue_remove (&pwsink->queue, buf))
|
||||
gst_buffer_unref (buf);
|
||||
g_hash_table_remove (pwsink->buf_ids, GINT_TO_POINTER (id));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_new_buffer (struct pw_listener *listener,
|
||||
struct pw_stream *stream,
|
||||
uint32_t id)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_new_buffer);
|
||||
GstBuffer *buf;
|
||||
|
||||
GST_LOG_OBJECT (pwsink, "got new buffer %u", id);
|
||||
if (pwsink->stream == NULL) {
|
||||
GST_LOG_OBJECT (pwsink, "no stream");
|
||||
return;
|
||||
}
|
||||
buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id));
|
||||
|
||||
if (buf) {
|
||||
gst_buffer_unref (buf);
|
||||
pw_thread_loop_signal (pwsink->main_loop, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_send_buffer (GstPipeWireSink *pwsink)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
ProcessMemData *data;
|
||||
gboolean res;
|
||||
guint i;
|
||||
|
||||
buffer = g_queue_pop_head (&pwsink->queue);
|
||||
if (buffer == NULL) {
|
||||
GST_WARNING ("out of buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
data = gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer),
|
||||
process_mem_data_quark);
|
||||
|
||||
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);
|
||||
}
|
||||
for (i = 0; i < data->buf->n_datas; i++) {
|
||||
struct spa_data *d = &data->buf->datas[i];
|
||||
GstMemory *mem = gst_buffer_peek_memory (buffer, i);
|
||||
d->chunk->offset = mem->offset - data->offset;
|
||||
d->chunk->size = mem->size;
|
||||
}
|
||||
|
||||
if (!(res = pw_stream_send_buffer (pwsink->stream, data->id))) {
|
||||
g_warning ("can't send buffer");
|
||||
pw_thread_loop_signal (pwsink->main_loop, FALSE);
|
||||
} else
|
||||
pwsink->need_ready--;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_need_buffer (struct pw_listener *listener,
|
||||
struct pw_stream *stream)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_need_buffer);
|
||||
|
||||
pwsink->need_ready++;
|
||||
GST_DEBUG ("need buffer %u", pwsink->need_ready);
|
||||
do_send_buffer (pwsink);
|
||||
}
|
||||
|
||||
static void
|
||||
on_state_changed (struct pw_listener *listener,
|
||||
struct pw_stream *stream)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_state_changed);
|
||||
enum pw_stream_state state;
|
||||
|
||||
state = stream->state;
|
||||
GST_DEBUG ("got stream state %d", state);
|
||||
|
||||
switch (state) {
|
||||
case PW_STREAM_STATE_UNCONNECTED:
|
||||
case PW_STREAM_STATE_CONNECTING:
|
||||
case PW_STREAM_STATE_CONFIGURE:
|
||||
case PW_STREAM_STATE_READY:
|
||||
case PW_STREAM_STATE_PAUSED:
|
||||
case PW_STREAM_STATE_STREAMING:
|
||||
break;
|
||||
case PW_STREAM_STATE_ERROR:
|
||||
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
|
||||
("stream error: %s", stream->error), (NULL));
|
||||
break;
|
||||
}
|
||||
pw_thread_loop_signal (pwsink->main_loop, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
on_format_changed (struct pw_listener *listener,
|
||||
struct pw_stream *stream,
|
||||
struct spa_format *format)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_format_changed);
|
||||
|
||||
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;
|
||||
gboolean res = FALSE;
|
||||
|
||||
pwsink = GST_PIPEWIRE_SINK (bsink);
|
||||
|
||||
possible = gst_caps_to_format_all (caps, pwsink->remote->core->type.map);
|
||||
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
state = pwsink->stream->state;
|
||||
|
||||
if (state == PW_STREAM_STATE_ERROR)
|
||||
goto start_error;
|
||||
|
||||
if (state == PW_STREAM_STATE_UNCONNECTED) {
|
||||
enum pw_stream_flags flags = 0;
|
||||
|
||||
if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE)
|
||||
flags |= PW_STREAM_FLAG_AUTOCONNECT;
|
||||
|
||||
pw_stream_connect (pwsink->stream,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
PW_STREAM_MODE_BUFFER,
|
||||
pwsink->path,
|
||||
flags,
|
||||
possible->len,
|
||||
(const struct spa_format **) possible->pdata);
|
||||
|
||||
while (TRUE) {
|
||||
state = pwsink->stream->state;
|
||||
|
||||
if (state == PW_STREAM_STATE_READY)
|
||||
break;
|
||||
|
||||
if (state == PW_STREAM_STATE_ERROR)
|
||||
goto start_error;
|
||||
|
||||
pw_thread_loop_wait (pwsink->main_loop);
|
||||
}
|
||||
}
|
||||
res = TRUE;
|
||||
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
pwsink->negotiated = res;
|
||||
|
||||
return res;
|
||||
|
||||
start_error:
|
||||
{
|
||||
GST_ERROR ("could not start stream");
|
||||
pw_thread_loop_unlock (pwsink->main_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;
|
||||
|
||||
pwsink = GST_PIPEWIRE_SINK (bsink);
|
||||
|
||||
if (!pwsink->negotiated)
|
||||
goto not_negotiated;
|
||||
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
if (pwsink->stream->state != PW_STREAM_STATE_STREAMING)
|
||||
goto done;
|
||||
|
||||
if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool)) {
|
||||
GstBuffer *b = NULL;
|
||||
GstMapInfo info = { 0, };
|
||||
|
||||
if (!gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool)))
|
||||
gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->pool), TRUE);
|
||||
|
||||
if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->pool), &b, NULL)) != GST_FLOW_OK)
|
||||
goto done;
|
||||
|
||||
gst_buffer_map (b, &info, GST_MAP_WRITE);
|
||||
gst_buffer_extract (buffer, 0, info.data, info.size);
|
||||
gst_buffer_unmap (b, &info);
|
||||
gst_buffer_resize (b, 0, gst_buffer_get_size (buffer));
|
||||
buffer = b;
|
||||
} else {
|
||||
gst_buffer_ref (buffer);
|
||||
}
|
||||
|
||||
GST_DEBUG ("push buffer in queue");
|
||||
g_queue_push_tail (&pwsink->queue, buffer);
|
||||
|
||||
if (pwsink->need_ready && pwsink->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE)
|
||||
do_send_buffer (pwsink);
|
||||
|
||||
done:
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
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;
|
||||
|
||||
if (G_VALUE_HOLDS_STRING (value))
|
||||
pw_properties_set (properties,
|
||||
g_quark_to_string (field_id),
|
||||
g_value_get_string (value));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_sink_start (GstBaseSink * basesink)
|
||||
{
|
||||
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
|
||||
struct pw_properties *props;
|
||||
|
||||
pwsink->negotiated = FALSE;
|
||||
|
||||
if (pwsink->properties) {
|
||||
props = pw_properties_new (NULL, NULL);
|
||||
gst_structure_foreach (pwsink->properties, copy_properties, props);
|
||||
} else {
|
||||
props = NULL;
|
||||
}
|
||||
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
pwsink->stream = pw_stream_new (pwsink->remote, pwsink->client_name, props);
|
||||
pwsink->pool->stream = pwsink->stream;
|
||||
|
||||
pw_signal_add (&pwsink->stream->state_changed, &pwsink->stream_state_changed, on_state_changed);
|
||||
pw_signal_add (&pwsink->stream->format_changed, &pwsink->stream_format_changed, on_format_changed);
|
||||
pw_signal_add (&pwsink->stream->add_buffer, &pwsink->stream_add_buffer, on_add_buffer);
|
||||
pw_signal_add (&pwsink->stream->remove_buffer, &pwsink->stream_remove_buffer, on_remove_buffer);
|
||||
pw_signal_add (&pwsink->stream->new_buffer, &pwsink->stream_new_buffer, on_new_buffer);
|
||||
pw_signal_add (&pwsink->stream->need_buffer, &pwsink->stream_need_buffer, on_need_buffer);
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_sink_stop (GstBaseSink * basesink)
|
||||
{
|
||||
GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
|
||||
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
if (pwsink->stream) {
|
||||
pw_stream_disconnect (pwsink->stream);
|
||||
pw_stream_destroy (pwsink->stream);
|
||||
pwsink->stream = NULL;
|
||||
pwsink->pool->stream = NULL;
|
||||
}
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
pwsink->negotiated = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_remote_state_changed (struct pw_listener *listener,
|
||||
struct pw_remote *remote)
|
||||
{
|
||||
GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, remote_state_changed);
|
||||
enum pw_remote_state state;
|
||||
|
||||
state = remote->state;
|
||||
GST_DEBUG ("got remote state %d", state);
|
||||
|
||||
switch (state) {
|
||||
case PW_REMOTE_STATE_UNCONNECTED:
|
||||
case PW_REMOTE_STATE_CONNECTING:
|
||||
case PW_REMOTE_STATE_CONNECTED:
|
||||
break;
|
||||
case PW_REMOTE_STATE_ERROR:
|
||||
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
|
||||
("remote error: %s", remote->error), (NULL));
|
||||
break;
|
||||
}
|
||||
pw_thread_loop_signal (pwsink->main_loop, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_sink_open (GstPipeWireSink * pwsink)
|
||||
{
|
||||
if (pw_thread_loop_start (pwsink->main_loop) != SPA_RESULT_OK)
|
||||
goto mainloop_error;
|
||||
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
pwsink->remote = pw_remote_new (pwsink->core, NULL);
|
||||
|
||||
pw_signal_add (&pwsink->remote->state_changed, &pwsink->remote_state_changed, on_remote_state_changed);
|
||||
|
||||
pw_remote_connect (pwsink->remote);
|
||||
|
||||
while (TRUE) {
|
||||
enum pw_remote_state state = pwsink->remote->state;
|
||||
|
||||
if (state == PW_REMOTE_STATE_CONNECTED)
|
||||
break;
|
||||
|
||||
if (state == PW_REMOTE_STATE_ERROR)
|
||||
goto connect_error;
|
||||
|
||||
pw_thread_loop_wait (pwsink->main_loop);
|
||||
}
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
mainloop_error:
|
||||
{
|
||||
GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
|
||||
("Failed to start mainloop"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
connect_error:
|
||||
{
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_sink_close (GstPipeWireSink * pwsink)
|
||||
{
|
||||
pw_thread_loop_lock (pwsink->main_loop);
|
||||
if (pwsink->stream) {
|
||||
pw_stream_disconnect (pwsink->stream);
|
||||
}
|
||||
if (pwsink->remote) {
|
||||
pw_remote_disconnect (pwsink->remote);
|
||||
|
||||
while (TRUE) {
|
||||
enum pw_remote_state state = pwsink->remote->state;
|
||||
|
||||
if (state == PW_REMOTE_STATE_UNCONNECTED)
|
||||
break;
|
||||
|
||||
if (state == PW_REMOTE_STATE_ERROR)
|
||||
break;
|
||||
|
||||
pw_thread_loop_wait (pwsink->main_loop);
|
||||
}
|
||||
}
|
||||
pw_thread_loop_unlock (pwsink->main_loop);
|
||||
|
||||
pw_thread_loop_stop (pwsink->main_loop);
|
||||
|
||||
if (pwsink->stream) {
|
||||
pw_stream_destroy (pwsink->stream);
|
||||
pwsink->stream = NULL;
|
||||
}
|
||||
|
||||
if (pwsink->remote) {
|
||||
pw_remote_destroy (pwsink->remote);
|
||||
pwsink->remote = 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 recording */
|
||||
break;
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
/* stop recording ASAP by corking */
|
||||
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:
|
||||
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;
|
||||
}
|
||||
}
|
||||
114
src/gst/gstpipewiresink.h
Normal file
114
src/gst/gstpipewiresink.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PIPEWIRE_SINK_H__
|
||||
#define __GST_PIPEWIRE_SINK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstbasesink.h>
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <gst/gstpipewirepool.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_SINK \
|
||||
(gst_pipewire_sink_get_type())
|
||||
#define GST_PIPEWIRE_SINK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSink))
|
||||
#define GST_PIPEWIRE_SINK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSinkClass))
|
||||
#define GST_IS_PIPEWIRE_SINK(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SINK))
|
||||
#define GST_IS_PIPEWIRE_SINK_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SINK))
|
||||
#define GST_PIPEWIRE_SINK_CAST(obj) \
|
||||
((GstPipeWireSink *) (obj))
|
||||
|
||||
typedef struct _GstPipeWireSink GstPipeWireSink;
|
||||
typedef struct _GstPipeWireSinkClass GstPipeWireSinkClass;
|
||||
|
||||
|
||||
/**
|
||||
* GstPipeWireSinkMode:
|
||||
* @GST_PIPEWIRE_SINK_MODE_DEFAULT: the default mode as configured in the server
|
||||
* @GST_PIPEWIRE_SINK_MODE_RENDER: try to render the media
|
||||
* @GST_PIPEWIRE_SINK_MODE_PROVIDE: provide the media
|
||||
*
|
||||
* Different modes of operation.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GST_PIPEWIRE_SINK_MODE_DEFAULT,
|
||||
GST_PIPEWIRE_SINK_MODE_RENDER,
|
||||
GST_PIPEWIRE_SINK_MODE_PROVIDE,
|
||||
} GstPipeWireSinkMode;
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ())
|
||||
|
||||
/**
|
||||
* GstPipeWireSink:
|
||||
*
|
||||
* Opaque data structure.
|
||||
*/
|
||||
struct _GstPipeWireSink {
|
||||
GstBaseSink element;
|
||||
|
||||
/*< private >*/
|
||||
gchar *path;
|
||||
gchar *client_name;
|
||||
|
||||
/* video state */
|
||||
gboolean negotiated;
|
||||
|
||||
struct pw_loop *loop;
|
||||
struct pw_thread_loop *main_loop;
|
||||
|
||||
struct pw_core *core;
|
||||
struct pw_remote *remote;
|
||||
struct pw_listener remote_state_changed;
|
||||
|
||||
struct pw_stream *stream;
|
||||
struct pw_listener stream_state_changed;
|
||||
struct pw_listener stream_format_changed;
|
||||
struct pw_listener stream_add_buffer;
|
||||
struct pw_listener stream_remove_buffer;
|
||||
struct pw_listener stream_new_buffer;
|
||||
struct pw_listener stream_need_buffer;
|
||||
|
||||
GstAllocator *allocator;
|
||||
GstStructure *properties;
|
||||
GstPipeWireSinkMode mode;
|
||||
|
||||
GstPipeWirePool *pool;
|
||||
GHashTable *buf_ids;
|
||||
GQueue queue;
|
||||
guint need_ready;
|
||||
};
|
||||
|
||||
struct _GstPipeWireSinkClass {
|
||||
GstBaseSinkClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_sink_get_type (void);
|
||||
GType gst_pipewire_sink_mode_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PIPEWIRE_SINK_H__ */
|
||||
1154
src/gst/gstpipewiresrc.c
Normal file
1154
src/gst/gstpipewiresrc.c
Normal file
File diff suppressed because it is too large
Load diff
97
src/gst/gstpipewiresrc.h
Normal file
97
src/gst/gstpipewiresrc.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PIPEWIRE_SRC_H__
|
||||
#define __GST_PIPEWIRE_SRC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PIPEWIRE_SRC \
|
||||
(gst_pipewire_src_get_type())
|
||||
#define GST_PIPEWIRE_SRC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrc))
|
||||
#define GST_PIPEWIRE_SRC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrcClass))
|
||||
#define GST_IS_PIPEWIRE_SRC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SRC))
|
||||
#define GST_IS_PIPEWIRE_SRC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SRC))
|
||||
#define GST_PIPEWIRE_SRC_CAST(obj) \
|
||||
((GstPipeWireSrc *) (obj))
|
||||
|
||||
typedef struct _GstPipeWireSrc GstPipeWireSrc;
|
||||
typedef struct _GstPipeWireSrcClass GstPipeWireSrcClass;
|
||||
|
||||
/**
|
||||
* GstPipeWireSrc:
|
||||
*
|
||||
* Opaque data structure.
|
||||
*/
|
||||
struct _GstPipeWireSrc {
|
||||
GstPushSrc element;
|
||||
|
||||
/*< private >*/
|
||||
gchar *path;
|
||||
gchar *client_name;
|
||||
gboolean always_copy;
|
||||
|
||||
gboolean negotiated;
|
||||
gboolean flushing;
|
||||
gboolean started;
|
||||
|
||||
gboolean is_live;
|
||||
GstClockTime min_latency;
|
||||
GstClockTime max_latency;
|
||||
|
||||
struct pw_loop *loop;
|
||||
struct pw_thread_loop *main_loop;
|
||||
|
||||
struct pw_core *core;
|
||||
struct pw_remote *remote;
|
||||
struct pw_listener remote_state_changed;
|
||||
|
||||
struct pw_stream *stream;
|
||||
struct pw_listener stream_state_changed;
|
||||
struct pw_listener stream_format_changed;
|
||||
struct pw_listener stream_add_buffer;
|
||||
struct pw_listener stream_remove_buffer;
|
||||
struct pw_listener stream_new_buffer;
|
||||
|
||||
GstAllocator *fd_allocator;
|
||||
GstStructure *properties;
|
||||
|
||||
GHashTable *buf_ids;
|
||||
GQueue queue;
|
||||
GstClock *clock;
|
||||
};
|
||||
|
||||
struct _GstPipeWireSrcClass {
|
||||
GstPushSrcClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pipewire_src_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PIPEWIRE_SRC_H__ */
|
||||
31
src/gst/meson.build
Normal file
31
src/gst/meson.build
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
pipewire_gst_sources = [
|
||||
'gstpipewire.c',
|
||||
'gstpipewireclock.c',
|
||||
'gstpipewiredeviceprovider.c',
|
||||
'gstpipewireformat.c',
|
||||
'gstpipewirepool.c',
|
||||
'gstpipewiresink.c',
|
||||
'gstpipewiresrc.c',
|
||||
]
|
||||
|
||||
pipewire_gst_headers = [
|
||||
'gstpipewireclock.h',
|
||||
'gstpipewiredeviceprovider.h',
|
||||
'gstpipewireformat.h',
|
||||
'gstpipewirepool.h',
|
||||
'gstpipewiresink.h',
|
||||
'gstpipewiresrc.h',
|
||||
]
|
||||
|
||||
pipewire_gst_c_args = [
|
||||
'-DHAVE_CONFIG_H',
|
||||
]
|
||||
|
||||
pipewire_gst = shared_library('gstpipewire',
|
||||
pipewire_gst_sources,
|
||||
c_args : pipewire_gst_c_args,
|
||||
include_directories : [configinc, spa_inc],
|
||||
dependencies : [gobject_dep, glib_dep, gio_dep, gst_dep, pipewire_dep],
|
||||
install : true,
|
||||
install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue