From 933ebba43535d75a3274f519cbcaf70863a9464c Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Tue, 30 Mar 2021 14:40:53 +0100 Subject: debugutils: Add fakeaudiosink element This element can be useful for CI purposes on machines not running any system audio daemon. The element implements the GstStreamVolume interface. Part-of: --- docs/plugins/gst_plugins_cache.json | 333 ++++++++++++++++++++++++++++++++++++ gst/debugutils/debugutilsbad.c | 3 + gst/debugutils/gstfakeaudiosink.c | 262 ++++++++++++++++++++++++++++ gst/debugutils/gstfakeaudiosink.h | 63 +++++++ gst/debugutils/meson.build | 3 +- 5 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 gst/debugutils/gstfakeaudiosink.c create mode 100644 gst/debugutils/gstfakeaudiosink.h diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index 9a484b301..b24ff6f10 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -8462,6 +8462,339 @@ }, "rank": "none" }, + "fakeaudiosink": { + "author": "Philippe Normand ", + "description": "Fake audio renderer", + "hierarchy": [ + "GstFakeAudioSink", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy", + "GstStreamVolume" + ], + "klass": "Audio/Sink", + "long-name": "Fake Audio Sink", + "pad-templates": { + "sink": { + "caps": "audio/x-raw:\n format: { F64LE, F64BE, F32LE, F32BE, S32LE, S32BE, U32LE, U32BE, S24_32LE, S24_32BE, U24_32LE, U24_32BE, S24LE, S24BE, U24LE, U24BE, S20LE, S20BE, U20LE, U20BE, S18LE, S18BE, U18LE, U18BE, S16LE, S16BE, U16LE, U16BE, S8, U8 }\n rate: [ 1, 2147483647 ]\n channels: [ 1, 2147483647 ]\n", + "direction": "sink", + "presence": "always" + } + }, + "properties": { + "async": { + "blurb": "Go asynchronously to PAUSED", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "blocksize": { + "blurb": "Size in bytes to pull per buffer (0 = default)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "4096", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "can-activate-pull": { + "blurb": "Can activate in pull mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "can-activate-push": { + "blurb": "Can activate in push mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "drop-out-of-segment": { + "blurb": "Drop and don't render / hand off out-of-segment buffers", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "dump": { + "blurb": "Dump buffer contents to stdout", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "playing", + "readable": true, + "type": "gboolean", + "writable": true + }, + "enable-last-sample": { + "blurb": "Enable the last-sample property", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "last-message": { + "blurb": "The message describing current status", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": false + }, + "last-sample": { + "blurb": "The last sample received in the sink", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "mutable": "null", + "readable": true, + "type": "GstSample", + "writable": false + }, + "max-bitrate": { + "blurb": "The maximum bits per second to render (0 = disabled)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "max-lateness": { + "blurb": "Maximum number of nanoseconds that a buffer can be late before it is dropped (-1 unlimited)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "18446744073709551615", + "max": "9223372036854775807", + "min": "-1", + "mutable": "null", + "readable": true, + "type": "gint64", + "writable": true + }, + "mute": { + "blurb": "Mute the audio channel without changing the volume", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "num-buffers": { + "blurb": "Number of buffers to accept going EOS", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "-1", + "max": "2147483647", + "min": "-1", + "mutable": "null", + "readable": true, + "type": "gint", + "writable": true + }, + "processing-deadline": { + "blurb": "Maximum processing time for a buffer in nanoseconds", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "20000000", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "qos": { + "blurb": "Generate Quality-of-Service events upstream", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "render-delay": { + "blurb": "Additional render delay of the sink in nanoseconds", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "signal-handoffs": { + "blurb": "Send a signal before unreffing the buffer", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "silent": { + "blurb": "Don't produce last_message events", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "playing", + "readable": true, + "type": "gboolean", + "writable": true + }, + "state-error": { + "blurb": "Generate a state change error", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "none (0)", + "mutable": "null", + "readable": true, + "type": "GstFakeSinkStateError", + "writable": true + }, + "stats": { + "blurb": "Sink Statistics", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "application/x-gst-base-sink-stats, average-rate=(double)0, dropped=(guint64)0, rendered=(guint64)0;", + "mutable": "null", + "readable": true, + "type": "GstStructure", + "writable": false + }, + "sync": { + "blurb": "Sync on the clock", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "throttle-time": { + "blurb": "The time to keep between rendered buffers (0 = disabled)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "ts-offset": { + "blurb": "Timestamp offset in nanoseconds", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "9223372036854775807", + "min": "-9223372036854775808", + "mutable": "null", + "readable": true, + "type": "gint64", + "writable": true + }, + "volume": { + "blurb": "The audio volume, 1.0=100%%", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "1", + "max": "10", + "min": "0", + "mutable": "null", + "readable": true, + "type": "gdouble", + "writable": true + } + }, + "rank": "none" + }, "fakevideosink": { "author": "Nicolas Dufresne ", "description": "Fake video display that allows zero-copy", diff --git a/gst/debugutils/debugutilsbad.c b/gst/debugutils/debugutilsbad.c index 3774f7bb5..51682e9c8 100644 --- a/gst/debugutils/debugutilsbad.c +++ b/gst/debugutils/debugutilsbad.c @@ -30,6 +30,7 @@ GType gst_compare_get_type (void); GType gst_debug_spy_get_type (void); GType gst_error_ignore_get_type (void); GType gst_watchdog_get_type (void); +GType gst_fake_audio_sink_get_type (void); GType gst_fake_video_sink_get_type (void); GType gst_test_src_bin_get_type (void); GType gst_clock_select_get_type (void); @@ -51,6 +52,8 @@ plugin_init (GstPlugin * plugin) gst_watchdog_get_type ()); gst_element_register (plugin, "errorignore", GST_RANK_NONE, gst_error_ignore_get_type ()); + gst_element_register (plugin, "fakeaudiosink", GST_RANK_NONE, + gst_fake_audio_sink_get_type ()); gst_element_register (plugin, "fakevideosink", GST_RANK_NONE, gst_fake_video_sink_get_type ()); gst_element_register (plugin, "testsrcbin", GST_RANK_NONE, diff --git a/gst/debugutils/gstfakeaudiosink.c b/gst/debugutils/gstfakeaudiosink.c new file mode 100644 index 000000000..8f8c56bf8 --- /dev/null +++ b/gst/debugutils/gstfakeaudiosink.c @@ -0,0 +1,262 @@ +/* + * GStreamer + * Copyright (C) 2017 Collabora Inc. + * Copyright (C) 2021 Igalia S.L. + * Author: Philippe Normand + * + * 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-fakeaudiosink + * @title: fakeaudiosink + * + * This element is the same as fakesink but will pretend to act as an audio sink + * supporting the `GstStreamVolume` interface. This is useful for throughput + * testing while creating a new pipeline or for CI purposes on machines not + * running a real audio daemon. + * + * ## Example launch lines + * |[ + * gst-launch-1.0 audiotestsrc ! fakeaudiosink + * ]| + * + * Since: 1.20 + */ + +#include "gstfakeaudiosink.h" + +#include + +enum +{ + PROP_0, + PROP_VOLUME, + PROP_MUTE, + PROP_LAST +}; + + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL))); + +G_DEFINE_TYPE_WITH_CODE (GstFakeAudioSink, gst_fake_audio_sink, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL);); + +/* TODO complete the types and make this an utility */ +static void +gst_fake_audio_sink_proxy_properties (GstFakeAudioSink * self, + GstElement * child) +{ + static gsize initialized = 0; + + if (g_once_init_enter (&initialized)) { + GObjectClass *object_class; + GParamSpec **properties; + guint n_properties, i; + + object_class = G_OBJECT_CLASS (GST_FAKE_AUDIO_SINK_GET_CLASS (self)); + properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (child), + &n_properties); + + for (i = 0; i < n_properties; i++) { + guint property_id = i + PROP_LAST; + + if (properties[i]->owner_type != G_OBJECT_TYPE (child) && + properties[i]->owner_type != GST_TYPE_BASE_SINK) + continue; + + if (G_IS_PARAM_SPEC_BOOLEAN (properties[i])) { + GParamSpecBoolean *prop = G_PARAM_SPEC_BOOLEAN (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_boolean (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->default_value, properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_INT (properties[i])) { + GParamSpecInt *prop = G_PARAM_SPEC_INT (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_int (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->minimum, prop->maximum, prop->default_value, + properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_UINT (properties[i])) { + GParamSpecUInt *prop = G_PARAM_SPEC_UINT (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_uint (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->minimum, prop->maximum, prop->default_value, + properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_INT64 (properties[i])) { + GParamSpecInt64 *prop = G_PARAM_SPEC_INT64 (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_int64 (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->minimum, prop->maximum, prop->default_value, + properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_UINT64 (properties[i])) { + GParamSpecUInt64 *prop = G_PARAM_SPEC_UINT64 (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_uint64 (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->minimum, prop->maximum, prop->default_value, + properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_ENUM (properties[i])) { + GParamSpecEnum *prop = G_PARAM_SPEC_ENUM (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_enum (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + properties[i]->value_type, prop->default_value, + properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_STRING (properties[i])) { + GParamSpecString *prop = G_PARAM_SPEC_STRING (properties[i]); + g_object_class_install_property (object_class, property_id, + g_param_spec_string (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + prop->default_value, properties[i]->flags)); + } else if (G_IS_PARAM_SPEC_BOXED (properties[i])) { + g_object_class_install_property (object_class, property_id, + g_param_spec_boxed (g_param_spec_get_name (properties[i]), + g_param_spec_get_nick (properties[i]), + g_param_spec_get_blurb (properties[i]), + properties[i]->value_type, properties[i]->flags)); + } + } + + g_free (properties); + g_once_init_leave (&initialized, 1); + } +} + +static void +gst_fake_audio_sink_init (GstFakeAudioSink * self) +{ + GstElement *child; + GstPadTemplate *template = gst_static_pad_template_get (&sink_factory); + + self->volume = 1.0; + self->mute = FALSE; + + child = gst_element_factory_make ("fakesink", "sink"); + + if (child) { + GstPad *sink_pad = gst_element_get_static_pad (child, "sink"); + GstPad *ghost_pad; + + /* mimic GstAudioSink base class */ + g_object_set (child, "qos", TRUE, "sync", TRUE, NULL); + + gst_bin_add (GST_BIN_CAST (self), child); + + ghost_pad = gst_ghost_pad_new_from_template ("sink", sink_pad, template); + gst_object_unref (template); + gst_element_add_pad (GST_ELEMENT_CAST (self), ghost_pad); + gst_object_unref (sink_pad); + + self->child = child; + + gst_fake_audio_sink_proxy_properties (self, child); + } else { + g_warning ("Check your GStreamer installation, " + "core element 'fakesink' is missing."); + } +} + +static void +gst_fake_audio_sink_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstFakeAudioSink *self = GST_FAKE_AUDIO_SINK (object); + + switch (property_id) { + case PROP_VOLUME: + g_value_set_double (value, self->volume); + break; + case PROP_MUTE: + g_value_set_boolean (value, self->mute); + break; + default: + g_object_get_property (G_OBJECT (self->child), pspec->name, value); + break; + } +} + +static void +gst_fake_audio_sink_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstFakeAudioSink *self = GST_FAKE_AUDIO_SINK (object); + + switch (property_id) { + case PROP_VOLUME: + self->volume = g_value_get_double (value); + break; + case PROP_MUTE: + self->mute = g_value_get_boolean (value); + break; + default: + g_object_set_property (G_OBJECT (self->child), pspec->name, value); + break; + } +} + +static void +gst_fake_audio_sink_class_init (GstFakeAudioSinkClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gst_fake_audio_sink_get_property; + object_class->set_property = gst_fake_audio_sink_set_property; + + + /** + * GstFakeAudioSink:volume + * + * Control the audio volume + * + * Since: 1.20 + */ + g_object_class_install_property (object_class, PROP_VOLUME, + g_param_spec_double ("volume", "Volume", "The audio volume, 1.0=100%", + 0, 10, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstFakeAudioSink:mute + * + * Control the mute state + * + * Since: 1.20 + */ + g_object_class_install_property (object_class, PROP_MUTE, + g_param_spec_boolean ("mute", "Mute", + "Mute the audio channel without changing the volume", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + + gst_element_class_add_static_pad_template (element_class, &sink_factory); + gst_element_class_set_static_metadata (element_class, "Fake Audio Sink", + "Audio/Sink", "Fake audio renderer", + "Philippe Normand "); +} diff --git a/gst/debugutils/gstfakeaudiosink.h b/gst/debugutils/gstfakeaudiosink.h new file mode 100644 index 000000000..8fd70d911 --- /dev/null +++ b/gst/debugutils/gstfakeaudiosink.h @@ -0,0 +1,63 @@ +/* + * GStreamer + * Copyright (C) 2017 Collabora Inc. + * Copyright (C) 2021 Igalia S.L. + * Author: Philippe Normand + * + * 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_FAKE_AUDIO_SINK_H_ +#define _GST_FAKE_AUDIO_SINK_H_ + +#include + +#define GST_TYPE_FAKE_AUDIO_SINK \ + (gst_fake_audio_sink_get_type()) +#define GST_FAKE_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FAKE_AUDIO_SINK, GstFakeAudioSink)) +#define GST_FAKE_AUDIO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FAKE_AUDIO_SINK, GstFakeAudioSinkClass)) +#define GST_FAKE_AUDIO_SINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_FAKE_AUDIO_SINK, GstFakeAudioSinkClass)) +#define GST_IS_FAKE_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FAKE_AUDIO_SINK)) +#define GST_IS_FAKE_AUDIO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FAKE_AUDIO_SINK)) + +G_BEGIN_DECLS + +typedef struct _GstFakeAudioSink GstFakeAudioSink; +typedef struct _GstFakeAudioSinkClass GstFakeAudioSinkClass; + +struct _GstFakeAudioSink +{ + GstBin parent; + GstElement *child; + gdouble volume; + gboolean mute; +}; + +struct _GstFakeAudioSinkClass +{ + GstBinClass parent; +}; + +GType gst_fake_audio_sink_get_type (void); + +G_END_DECLS + +#endif diff --git a/gst/debugutils/meson.build b/gst/debugutils/meson.build index c863556fe..d05161195 100644 --- a/gst/debugutils/meson.build +++ b/gst/debugutils/meson.build @@ -6,6 +6,7 @@ debugutilsbad_sources = [ 'gstchecksumsink.c', 'gstchopmydata.c', 'gstcompare.c', + 'gstfakeaudiosink.c', 'gstfakevideosink.c', 'gstwatchdog.c', 'gsttestsrcbin.c', @@ -16,7 +17,7 @@ gstdebugutilsbad = library('gstdebugutilsbad', debugutilsbad_sources, c_args : gst_plugins_bad_args, include_directories : [configinc], - dependencies : [gstbase_dep, gstvideo_dep, gstnet_dep], + dependencies : [gstbase_dep, gstvideo_dep, gstnet_dep, gstaudio_dep], install : true, install_dir : plugins_install_dir, ) -- cgit v1.2.1