Created
May 22, 2020 03:09
-
-
Save haydenmc/63638f5bdc3eac73e659e976c6efd029 to your computer and use it in GitHub Desktop.
GStreamer Mixer FTL Plugin Patch for 1.14.5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From b08b81705e30ce81c3747f5a424461cc297a8510 Mon Sep 17 00:00:00 2001 | |
From: "Jan Alexander Steffens (heftig)" <[email protected]> | |
Date: Thu, 2 May 2019 16:26:51 +0200 | |
Subject: [PATCH] WIP: Add plugin for Microsoft Mixer's FTL protocol | |
We have handle the failure of the FTL library initialization somewhere | |
else. | |
--- | |
ext/ftl/gstftl.c | 81 +++++ | |
ext/ftl/gstftlaudiosink.c | 116 +++++++ | |
ext/ftl/gstftlaudiosink.h | 37 +++ | |
ext/ftl/gstftlenums.c | 148 +++++++++ | |
ext/ftl/gstftlenums.h | 41 +++ | |
ext/ftl/gstftlsink.c | 628 ++++++++++++++++++++++++++++++++++++++ | |
ext/ftl/gstftlsink.h | 40 +++ | |
ext/ftl/gstftlvideosink.c | 189 ++++++++++++ | |
ext/ftl/gstftlvideosink.h | 37 +++ | |
ext/ftl/meson.build | 22 ++ | |
ext/meson.build | 1 + | |
meson_options.txt | 1 + | |
12 files changed, 1341 insertions(+) | |
create mode 100644 ext/ftl/gstftl.c | |
create mode 100644 ext/ftl/gstftlaudiosink.c | |
create mode 100644 ext/ftl/gstftlaudiosink.h | |
create mode 100644 ext/ftl/gstftlenums.c | |
create mode 100644 ext/ftl/gstftlenums.h | |
create mode 100644 ext/ftl/gstftlsink.c | |
create mode 100644 ext/ftl/gstftlsink.h | |
create mode 100644 ext/ftl/gstftlvideosink.c | |
create mode 100644 ext/ftl/gstftlvideosink.h | |
create mode 100644 ext/ftl/meson.build | |
diff --git a/ext/ftl/gstftl.c b/ext/ftl/gstftl.c | |
new file mode 100644 | |
index 000000000..adccb2593 | |
--- /dev/null | |
+++ b/ext/ftl/gstftl.c | |
@@ -0,0 +1,81 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+/** | |
+ * SECTION:ftl-plugin | |
+ * | |
+ * Sink for Microsoft Mixer's FTL protocol | |
+ * | |
+ * <refsect2> | |
+ * <title>Example launch line</title> | |
+ * |[ | |
+ * gst-launch-1.0 videotestsrc ! timeoverlay ! | |
+video/x-raw,width=1920,height=1080,framerate=30/1 ! x264enc bframes=0 b-adapt=0 | |
+key-int-max=30 speed-preset=superfast tune=zerolatency bitrate=2800 ! tee name=t | |
+t. ! queue ! h264parse ! avdec_h264 ! videoconvert ! xvimagesink t. ! queue ! | |
+f.videosink audiotestsrc ! opusenc ! f.audiosink ftlsink name=f | |
+stream-key=<your-mixer-key> | |
+ * ]| | |
+ * | |
+ * This pipeline sends audio and video to Microsoft Mixer. It also | |
+ * renders video in your local host, so you can visualy compare | |
+ * the low latency of FTL. | |
+ * | |
+ * </refsect2> | |
+ */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include <config.h> | |
+#endif | |
+ | |
+#include "gstftlsink.h" | |
+ | |
+GST_DEBUG_CATEGORY_STATIC (gst_debug_ftl); | |
+#define GST_CAT_DEFAULT gst_debug_ftl | |
+ | |
+static gboolean | |
+plugin_init (GstPlugin * plugin) | |
+{ | |
+ ftl_status_t status_code; | |
+ | |
+ GST_DEBUG_CATEGORY_INIT (gst_debug_ftl, "ftl", 0, | |
+ "debug category for ftl plugin"); | |
+ | |
+ status_code = ftl_init (); | |
+ if (status_code != FTL_SUCCESS) { | |
+ GST_ERROR_OBJECT (plugin, "Failed to initialize FTL library: %s", | |
+ ftl_status_code_to_string (status_code)); | |
+ return FALSE; | |
+ } | |
+ | |
+ if (!gst_element_register (plugin, "ftlsink", | |
+ GST_RANK_NONE, GST_TYPE_FTL_SINK)) { | |
+ return FALSE; | |
+ } | |
+ | |
+ return TRUE; | |
+} | |
+ | |
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, ftl, | |
+ "Plugin to send audio and video using Microsoft Mixer's FTL protocol", | |
+ plugin_init, VERSION, "MIT/X11", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) | |
diff --git a/ext/ftl/gstftlaudiosink.c b/ext/ftl/gstftlaudiosink.c | |
new file mode 100644 | |
index 000000000..377e09a92 | |
--- /dev/null | |
+++ b/ext/ftl/gstftlaudiosink.c | |
@@ -0,0 +1,116 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include "gstftlaudiosink.h" | |
+ | |
+#include "gstftlsink.h" | |
+ | |
+GST_DEBUG_CATEGORY_STATIC (gst_ftl_audio_sink_debug_category); | |
+#define GST_CAT_DEFAULT gst_ftl_audio_sink_debug_category | |
+ | |
+struct _GstFtlAudioSink | |
+{ | |
+ GstBaseSink parent_instance; | |
+}; | |
+ | |
+static GstFlowReturn gst_ftl_audio_sink_render (GstBaseSink * sink, | |
+ GstBuffer * buffer); | |
+ | |
+/* pad templates */ | |
+ | |
+static GstStaticPadTemplate gst_ftl_audio_sink_template = | |
+GST_STATIC_PAD_TEMPLATE ("sink", | |
+ GST_PAD_SINK, | |
+ GST_PAD_ALWAYS, | |
+ GST_STATIC_CAPS (GST_FTL_AUDIO_SINK_CAPS) | |
+ ); | |
+ | |
+/* class initialization */ | |
+ | |
+G_DEFINE_TYPE (GstFtlAudioSink, gst_ftl_audio_sink, GST_TYPE_BASE_SINK); | |
+ | |
+static void | |
+gst_ftl_audio_sink_class_init (GstFtlAudioSinkClass * klass) | |
+{ | |
+ GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass); | |
+ | |
+ GST_DEBUG_CATEGORY_INIT (gst_ftl_audio_sink_debug_category, "ftlaudiosink", 0, | |
+ "debug category for ftlaudiosink element"); | |
+ | |
+ gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), | |
+ "FTL audio sink", "Sink", "Internal audio sink of ftlsink", | |
+ "Make.TV, Inc. <[email protected]>"); | |
+ | |
+ gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), | |
+ &gst_ftl_audio_sink_template); | |
+ | |
+ base_sink_class->render = GST_DEBUG_FUNCPTR (gst_ftl_audio_sink_render); | |
+} | |
+ | |
+static void | |
+gst_ftl_audio_sink_init (GstFtlAudioSink * self) | |
+{ | |
+} | |
+ | |
+static GstFlowReturn | |
+gst_ftl_audio_sink_render (GstBaseSink * sink, GstBuffer * buffer) | |
+{ | |
+ GstFtlAudioSink *self = GST_FTL_AUDIO_SINK (sink); | |
+ GstFtlSink *parent = GST_FTL_SINK (GST_OBJECT_PARENT (self)); | |
+ GstClockTime time; | |
+ GstMapInfo map; | |
+ gint bytes_sent; | |
+ | |
+ if (!gst_ftl_sink_connect (parent)) { | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ time = GST_BUFFER_DTS_OR_PTS (buffer); | |
+ if (!GST_CLOCK_TIME_IS_VALID (time)) { | |
+ GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Got buffer without timestamp"), | |
+ ("%" GST_PTR_FORMAT, buffer)); | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ time = gst_segment_to_running_time (&sink->segment, GST_FORMAT_TIME, time); | |
+ | |
+ if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) { | |
+ GST_ERROR_OBJECT (self, "Failed to map %" GST_PTR_FORMAT, buffer); | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ GST_LOG_OBJECT (self, "sending %" G_GSIZE_FORMAT " bytes at %" | |
+ GST_TIME_FORMAT, map.size, GST_TIME_ARGS (time)); | |
+ | |
+ bytes_sent = ftl_ingest_send_media_dts (gst_ftl_sink_get_handle (parent), | |
+ FTL_AUDIO_DATA, GST_TIME_AS_USECONDS (time), map.data, map.size, 1); | |
+ | |
+ gst_buffer_unmap (buffer, &map); | |
+ | |
+ GST_LOG_OBJECT (self, "sent %d bytes", bytes_sent); | |
+ return GST_FLOW_OK; | |
+} | |
diff --git a/ext/ftl/gstftlaudiosink.h b/ext/ftl/gstftlaudiosink.h | |
new file mode 100644 | |
index 000000000..8a66a13cc | |
--- /dev/null | |
+++ b/ext/ftl/gstftlaudiosink.h | |
@@ -0,0 +1,37 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifndef _GST_FTL_AUDIO_SINK_H_ | |
+#define _GST_FTL_AUDIO_SINK_H_ | |
+ | |
+#include <gst/base/gstbasesink.h> | |
+ | |
+G_BEGIN_DECLS | |
+ | |
+#define GST_TYPE_FTL_AUDIO_SINK gst_ftl_audio_sink_get_type () | |
+G_DECLARE_FINAL_TYPE (GstFtlAudioSink, gst_ftl_audio_sink, GST, FTL_AUDIO_SINK, GstBaseSink) | |
+ | |
+#define GST_FTL_AUDIO_SINK_CAPS "audio/x-opus" | |
+ | |
+G_END_DECLS | |
+#endif | |
diff --git a/ext/ftl/gstftlenums.c b/ext/ftl/gstftlenums.c | |
new file mode 100644 | |
index 000000000..7612565f6 | |
--- /dev/null | |
+++ b/ext/ftl/gstftlenums.c | |
@@ -0,0 +1,148 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include "gstftlenums.h" | |
+ | |
+GstDebugLevel | |
+gst_ftl_log_severity_to_level (ftl_log_severity_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_LOG_CRITICAL: | |
+ return GST_LEVEL_ERROR; | |
+ case FTL_LOG_ERROR: | |
+ return GST_LEVEL_ERROR; | |
+ case FTL_LOG_WARN: | |
+ return GST_LEVEL_WARNING; | |
+ case FTL_LOG_INFO: | |
+ return GST_LEVEL_INFO; | |
+ case FTL_LOG_DEBUG: | |
+ return GST_LEVEL_DEBUG; | |
+ default: | |
+ return GST_LEVEL_LOG; | |
+ } | |
+} | |
+ | |
+const gchar * | |
+gst_ftl_status_type_get_nick (ftl_status_types_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_STATUS_NONE: | |
+ return "none"; | |
+ case FTL_STATUS_LOG: | |
+ return "log"; | |
+ case FTL_STATUS_EVENT: | |
+ return "event"; | |
+ case FTL_STATUS_VIDEO_PACKETS: | |
+ return "video-packets"; | |
+ case FTL_STATUS_VIDEO_PACKETS_INSTANT: | |
+ return "video-packets-instant"; | |
+ case FTL_STATUS_AUDIO_PACKETS: | |
+ return "audio-packets"; | |
+ case FTL_STATUS_VIDEO: | |
+ return "video"; | |
+ case FTL_STATUS_AUDIO: | |
+ return "audio"; | |
+ case FTL_STATUS_FRAMES_DROPPED: | |
+ return "frames-dropped"; | |
+ case FTL_STATUS_NETWORK: | |
+ return "network"; | |
+ case FTL_BITRATE_CHANGED: | |
+ return "bitrate-changed"; | |
+ default: | |
+ return "<unknown>"; | |
+ } | |
+} | |
+ | |
+const gchar * | |
+gst_ftl_status_event_type_get_nick (ftl_status_event_types_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_STATUS_EVENT_TYPE_UNKNOWN: | |
+ return "unknown"; | |
+ case FTL_STATUS_EVENT_TYPE_CONNECTED: | |
+ return "connected"; | |
+ case FTL_STATUS_EVENT_TYPE_DISCONNECTED: | |
+ return "disconnected"; | |
+ case FTL_STATUS_EVENT_TYPE_DESTROYED: | |
+ return "destroyed"; | |
+ case FTL_STATUS_EVENT_INGEST_ERROR_CODE: | |
+ return "ingest-error-code"; | |
+ default: | |
+ return "<unknown>"; | |
+ } | |
+} | |
+ | |
+const gchar * | |
+gst_ftl_status_event_reason_get_nick (ftl_status_event_reasons_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_STATUS_EVENT_REASON_NONE: | |
+ return "none"; | |
+ case FTL_STATUS_EVENT_REASON_NO_MEDIA: | |
+ return "no-media"; | |
+ case FTL_STATUS_EVENT_REASON_API_REQUEST: | |
+ return "api-request"; | |
+ case FTL_STATUS_EVENT_REASON_UNKNOWN: | |
+ return "unknown"; | |
+ default: | |
+ return "<unknown>"; | |
+ } | |
+} | |
+ | |
+const gchar * | |
+gst_ftl_bitrate_changed_type_get_nick (ftl_bitrate_changed_type_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_BITRATE_DECREASED: | |
+ return "decreased"; | |
+ case FTL_BITRATE_INCREASED: | |
+ return "increased"; | |
+ case FTL_BITRATE_STABILIZED: | |
+ return "stabilized"; | |
+ default: | |
+ return "<unknown>"; | |
+ } | |
+} | |
+ | |
+const gchar * | |
+gst_ftl_bitrate_changed_reason_get_nick (ftl_bitrate_changed_reason_t value) | |
+{ | |
+ switch (value) { | |
+ case FTL_BANDWIDTH_CONSTRAINED: | |
+ return "bandwidth-constrained"; | |
+ case FTL_UPGRADE_EXCESSIVE: | |
+ return "upgrade-excessive"; | |
+ case FTL_BANDWIDTH_AVAILABLE: | |
+ return "bandwidth-available"; | |
+ case FTL_STABILIZE_ON_LOWER_BITRATE: | |
+ return "stabilize-on-lower-bitrate"; | |
+ case FTL_STABILIZE_ON_ORIGINAL_BITRATE: | |
+ return "stabilize-on-original-bitrate"; | |
+ default: | |
+ return "<unknown>"; | |
+ } | |
+} | |
diff --git a/ext/ftl/gstftlenums.h b/ext/ftl/gstftlenums.h | |
new file mode 100644 | |
index 000000000..af646784a | |
--- /dev/null | |
+++ b/ext/ftl/gstftlenums.h | |
@@ -0,0 +1,41 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifndef _GST_FTL_ENUMS_H_ | |
+#define _GST_FTL_ENUMS_H_ | |
+ | |
+#include <gst/gst.h> | |
+#include "ftl.h" | |
+ | |
+G_BEGIN_DECLS | |
+ | |
+GstDebugLevel gst_ftl_log_severity_to_level (ftl_log_severity_t value); | |
+const gchar * gst_ftl_status_type_get_nick (ftl_status_types_t value); | |
+const gchar * gst_ftl_status_event_type_get_nick (ftl_status_event_types_t value); | |
+const gchar * gst_ftl_status_event_reason_get_nick (ftl_status_event_reasons_t value); | |
+const gchar * gst_ftl_bitrate_changed_type_get_nick (ftl_bitrate_changed_type_t value); | |
+const gchar * gst_ftl_bitrate_changed_reason_get_nick (ftl_bitrate_changed_reason_t value); | |
+ | |
+G_END_DECLS | |
+ | |
+#endif | |
diff --git a/ext/ftl/gstftlsink.c b/ext/ftl/gstftlsink.c | |
new file mode 100644 | |
index 000000000..e875be513 | |
--- /dev/null | |
+++ b/ext/ftl/gstftlsink.c | |
@@ -0,0 +1,628 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+/** | |
+ * SECTION:element-gstftlsink | |
+ * | |
+ * The ftlsink element is a wraper of FTL. | |
+ * | |
+ * <refsect2> | |
+ * <title>Example launch line</title> | |
+ * |[ | |
+ * gst-launch-1.0 videotestsrc ! video/x-raw,width=640,height=360,framerate=30/1 ! x264enc bframes=0 b-adapt=0 key-int-max=30 speed-preset=superfast tune=zerolatency option-string=scenecut=0 bitrate=2000 ! f.videosink audiotestsrc ! opusenc ! f.audiosink ftlsink name=f stream-key=<mixer-stream-key> | |
+ * ]| | |
+ * Simple pipeline to stream to mixer.com | |
+ * </refsect2> | |
+ */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include "gstftlsink.h" | |
+ | |
+#include "gstftlenums.h" | |
+#include "gstftlvideosink.h" | |
+#include "gstftlaudiosink.h" | |
+#include <inttypes.h> | |
+ | |
+#define STATUS_POLL_RATE_MS 200 | |
+ | |
+GST_DEBUG_CATEGORY_STATIC (gst_debug_ftl_sink); | |
+#define GST_CAT_DEFAULT gst_debug_ftl_sink | |
+ | |
+static GQuark ftl_stats_id; | |
+ | |
+struct _GstFtlSink | |
+{ | |
+ GstBin parent_instance; | |
+ | |
+ gchar *ingest_hostname; | |
+ gchar *stream_key; | |
+ gboolean sync; | |
+ guint peak_kbps; | |
+ | |
+ GstPad *audiosinkpad; | |
+ GstPad *videosinkpad; | |
+ | |
+ GstElement *ftlvideosink; | |
+ GstElement *ftlaudiosink; | |
+ | |
+ ftl_handle_t handle; | |
+ GMutex connect_lock; | |
+ | |
+ gboolean async_connect; | |
+ gboolean connected; | |
+ | |
+ GstTask *status_task; | |
+ GRecMutex status_lock; | |
+}; | |
+ | |
+static void gst_ftl_sink_finalize (GObject * object); | |
+static void gst_ftl_sink_set_property (GObject * object, guint prop_id, | |
+ const GValue * value, GParamSpec * param_spec); | |
+static void gst_ftl_sink_get_property (GObject * object, guint prop_id, | |
+ GValue * value, GParamSpec * param_spec); | |
+static GstStateChangeReturn gst_ftl_sink_change_state (GstElement * element, | |
+ GstStateChange transition); | |
+static void gst_ftl_sink_status_loop (gpointer user_data); | |
+static void gst_ftl_sink_handle_event (GstFtlSink * self, | |
+ ftl_status_event_msg_t * event); | |
+ | |
+enum | |
+{ | |
+ SIGNAL_GET_STATS, | |
+ N_SIGNALS, | |
+}; | |
+ | |
+enum | |
+{ | |
+ PROP_0, | |
+ PROP_ASYNC_CONNECT, | |
+ PROP_SYNC, | |
+ PROP_INGEST_HOSTNAME, | |
+ PROP_STREAM_KEY, | |
+ PROP_PEAK_KBPS, | |
+ N_PROPERTIES, | |
+}; | |
+ | |
+static guint G_GNUC_UNUSED signals[N_SIGNALS] = { 0, }; | |
+static GParamSpec *properties[N_PROPERTIES] = { NULL, }; | |
+ | |
+#define gst_ftl_sink_parent_class parent_class | |
+G_DEFINE_TYPE (GstFtlSink, gst_ftl_sink, GST_TYPE_BIN); | |
+ | |
+/* pad templates */ | |
+ | |
+static GstStaticPadTemplate gst_ftl_sink_audio_template = | |
+GST_STATIC_PAD_TEMPLATE ("audiosink", | |
+ GST_PAD_SINK, | |
+ GST_PAD_ALWAYS, | |
+ GST_STATIC_CAPS (GST_FTL_AUDIO_SINK_CAPS) | |
+ ); | |
+static GstStaticPadTemplate gst_ftl_sink_video_template = | |
+GST_STATIC_PAD_TEMPLATE ("videosink", | |
+ GST_PAD_SINK, | |
+ GST_PAD_ALWAYS, | |
+ GST_STATIC_CAPS (GST_FTL_VIDEO_SINK_CAPS) | |
+ ); | |
+ | |
+/* class initialization */ | |
+ | |
+static void | |
+gst_ftl_sink_class_init (GstFtlSinkClass * klass) | |
+{ | |
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass); | |
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass); | |
+ | |
+ GST_DEBUG_CATEGORY_INIT (gst_debug_ftl_sink, "ftlsink", 0, | |
+ "debug category for ftlsink element"); | |
+ | |
+ ftl_stats_id = g_quark_from_static_string ("ftl-stats"); | |
+ | |
+ gst_element_class_set_metadata (element_class, | |
+ "FTL Sink", "Sink", | |
+ "Send to Mixer using the Faster Than Light (FTL) streaming protocol", | |
+ "Make.TV, Inc. <[email protected]>"); | |
+ | |
+ gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), | |
+ &gst_ftl_sink_video_template); | |
+ gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), | |
+ &gst_ftl_sink_audio_template); | |
+ | |
+ gobject_class->set_property = gst_ftl_sink_set_property; | |
+ gobject_class->get_property = gst_ftl_sink_get_property; | |
+ gobject_class->finalize = gst_ftl_sink_finalize; | |
+ | |
+ properties[PROP_ASYNC_CONNECT] = g_param_spec_boolean ("async-connect", | |
+ "Async connect", "Connect on PAUSED, otherwise on first push", TRUE, | |
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | | |
+ GST_PARAM_MUTABLE_READY); | |
+ | |
+ properties[PROP_SYNC] = g_param_spec_boolean ("sync", "Sync", | |
+ "Sync on the clock", TRUE, | |
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); | |
+ | |
+ properties[PROP_INGEST_HOSTNAME] = g_param_spec_string ("ingest-hostname", | |
+ "Ingest hostname", "Hostname to connect to", "auto", | |
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); | |
+ | |
+ properties[PROP_STREAM_KEY] = g_param_spec_string ("stream-key", "Stream key", | |
+ "Stream key of target channel", NULL, | |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); | |
+ | |
+ properties[PROP_PEAK_KBPS] = g_param_spec_uint ("peak-kbps", "Peak bitrate", | |
+ "Bitrate in kbit/sec to pace outgoing packets", 0, G_MAXINT, 0, | |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); | |
+ | |
+ g_object_class_install_properties (gobject_class, N_PROPERTIES, properties); | |
+ | |
+ element_class->change_state = GST_DEBUG_FUNCPTR (gst_ftl_sink_change_state); | |
+ | |
+ GST_DEBUG_REGISTER_FUNCPTR (gst_ftl_sink_status_loop); | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_init (GstFtlSink * self) | |
+{ | |
+ GstPad *pad_audio, *pad_video; | |
+ | |
+ self->ftlvideosink = | |
+ g_object_new (GST_TYPE_FTL_VIDEO_SINK, "name", "videosink", NULL); | |
+ g_assert_nonnull (self->ftlvideosink); | |
+ self->ftlaudiosink = | |
+ g_object_new (GST_TYPE_FTL_AUDIO_SINK, "name", "audiosink", NULL); | |
+ g_assert_nonnull (self->ftlaudiosink); | |
+ | |
+ gst_bin_add_many (GST_BIN (self), self->ftlvideosink, self->ftlaudiosink, | |
+ NULL); | |
+ | |
+ pad_video = gst_element_get_static_pad (self->ftlvideosink, "sink"); | |
+ pad_audio = gst_element_get_static_pad (self->ftlaudiosink, "sink"); | |
+ | |
+ self->videosinkpad = | |
+ gst_ghost_pad_new_from_template ("videosink", pad_video, | |
+ gst_static_pad_template_get (&gst_ftl_sink_video_template)); | |
+ self->audiosinkpad = | |
+ gst_ghost_pad_new_from_template ("audiosink", pad_audio, | |
+ gst_static_pad_template_get (&gst_ftl_sink_audio_template)); | |
+ | |
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->videosinkpad); | |
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->audiosinkpad); | |
+ | |
+ g_object_bind_property (self, "sync", self->ftlvideosink, "sync", | |
+ G_BINDING_DEFAULT); | |
+ g_object_bind_property (self, "sync", self->ftlaudiosink, "sync", | |
+ G_BINDING_DEFAULT); | |
+ | |
+ self->status_task = gst_task_new (gst_ftl_sink_status_loop, self, NULL); | |
+ g_rec_mutex_init (&self->status_lock); | |
+ gst_task_set_lock (self->status_task, &self->status_lock); | |
+ | |
+ g_mutex_init (&self->connect_lock); | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_finalize (GObject * object) | |
+{ | |
+ GstFtlSink *self = GST_FTL_SINK (object); | |
+ | |
+ g_clear_object (&self->status_task); | |
+ g_rec_mutex_clear (&self->status_lock); | |
+ | |
+ g_mutex_clear (&self->connect_lock); | |
+ | |
+ G_OBJECT_CLASS (parent_class)->finalize (object); | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_set_property (GObject * object, guint prop_id, | |
+ const GValue * value, GParamSpec * pspec) | |
+{ | |
+ GstFtlSink *self = GST_FTL_SINK (object); | |
+ | |
+ GST_OBJECT_LOCK (self); | |
+ | |
+ switch (prop_id) { | |
+ case PROP_ASYNC_CONNECT: | |
+ self->async_connect = g_value_get_boolean (value); | |
+ break; | |
+ | |
+ case PROP_SYNC: | |
+ self->sync = g_value_get_boolean (value); | |
+ break; | |
+ | |
+ case PROP_INGEST_HOSTNAME: | |
+ g_free (self->ingest_hostname); | |
+ self->ingest_hostname = g_value_dup_string (value); | |
+ break; | |
+ | |
+ case PROP_STREAM_KEY: | |
+ g_free (self->stream_key); | |
+ self->stream_key = g_value_dup_string (value); | |
+ break; | |
+ | |
+ case PROP_PEAK_KBPS: | |
+ self->peak_kbps = g_value_get_uint (value); | |
+ break; | |
+ | |
+ default: | |
+ /* We don't have any other property... */ | |
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
+ break; | |
+ } | |
+ | |
+ GST_OBJECT_UNLOCK (self); | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_get_property (GObject * object, guint prop_id, | |
+ GValue * value, GParamSpec * pspec) | |
+{ | |
+ GstFtlSink *self = GST_FTL_SINK (object); | |
+ | |
+ GST_OBJECT_LOCK (self); | |
+ | |
+ switch (prop_id) { | |
+ case PROP_ASYNC_CONNECT: | |
+ g_value_set_boolean (value, self->async_connect); | |
+ break; | |
+ case PROP_SYNC: | |
+ g_value_set_boolean (value, self->sync); | |
+ break; | |
+ case PROP_INGEST_HOSTNAME: | |
+ g_value_set_string (value, self->ingest_hostname); | |
+ break; | |
+ | |
+ case PROP_STREAM_KEY: | |
+ g_value_set_string (value, self->stream_key); | |
+ break; | |
+ | |
+ case PROP_PEAK_KBPS: | |
+ g_value_set_uint (value, self->peak_kbps); | |
+ break; | |
+ | |
+ default: | |
+ /* We don't have any other property... */ | |
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
+ break; | |
+ } | |
+ | |
+ GST_OBJECT_UNLOCK (self); | |
+} | |
+ | |
+static ftl_status_t | |
+gst_ftl_sink_create_ingest (GstFtlSink * self) | |
+{ | |
+ ftl_ingest_params_t params; | |
+ ftl_status_t status_code; | |
+ | |
+ GST_OBJECT_LOCK (self); | |
+ params.ingest_hostname = self->ingest_hostname; | |
+ params.stream_key = self->stream_key; | |
+ params.video_codec = FTL_VIDEO_H264; | |
+ params.audio_codec = FTL_AUDIO_OPUS; | |
+ params.peak_kbps = self->peak_kbps; | |
+ params.fps_num = 0; | |
+ params.fps_den = 1; | |
+ params.vendor_name = GST_PACKAGE_NAME; | |
+ params.vendor_version = VERSION; | |
+ | |
+ status_code = ftl_ingest_create (&self->handle, ¶ms); | |
+ GST_OBJECT_UNLOCK (self); | |
+ | |
+ return status_code; | |
+} | |
+ | |
+gboolean | |
+gst_ftl_sink_connect (GstFtlSink * self) | |
+{ | |
+ gboolean connected; | |
+ | |
+ g_mutex_lock (&self->connect_lock); | |
+ | |
+ connected = self->connected; | |
+ if (!connected) { | |
+ ftl_status_t status_code = ftl_ingest_connect (&self->handle); | |
+ if (status_code == FTL_SUCCESS) | |
+ connected = self->connected = TRUE; | |
+ else | |
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, | |
+ ("Failed to connect to ingest: %s", | |
+ ftl_status_code_to_string (status_code)), ("status code %d", | |
+ status_code)); | |
+ } | |
+ | |
+ g_mutex_unlock (&self->connect_lock); | |
+ return connected; | |
+} | |
+ | |
+static gboolean | |
+gst_ftl_sink_disconnect (GstFtlSink * self) | |
+{ | |
+ gboolean connected; | |
+ | |
+ g_mutex_lock (&self->connect_lock); | |
+ | |
+ connected = self->connected; | |
+ if (connected) { | |
+ ftl_status_t status_code = ftl_ingest_disconnect (&self->handle); | |
+ if (status_code == FTL_SUCCESS) | |
+ connected = self->connected = FALSE; | |
+ else | |
+ GST_ERROR_OBJECT (self, "Failed to disconnect from ingest: %s", | |
+ ftl_status_code_to_string (status_code)); | |
+ } | |
+ | |
+ g_mutex_unlock (&self->connect_lock); | |
+ return !connected; | |
+} | |
+ | |
+static GstStateChangeReturn | |
+gst_ftl_sink_change_state (GstElement * element, GstStateChange transition) | |
+{ | |
+ GstFtlSink *self = GST_FTL_SINK (element); | |
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; | |
+ ftl_status_t status_code; | |
+ gboolean async; | |
+ | |
+ GST_DEBUG_OBJECT (self, "changing state: %s => %s", | |
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), | |
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); | |
+ | |
+ switch (transition) { | |
+ case GST_STATE_CHANGE_NULL_TO_READY: | |
+ status_code = gst_ftl_sink_create_ingest (self); | |
+ if (status_code != FTL_SUCCESS) { | |
+ GST_ERROR_OBJECT (self, "Failed to create ingest handle: %s\n", | |
+ ftl_status_code_to_string (status_code)); | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ break; | |
+ | |
+ case GST_STATE_CHANGE_READY_TO_PAUSED: | |
+ /* Start retrieving status messages */ | |
+ if (!gst_task_start (self->status_task)) { | |
+ GST_ERROR_OBJECT (self, "Failed to start status task"); | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ | |
+ GST_OBJECT_LOCK (self); | |
+ async = self->async_connect; | |
+ GST_OBJECT_UNLOCK (self); | |
+ | |
+ if (async && !gst_ftl_sink_connect (self)) { | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ break; | |
+ | |
+ default: | |
+ break; | |
+ } | |
+ | |
+ /* FIXME: ret gets GST_STATE_CHANGE_SUCCESS even when the stream key is | |
+ invalid. We might want to use GST_ERROR, or g_error to make it fatal? | |
+ */ | |
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); | |
+ if (ret == GST_STATE_CHANGE_FAILURE) | |
+ return ret; | |
+ | |
+ switch (transition) { | |
+ case GST_STATE_CHANGE_PAUSED_TO_READY: | |
+ if (!gst_ftl_sink_disconnect (self)) { | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ | |
+ if (!gst_task_join (self->status_task)) { | |
+ GST_ERROR_OBJECT (self, "Failed to join status task"); | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ break; | |
+ | |
+ case GST_STATE_CHANGE_READY_TO_NULL: | |
+ if ((status_code = ftl_ingest_destroy (&self->handle)) != FTL_SUCCESS) { | |
+ GST_ERROR_OBJECT (self, "Failed to destroy ingest handle: %s", | |
+ ftl_status_code_to_string (status_code)); | |
+ return GST_STATE_CHANGE_FAILURE; | |
+ } | |
+ | |
+ default: | |
+ break; | |
+ } | |
+ | |
+ return ret; | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_status_loop (gpointer user_data) | |
+{ | |
+ GstFtlSink *self = user_data; | |
+ ftl_status_t status_code; | |
+ ftl_status_msg_t message = { FTL_STATUS_NONE, }; | |
+ GstStructure *stats_message = NULL; | |
+ | |
+ GST_TRACE_OBJECT (self, "Getting status"); | |
+ status_code = ftl_ingest_get_status (&self->handle, &message, | |
+ STATUS_POLL_RATE_MS); | |
+ | |
+ while (status_code == FTL_SUCCESS) { | |
+ switch (message.type) { | |
+ case FTL_STATUS_LOG:{ | |
+ ftl_status_log_msg_t *msg = &message.msg.log; | |
+ GstDebugLevel level; | |
+ | |
+ level = gst_ftl_log_severity_to_level (msg->log_level); | |
+ g_strchomp (msg->string); | |
+ | |
+ GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, level, self, "%s", msg->string); | |
+ break; | |
+ } | |
+ | |
+ case FTL_STATUS_EVENT: | |
+ gst_ftl_sink_handle_event (self, &message.msg.event); | |
+ break; | |
+ | |
+ /* Really for both streams */ | |
+ case FTL_STATUS_VIDEO_PACKETS:{ | |
+ ftl_packet_stats_msg_t *msg = &message.msg.pkt_stats; | |
+ | |
+ GST_LOG_OBJECT (self, "Packet stats: period %" PRId64 " ms, %" PRId64 | |
+ " packets sent, %" PRId64 " NACK requests, %" PRId64 | |
+ " packets lost, %" PRId64 " packets recovered, %" PRId64 | |
+ " packets late", msg->period, msg->sent, msg->nack_reqs, msg->lost, | |
+ msg->recovered, msg->late); | |
+ | |
+ if (stats_message == NULL) | |
+ stats_message = gst_structure_new_id_empty (ftl_stats_id); | |
+ | |
+ gst_structure_set (stats_message, | |
+ "time-total", GST_TYPE_CLOCK_TIME, msg->period * GST_MSECOND, | |
+ "packets-sent", G_TYPE_INT64, msg->sent, | |
+ "nacks-received", G_TYPE_INT64, msg->nack_reqs, NULL); | |
+ break; | |
+ } | |
+ | |
+ /* Really for both streams */ | |
+ case FTL_STATUS_VIDEO_PACKETS_INSTANT:{ | |
+ ftl_packet_stats_instant_msg_t *msg = &message.msg.ipkt_stats; | |
+ | |
+ GST_LOG_OBJECT (self, "Instant packet stats: period %" PRId64 | |
+ " ms, RTT %d ms (min %d ms, max %d ms), delay %d ms (min %d" | |
+ " ms, max %d ms)", msg->period, msg->avg_rtt, msg->min_rtt, | |
+ msg->max_rtt, msg->avg_xmit_delay, msg->min_xmit_delay, | |
+ msg->max_xmit_delay); | |
+ | |
+ if (stats_message == NULL) | |
+ stats_message = gst_structure_new_id_empty (ftl_stats_id); | |
+ | |
+ gst_structure_set (stats_message, | |
+ "time-interval", GST_TYPE_CLOCK_TIME, msg->period * GST_MSECOND, | |
+ "rtt-min", G_TYPE_INT, msg->min_rtt, | |
+ "rtt-max", G_TYPE_INT, msg->max_rtt, | |
+ "rtt-avg", G_TYPE_INT, msg->avg_rtt, | |
+ "xmit-delay-min", G_TYPE_INT, msg->min_xmit_delay, | |
+ "xmit-delay-max", G_TYPE_INT, msg->max_xmit_delay, | |
+ "xmit-delay-avg", G_TYPE_INT, msg->avg_xmit_delay, NULL); | |
+ break; | |
+ } | |
+ | |
+ /* Really just video, this time */ | |
+ case FTL_STATUS_VIDEO:{ | |
+ ftl_video_frame_stats_msg_t *msg = &message.msg.video_stats; | |
+ | |
+ GST_LOG_OBJECT (self, "Video frame stats: period %" PRId64 | |
+ " ms, %" PRId64 " frames queued, %" PRId64 " frames sent, %" PRId64 | |
+ " bytes queued, %" PRId64 " bytes sent, %" PRId64 | |
+ " bandwidth throttles, queue fill level %d, max frame size %d", | |
+ msg->period, msg->frames_queued, msg->frames_sent, | |
+ msg->bytes_queued, msg->bytes_sent, msg->bw_throttling_count, | |
+ msg->queue_fullness, msg->max_frame_size); | |
+ | |
+ if (stats_message == NULL) | |
+ stats_message = gst_structure_new_id_empty (ftl_stats_id); | |
+ | |
+ gst_structure_set (stats_message, | |
+ "video-frames-queued", G_TYPE_INT64, msg->frames_queued, | |
+ "video-frames-sent", G_TYPE_INT64, msg->frames_sent, | |
+ "video-bytes-queued", G_TYPE_INT64, msg->bytes_queued, | |
+ "video-bytes-sent", G_TYPE_INT64, msg->bytes_sent, | |
+ "video-queue-level", G_TYPE_INT, msg->queue_fullness, | |
+ "video-max-frame-size", G_TYPE_INT, msg->max_frame_size, NULL); | |
+ break; | |
+ } | |
+ | |
+ case FTL_BITRATE_CHANGED:{ | |
+ ftl_bitrate_changed_msg_t *msg = &message.msg.bitrate_changed_msg; | |
+ gdouble nack_value = msg->nacks_to_frames_ratio; | |
+ const gchar *nack_unit = "nacks per frame"; | |
+ | |
+ if (msg->nacks_to_frames_ratio > 0 && msg->nacks_to_frames_ratio < 1) { | |
+ nack_value = 1 / nack_value; | |
+ nack_unit = "frames per nack"; | |
+ } | |
+ | |
+ GST_LOG_OBJECT (self, "Bitrate change: type %s, reason %s, %" PRIu64 | |
+ " bps current, %" PRIu64 " bps previous, %.3f %s, RTT %.3f ms," | |
+ " %" PRIu64 " frames dropped, queue fill level %.3f", | |
+ gst_ftl_bitrate_changed_type_get_nick (msg->bitrate_changed_type), | |
+ gst_ftl_bitrate_changed_reason_get_nick | |
+ (msg->bitrate_changed_reason), msg->current_encoding_bitrate, | |
+ msg->previous_encoding_bitrate, nack_value, nack_unit, msg->avg_rtt, | |
+ msg->avg_frames_dropped, msg->queue_fullness); | |
+ break; | |
+ } | |
+ | |
+ case FTL_STATUS_NONE: | |
+ case FTL_STATUS_AUDIO_PACKETS: | |
+ case FTL_STATUS_AUDIO: | |
+ case FTL_STATUS_FRAMES_DROPPED: | |
+ case FTL_STATUS_NETWORK: | |
+ default: | |
+ GST_WARNING_OBJECT (self, "Unhandled status message type: %s (%d)", | |
+ gst_ftl_status_type_get_nick (message.type), message.type); | |
+ break; | |
+ } | |
+ | |
+ GST_TRACE_OBJECT (self, "Getting more status"); | |
+ status_code = ftl_ingest_get_status (&self->handle, &message, 0); | |
+ } | |
+ | |
+ switch (status_code) { | |
+ case FTL_STATUS_TIMEOUT: | |
+ GST_TRACE_OBJECT (self, "Status queue empty"); | |
+ break; | |
+ default: | |
+ GST_WARNING_OBJECT (self, "Failed to get status: %s", | |
+ ftl_status_code_to_string (status_code)); | |
+ break; | |
+ } | |
+ | |
+ if (stats_message != NULL) | |
+ gst_element_post_message (GST_ELEMENT (self), | |
+ gst_message_new_element (GST_OBJECT (self), stats_message)); | |
+} | |
+ | |
+static void | |
+gst_ftl_sink_handle_event (GstFtlSink * self, ftl_status_event_msg_t * event) | |
+{ | |
+ GST_INFO_OBJECT (self, "Event: %s, reason %s: %s", | |
+ gst_ftl_status_event_type_get_nick (event->type), | |
+ gst_ftl_status_event_reason_get_nick (event->reason), | |
+ ftl_status_code_to_string (event->error_code)); | |
+ | |
+ if (event->type == FTL_STATUS_EVENT_TYPE_DISCONNECTED && | |
+ event->reason != FTL_STATUS_EVENT_REASON_API_REQUEST) | |
+ GST_ELEMENT_ERROR_WITH_DETAILS (self, RESOURCE, FAILED, | |
+ ("FTL connection unexpectedly terminated"), | |
+ ("Reason %s: %s", | |
+ gst_ftl_status_event_reason_get_nick (event->reason), | |
+ ftl_status_code_to_string (event->error_code)), | |
+ ("reason", G_TYPE_INT, event->reason, | |
+ "error-code", G_TYPE_INT, event->error_code, NULL)); | |
+} | |
+ | |
+ftl_handle_t * | |
+gst_ftl_sink_get_handle (GstFtlSink * self) | |
+{ | |
+ return &self->handle; | |
+} | |
diff --git a/ext/ftl/gstftlsink.h b/ext/ftl/gstftlsink.h | |
new file mode 100644 | |
index 000000000..9ee90fe12 | |
--- /dev/null | |
+++ b/ext/ftl/gstftlsink.h | |
@@ -0,0 +1,40 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifndef _GST_FTL_SINK_H_ | |
+#define _GST_FTL_SINK_H_ | |
+ | |
+#include <gst/gst.h> | |
+#include "ftl.h" | |
+ | |
+G_BEGIN_DECLS | |
+ | |
+#define GST_TYPE_FTL_SINK gst_ftl_sink_get_type () | |
+G_DECLARE_FINAL_TYPE (GstFtlSink, gst_ftl_sink, GST, FTL_SINK, GstBin) | |
+ | |
+ftl_handle_t * gst_ftl_sink_get_handle (GstFtlSink * sink); | |
+gboolean gst_ftl_sink_connect (GstFtlSink * self); | |
+ | |
+G_END_DECLS | |
+ | |
+#endif | |
diff --git a/ext/ftl/gstftlvideosink.c b/ext/ftl/gstftlvideosink.c | |
new file mode 100644 | |
index 000000000..462507545 | |
--- /dev/null | |
+++ b/ext/ftl/gstftlvideosink.c | |
@@ -0,0 +1,189 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include "gstftlvideosink.h" | |
+ | |
+#include "gstftlsink.h" | |
+ | |
+GST_DEBUG_CATEGORY_STATIC (gst_ftl_video_sink_debug_category); | |
+#define GST_CAT_DEFAULT gst_ftl_video_sink_debug_category | |
+ | |
+/* class header */ | |
+ | |
+struct _GstFtlVideoSink | |
+{ | |
+ GstBaseSink parent_instance; | |
+}; | |
+ | |
+/* prototypes */ | |
+ | |
+static GstFlowReturn gst_ftl_video_sink_render (GstBaseSink * sink, | |
+ GstBuffer * buffer); | |
+ | |
+enum | |
+{ | |
+ PROP_0 | |
+}; | |
+ | |
+/* pad templates */ | |
+ | |
+static GstStaticPadTemplate gst_ftl_video_sink_template = | |
+GST_STATIC_PAD_TEMPLATE ("sink", | |
+ GST_PAD_SINK, | |
+ GST_PAD_ALWAYS, | |
+ GST_STATIC_CAPS (GST_FTL_VIDEO_SINK_CAPS) | |
+ ); | |
+ | |
+/* class initialization */ | |
+ | |
+G_DEFINE_TYPE (GstFtlVideoSink, gst_ftl_video_sink, GST_TYPE_BASE_SINK); | |
+ | |
+static void | |
+gst_ftl_video_sink_class_init (GstFtlVideoSinkClass * klass) | |
+{ | |
+ GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass); | |
+ | |
+ GST_DEBUG_CATEGORY_INIT (gst_ftl_video_sink_debug_category, "ftlvideosink", 0, | |
+ "debug category for ftlvideosink element"); | |
+ | |
+ gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), | |
+ "FTL video sink", "Sink", "Internal video sink of ftlsink", | |
+ "Make.TV, Inc. <[email protected]>"); | |
+ | |
+ gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), | |
+ &gst_ftl_video_sink_template); | |
+ | |
+ base_sink_class->render = GST_DEBUG_FUNCPTR (gst_ftl_video_sink_render); | |
+} | |
+ | |
+static void | |
+gst_ftl_video_sink_init (GstFtlVideoSink * self) | |
+{ | |
+} | |
+ | |
+static guint8 * | |
+get_next_nalu (guint8 * input, gsize len, gsize * last_len) | |
+{ | |
+ guint32 start_code = 0xFFFFFFFF; | |
+ | |
+ for (gsize pos = 0; pos < len; pos++) { | |
+ start_code = (start_code << 8) | input[pos]; | |
+ | |
+ if ((start_code & 0xFFFFFF) == 1) { | |
+ if (last_len) | |
+ *last_len = pos - (start_code == 1 ? 3 : 2); | |
+ return input + pos + 1; | |
+ } | |
+ } | |
+ | |
+ if (last_len) | |
+ *last_len = len; | |
+ return NULL; | |
+} | |
+ | |
+static GstFlowReturn | |
+gst_ftl_video_sink_render (GstBaseSink * sink, GstBuffer * buffer) | |
+{ | |
+ GstFtlVideoSink *self = GST_FTL_VIDEO_SINK (sink); | |
+ GstFtlSink *parent = (GstFtlSink *) GST_OBJECT_PARENT (self); | |
+ GstClockTime time; | |
+ GstMapInfo map; | |
+ gint bytes_sent = 0; | |
+ guint num_nalus = 0; | |
+ guint8 *data, *end; | |
+ | |
+ if (!gst_ftl_sink_connect (parent)) { | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ time = GST_BUFFER_DTS (buffer); | |
+ if (!GST_CLOCK_TIME_IS_VALID (time)) { | |
+ GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Got buffer without DTS"), | |
+ ("%" GST_PTR_FORMAT, buffer)); | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ time = gst_segment_to_running_time (&sink->segment, GST_FORMAT_TIME, time); | |
+ | |
+ if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) { | |
+ GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to map buffer"), | |
+ ("%" GST_PTR_FORMAT, buffer)); | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ data = get_next_nalu (map.data, map.size, NULL); | |
+ end = map.data + map.size; | |
+ | |
+ while (data != NULL) { | |
+ gsize nalu_len; | |
+ guint8 *next = get_next_nalu (data, end - data, &nalu_len); | |
+ guint8 nalu_type = data[0] & 0x1f; | |
+ | |
+ num_nalus++; | |
+ | |
+ switch (nalu_type) { | |
+ case 0: | |
+ GST_ELEMENT_ERROR (self, STREAM, DECODE, ("Invalid NALU type 0"), | |
+ ("%" GST_PTR_FORMAT, buffer)); | |
+ gst_buffer_unmap (buffer, &map); | |
+ return GST_FLOW_ERROR; | |
+ case 9: /* AU delimiter */ | |
+ GST_LOG_OBJECT (self, "skipping AU delimiter (size %" G_GSIZE_FORMAT | |
+ ")", nalu_len); | |
+ break; | |
+ default: | |
+ { | |
+ gboolean last = (next == NULL); | |
+ gint sent = ftl_ingest_send_media_dts (gst_ftl_sink_get_handle (parent), | |
+ FTL_VIDEO_DATA, gst_util_uint64_scale_round (time, 1, GST_USECOND), | |
+ data, nalu_len, last); | |
+ | |
+ GST_LOG_OBJECT (self, | |
+ "sent %d bytes (NALU type %u, size %" G_GSIZE_FORMAT "%s) at %" | |
+ GST_TIME_FORMAT, sent, nalu_type, nalu_len, (last ? ", last" : ""), | |
+ GST_TIME_ARGS (time)); | |
+ | |
+ bytes_sent += sent; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ data = next; | |
+ } | |
+ | |
+ gst_buffer_unmap (buffer, &map); | |
+ | |
+ if (num_nalus == 0) { | |
+ GST_ELEMENT_ERROR (self, STREAM, DECODE, ("No NALU in buffer"), | |
+ ("%" GST_PTR_FORMAT, buffer)); | |
+ return GST_FLOW_ERROR; | |
+ } | |
+ | |
+ GST_LOG_OBJECT (self, "sent %u NALUs, %d bytes for %" GST_PTR_FORMAT, | |
+ num_nalus, bytes_sent, buffer); | |
+ return GST_FLOW_OK; | |
+} | |
diff --git a/ext/ftl/gstftlvideosink.h b/ext/ftl/gstftlvideosink.h | |
new file mode 100644 | |
index 000000000..9d94ac97b | |
--- /dev/null | |
+++ b/ext/ftl/gstftlvideosink.h | |
@@ -0,0 +1,37 @@ | |
+/* | |
+ * GStreamer | |
+ * Copyright (C) 2019 Make.TV, Inc. <[email protected]> | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to deal | |
+ * in the Software without restriction, including without limitation the rights | |
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
+ * copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+ * SOFTWARE. | |
+ */ | |
+ | |
+#ifndef _GST_FTL_VIDEO_SINK_H_ | |
+#define _GST_FTL_VIDEO_SINK_H_ | |
+ | |
+#include <gst/base/gstbasesink.h> | |
+ | |
+G_BEGIN_DECLS | |
+ | |
+#define GST_TYPE_FTL_VIDEO_SINK gst_ftl_video_sink_get_type () | |
+G_DECLARE_FINAL_TYPE (GstFtlVideoSink, gst_ftl_video_sink, GST, FTL_VIDEO_SINK, GstBaseSink) | |
+ | |
+#define GST_FTL_VIDEO_SINK_CAPS "video/x-h264, stream-format=byte-stream, alignment=au" | |
+ | |
+G_END_DECLS | |
+#endif | |
diff --git a/ext/ftl/meson.build b/ext/ftl/meson.build | |
new file mode 100644 | |
index 000000000..3344b905e | |
--- /dev/null | |
+++ b/ext/ftl/meson.build | |
@@ -0,0 +1,22 @@ | |
+ftl_sources = [ | |
+ 'gstftl.c', | |
+ 'gstftlaudiosink.c', | |
+ 'gstftlenums.c', | |
+ 'gstftlsink.c', | |
+ 'gstftlvideosink.c', | |
+] | |
+ | |
+ftl_dep = dependency('libftl', version : '>= 0.5.0', required : get_option('ftl')) | |
+ | |
+if ftl_dep.found() | |
+ gstftl = library('gstftl', | |
+ ftl_sources, | |
+ c_args : gst_plugins_bad_args, | |
+ include_directories : [configinc], | |
+ dependencies : [gstbase_dep, ftl_dep], | |
+ install : true, | |
+ install_dir : plugins_install_dir, | |
+ ) | |
+ pkgconfig.generate(gstftl, install_dir : plugins_pkgconfig_install_dir) | |
+ plugins += [gstftl] | |
+endif | |
diff --git a/ext/meson.build b/ext/meson.build | |
index cd193b7b3..47b30c5b0 100644 | |
--- a/ext/meson.build | |
+++ b/ext/meson.build | |
@@ -17,6 +17,7 @@ subdir('flite') | |
subdir('fluidsynth') | |
subdir('gl') | |
#subdir('gme') | |
+subdir('ftl') | |
subdir('gsm') | |
subdir('hls') | |
subdir('iqa') | |
diff --git a/meson_options.txt b/meson_options.txt | |
index 67c6904a6..373804ad6 100644 | |
--- a/meson_options.txt | |
+++ b/meson_options.txt | |
@@ -8,3 +8,4 @@ option('with-package-origin', type : 'string', value : 'Unknown package origin', | |
description : 'package origin URL to use in plugins') | |
option('enable_gst_player_tests', type: 'boolean', value: false, | |
description: 'Enable GstPlayer tests that need network access') | |
+option('ftl', type : 'feature', value : 'auto') | |
-- | |
2.17.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment