diff options
author | Saunier Thibault <saunierthibault@gmail.com> | 2015-12-03 12:32:05 +0100 |
---|---|---|
committer | Thibault Saunier <tsaunier@gnome.org> | 2019-08-28 13:02:13 +0000 |
commit | 7a66b16d976468fcf72c2d1398fd637bdb4e348c (patch) | |
tree | 6a2d09ec58ab6bd964135cca665814805bbcbfaa /gst-libs/gst/transcoder | |
parent | 87311d404ef75c08fc8417fc7fb41e17002e80f6 (diff) | |
download | gstreamer-plugins-bad-7a66b16d976468fcf72c2d1398fd637bdb4e348c.tar.gz |
Import GstTranscoder
Diffstat (limited to 'gst-libs/gst/transcoder')
-rw-r--r-- | gst-libs/gst/transcoder/gsttranscoder.c | 1609 | ||||
-rw-r--r-- | gst-libs/gst/transcoder/gsttranscoder.h | 141 | ||||
-rw-r--r-- | gst-libs/gst/transcoder/meson.build | 33 | ||||
-rw-r--r-- | gst-libs/gst/transcoder/transcoder-prelude.h | 36 |
4 files changed, 1819 insertions, 0 deletions
diff --git a/gst-libs/gst/transcoder/gsttranscoder.c b/gst-libs/gst/transcoder/gsttranscoder.c new file mode 100644 index 000000000..7abe3fa44 --- /dev/null +++ b/gst-libs/gst/transcoder/gsttranscoder.c @@ -0,0 +1,1609 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com> + * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org> + * + * 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:gsttranscoder + * @short_description: High level API to transcode media files + * from one format to any other format using the GStreamer framework. + * @symbols: + * - gst_transcoder_error_quark + */ + +#include "gsttranscoder.h" + +GST_DEBUG_CATEGORY_STATIC (gst_transcoder_debug); +#define GST_CAT_DEFAULT gst_transcoder_debug + +#define DEFAULT_URI NULL +#define DEFAULT_POSITION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE +#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100 +#define DEFAULT_AVOID_REENCODING FALSE + +GQuark +gst_transcoder_error_quark (void) +{ + static GQuark quark; + + if (!quark) + quark = g_quark_from_static_string ("gst-transcoder-error-quark"); + + return quark; +} + +enum +{ + PROP_0, + PROP_SIGNAL_DISPATCHER, + PROP_SRC_URI, + PROP_DEST_URI, + PROP_PROFILE, + PROP_POSITION, + PROP_DURATION, + PROP_PIPELINE, + PROP_POSITION_UPDATE_INTERVAL, + PROP_AVOID_REENCODING, + PROP_LAST +}; + +enum +{ + SIGNAL_POSITION_UPDATED, + SIGNAL_DURATION_CHANGED, + SIGNAL_DONE, + SIGNAL_ERROR, + SIGNAL_WARNING, + SIGNAL_LAST +}; + +struct _GstTranscoder +{ + GstObject parent; + + GstTranscoderSignalDispatcher *signal_dispatcher; + + GstEncodingProfile *profile; + gchar *source_uri; + gchar *dest_uri; + + GThread *thread; + GCond cond; + GMainContext *context; + GMainLoop *loop; + + GstElement *transcodebin; + GstBus *bus; + GstState target_state, current_state; + gboolean is_live, is_eos; + GSource *tick_source, *ready_timeout_source; + + guint position_update_interval_ms; + gint wanted_cpu_usage; + + GstClockTime last_duration; +}; + +struct _GstTranscoderClass +{ + GstObjectClass parent_class; +}; + +static void +gst_transcoder_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, void (*emitter) (gpointer data), gpointer data, + GDestroyNotify destroy); + +#define parent_class gst_transcoder_parent_class +G_DEFINE_TYPE (GstTranscoder, gst_transcoder, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_transcoder_dispose (GObject * object); +static void gst_transcoder_finalize (GObject * object); +static void gst_transcoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_transcoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_transcoder_constructed (GObject * object); + +static gpointer gst_transcoder_main (gpointer data); + +static gboolean gst_transcoder_set_position_update_interval_internal (gpointer + user_data); + + +/** + * gst_transcoder_set_cpu_usage: + * @self: The GstTranscoder to limit CPU usage on. + * @cpu_usage: The percentage of the CPU the process running the transcoder + * should try to use. It takes into account the number of cores available. + * + * Sets @cpu_usage as target percentage CPU usage of the process running the + * transcoding task. It will modulate the transcoding speed to reach that target + * usage. + */ +void +gst_transcoder_set_cpu_usage (GstTranscoder * self, gint cpu_usage) +{ + GST_OBJECT_LOCK (self); + self->wanted_cpu_usage = cpu_usage; + if (self->transcodebin) + g_object_set (self->transcodebin, "cpu-usage", cpu_usage, NULL); + GST_OBJECT_UNLOCK (self); +} + +static void +gst_transcoder_init (GstTranscoder * self) +{ + GST_TRACE_OBJECT (self, "Initializing"); + + self = gst_transcoder_get_instance_private (self); + + g_cond_init (&self->cond); + + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + self->wanted_cpu_usage = 100; + + self->position_update_interval_ms = DEFAULT_POSITION_UPDATE_INTERVAL_MS; + + GST_TRACE_OBJECT (self, "Initialized"); +} + +static void +gst_transcoder_class_init (GstTranscoderClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_transcoder_set_property; + gobject_class->get_property = gst_transcoder_get_property; + gobject_class->dispose = gst_transcoder_dispose; + gobject_class->finalize = gst_transcoder_finalize; + gobject_class->constructed = gst_transcoder_constructed; + + param_specs[PROP_SIGNAL_DISPATCHER] = + g_param_spec_object ("signal-dispatcher", + "Signal Dispatcher", "Dispatcher for the signals to e.g. event loops", + GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SRC_URI] = + g_param_spec_string ("src-uri", "URI", "Source URI", DEFAULT_URI, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_DEST_URI] = + g_param_spec_string ("dest-uri", "URI", "Source URI", DEFAULT_URI, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_PROFILE] = + g_param_spec_object ("profile", "Profile", + "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_POSITION] = + g_param_spec_uint64 ("position", "Position", "Current Position", + 0, G_MAXUINT64, DEFAULT_POSITION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "Duration", + 0, G_MAXUINT64, DEFAULT_DURATION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_PIPELINE] = + g_param_spec_object ("pipeline", "Pipeline", + "GStreamer pipeline that is used", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_POSITION_UPDATE_INTERVAL] = + g_param_spec_uint ("position-update-interval", "Position update interval", + "Interval in milliseconds between two position-updated signals." + "Pass 0 to stop updating the position.", + 0, 10000, DEFAULT_POSITION_UPDATE_INTERVAL_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GstTranscoder:avoid-reencoding: + * + * See #encodebin:avoid-reencoding + */ + param_specs[PROP_AVOID_REENCODING] = + g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", + "Whether to re-encode portions of compatible video streams that lay on segment boundaries", + DEFAULT_AVOID_REENCODING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + signals[SIGNAL_POSITION_UPDATED] = + g_signal_new ("position-updated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_DURATION_CHANGED] = + g_signal_new ("duration-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_DONE] = + g_signal_new ("done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); + + signals[SIGNAL_ERROR] = + g_signal_new ("error", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE); + + signals[SIGNAL_WARNING] = + g_signal_new ("warning", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE); +} + +static void +gst_transcoder_dispose (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Stopping main thread"); + + if (self->loop) { + g_main_loop_quit (self->loop); + + g_thread_join (self->thread); + self->thread = NULL; + + g_main_loop_unref (self->loop); + self->loop = NULL; + + g_main_context_unref (self->context); + self->context = NULL; + + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_transcoder_finalize (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Finalizing"); + + g_free (self->source_uri); + g_free (self->dest_uri); + if (self->signal_dispatcher) + g_object_unref (self->signal_dispatcher); + g_cond_clear (&self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_transcoder_constructed (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Constructed"); + + self->transcodebin = + gst_element_factory_make ("uritranscodebin", "uritranscodebin"); + + g_object_set (self->transcodebin, "source-uri", self->source_uri, + "dest-uri", self->dest_uri, "profile", self->profile, + "cpu-usage", self->wanted_cpu_usage, NULL); + + GST_OBJECT_LOCK (self); + self->thread = g_thread_new ("GstTranscoder", gst_transcoder_main, self); + while (!self->loop || !g_main_loop_is_running (self->loop)) + g_cond_wait (&self->cond, GST_OBJECT_GET_LOCK (self)); + GST_OBJECT_UNLOCK (self); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_transcoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + switch (prop_id) { + case PROP_SIGNAL_DISPATCHER: + self->signal_dispatcher = g_value_dup_object (value); + break; + case PROP_SRC_URI:{ + GST_OBJECT_LOCK (self); + g_free (self->source_uri); + self->source_uri = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set source_uri=%s", self->source_uri); + GST_OBJECT_UNLOCK (self); + break; + } + case PROP_DEST_URI:{ + GST_OBJECT_LOCK (self); + g_free (self->dest_uri); + self->dest_uri = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set dest_uri=%s", self->dest_uri); + GST_OBJECT_UNLOCK (self); + break; + } + case PROP_POSITION_UPDATE_INTERVAL: + GST_OBJECT_LOCK (self); + self->position_update_interval_ms = g_value_get_uint (value); + GST_DEBUG_OBJECT (self, "Set position update interval=%u ms", + g_value_get_uint (value)); + GST_OBJECT_UNLOCK (self); + + gst_transcoder_set_position_update_interval_internal (self); + break; + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + self->profile = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + g_object_set (self->transcodebin, "avoid-reencoding", + g_value_get_boolean (value), NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_transcoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + switch (prop_id) { + case PROP_SRC_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->source_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DEST_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->dest_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_POSITION:{ + gint64 position = 0; + + if (self->is_eos) + position = self->last_duration; + else + gst_element_query_position (self->transcodebin, GST_FORMAT_TIME, + &position); + g_value_set_uint64 (value, position); + GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_DURATION:{ + gint64 duration = 0; + + gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + &duration); + g_value_set_uint64 (value, duration); + GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_PIPELINE: + g_value_set_object (value, self->transcodebin); + break; + case PROP_POSITION_UPDATE_INTERVAL: + GST_OBJECT_LOCK (self); + g_value_set_uint (value, + gst_transcoder_get_position_update_interval (self)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->profile); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + { + gboolean avoid_reencoding; + + g_object_get (self->transcodebin, "avoid-reencoding", &avoid_reencoding, + NULL); + g_value_set_boolean (value, avoid_reencoding); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +main_loop_running_cb (gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_TRACE_OBJECT (self, "Main loop running now"); + + GST_OBJECT_LOCK (self); + g_cond_signal (&self->cond); + GST_OBJECT_UNLOCK (self); + + return G_SOURCE_REMOVE; +} + +typedef struct +{ + GstTranscoder *transcoder; + GstClockTime position; +} PositionUpdatedSignalData; + +static void +position_updated_dispatch (gpointer user_data) +{ + PositionUpdatedSignalData *data = user_data; + + if (data->transcoder->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->transcoder, signals[SIGNAL_POSITION_UPDATED], 0, + data->position); + g_object_notify_by_pspec (G_OBJECT (data->transcoder), + param_specs[PROP_POSITION]); + } +} + +static void +position_updated_signal_data_free (PositionUpdatedSignalData * data) +{ + g_object_unref (data->transcoder); + g_free (data); +} + +static gboolean +tick_cb (gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + gint64 position; + + if (self->target_state >= GST_STATE_PAUSED + && gst_element_query_position (self->transcodebin, GST_FORMAT_TIME, + &position)) { + GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_POSITION_UPDATED], 0, NULL, NULL, NULL) != 0) { + PositionUpdatedSignalData *data = g_new0 (PositionUpdatedSignalData, 1); + + data->transcoder = g_object_ref (self); + data->position = position; + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + position_updated_dispatch, data, + (GDestroyNotify) position_updated_signal_data_free); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +add_tick_source (GstTranscoder * self) +{ + if (self->tick_source) + return; + + if (!self->position_update_interval_ms) + return; + + self->tick_source = g_timeout_source_new (self->position_update_interval_ms); + g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL); + g_source_attach (self->tick_source, self->context); +} + +static void +remove_tick_source (GstTranscoder * self) +{ + if (!self->tick_source) + return; + + g_source_destroy (self->tick_source); + g_source_unref (self->tick_source); + self->tick_source = NULL; +} + +typedef struct +{ + GstTranscoder *transcoder; + GError *err; + GstStructure *details; +} IssueSignalData; + +static void +error_dispatch (gpointer user_data) +{ + IssueSignalData *data = user_data; + + g_signal_emit (data->transcoder, signals[SIGNAL_ERROR], 0, data->err, + data->details); +} + +static void +free_issue_signal_data (IssueSignalData * data) +{ + g_object_unref (data->transcoder); + if (data->details) + gst_structure_free (data->details); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_error (GstTranscoder * self, GError * err, const GstStructure * details) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_ERROR], 0, NULL, NULL, NULL) != 0) { + IssueSignalData *data = g_new0 (IssueSignalData, 1); + + data->transcoder = g_object_ref (self); + data->err = g_error_copy (err); + if (details) + data->details = gst_structure_copy (details); + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + error_dispatch, data, (GDestroyNotify) free_issue_signal_data); + } + + g_error_free (err); + + remove_tick_source (self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + self->is_live = FALSE; + self->is_eos = FALSE; + gst_element_set_state (self->transcodebin, GST_STATE_NULL); +} + +static void +dump_dot_file (GstTranscoder * self, const gchar * name) +{ + gchar *full_name; + + full_name = g_strdup_printf ("gst-transcoder.%p.%s", self, name); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->transcodebin), + GST_DEBUG_GRAPH_SHOW_VERBOSE, full_name); + + g_free (full_name); +} + +static void +warning_dispatch (gpointer user_data) +{ + IssueSignalData *data = user_data; + + g_signal_emit (data->transcoder, signals[SIGNAL_WARNING], 0, data->err, + data->details); +} + +static void +emit_warning (GstTranscoder * self, GError * err, const GstStructure * details) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_WARNING], 0, NULL, NULL, NULL) != 0) { + IssueSignalData *data = g_new0 (IssueSignalData, 1); + + data->transcoder = g_object_ref (self); + data->err = g_error_copy (err); + if (details) + data->details = gst_structure_copy (details); + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + warning_dispatch, data, (GDestroyNotify) free_issue_signal_data); + } + + g_error_free (err); +} + +static void +error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GError *err; + GstTranscoder *self = GST_TRANSCODER (user_data); + gchar *name, *debug, *message; + GstStructure *details = NULL; + + dump_dot_file (self, "error"); + + gst_message_parse_error (msg, &err, &debug); + gst_message_parse_error_details (msg, (const GstStructure **) &details); + + if (!details) + details = gst_structure_new_empty ("details"); + else + details = gst_structure_copy (details); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + gst_structure_set (details, "debug", G_TYPE_STRING, debug, + "msg-source-element-name", G_TYPE_STRING, "name", + "msg-source-type", G_TYPE_GTYPE, G_OBJECT_TYPE (msg->src), + "msg-error", G_TYPE_STRING, message, NULL); + emit_error (self, g_error_copy (err), details); + + gst_structure_free (details); + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (message); +} + +static void +warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GError *err, *transcoder_err; + gchar *name, *debug, *message, *full_message; + const GstStructure *details = NULL; + + dump_dot_file (self, "warning"); + + gst_message_parse_warning (msg, &err, &debug); + gst_message_parse_warning_details (msg, &details); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + if (debug) + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message, + err->message, debug); + else + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s", name, message, + err->message); + + GST_WARNING_OBJECT (self, "WARNING: from element %s: %s\n", name, + err->message); + if (debug != NULL) + GST_WARNING_OBJECT (self, "Additional debug info:\n%s\n", debug); + + transcoder_err = + g_error_new_literal (GST_TRANSCODER_ERROR, GST_TRANSCODER_ERROR_FAILED, + full_message); + emit_warning (self, transcoder_err, details); + + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (full_message); + g_free (message); +} + +static void +eos_dispatch (gpointer user_data) +{ + g_signal_emit (user_data, signals[SIGNAL_DONE], 0); +} + +static void +eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_DEBUG_OBJECT (self, "End of stream"); + + gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + (gint64 *) & self->last_duration); + tick_cb (self); + remove_tick_source (self); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DONE], 0, NULL, NULL, NULL) != 0) { + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref); + } + self->is_eos = TRUE; +} + +static void +clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Clock lost"); + if (self->target_state >= GST_STATE_PLAYING) { + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PAUSED); + if (state_ret != GST_STATE_CHANGE_FAILURE) + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING); + + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "Failed to handle clock loss"), + NULL); + } +} + +typedef struct +{ + GstTranscoder *transcoder; + GstClockTime duration; +} DurationChangedSignalData; + +static void +duration_changed_dispatch (gpointer user_data) +{ + DurationChangedSignalData *data = user_data; + + if (data->transcoder->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->transcoder, signals[SIGNAL_DURATION_CHANGED], 0, + data->duration); + g_object_notify_by_pspec (G_OBJECT (data->transcoder), + param_specs[PROP_DURATION]); + } +} + +static void +duration_changed_signal_data_free (DurationChangedSignalData * data) +{ + g_object_unref (data->transcoder); + g_free (data); +} + +static void +emit_duration_changed (GstTranscoder * self, GstClockTime duration) +{ + GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) { + DurationChangedSignalData *data = g_new0 (DurationChangedSignalData, 1); + + data->transcoder = g_object_ref (self); + data->duration = duration; + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + duration_changed_dispatch, data, + (GDestroyNotify) duration_changed_signal_data_free); + } +} + +static void +state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstState old_state, new_state, pending_state; + + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->transcodebin)) { + gchar *transition_name; + + GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state), + gst_element_state_get_name (pending_state)); + + transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + dump_dot_file (self, transition_name); + g_free (transition_name); + + self->current_state = new_state; + + if (new_state == GST_STATE_PLAYING + && pending_state == GST_STATE_VOID_PENDING) { + add_tick_source (self); + } + } +} + +static void +duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + gint64 duration; + + if (gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + &duration)) { + emit_duration_changed (self, duration); + } +} + +static void +latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_DEBUG_OBJECT (self, "Latency changed"); + + gst_bin_recalculate_latency (GST_BIN (self->transcodebin)); +} + +static void +request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstState state; + GstStateChangeReturn state_ret; + + gst_message_parse_request_state (msg, &state); + + GST_DEBUG_OBJECT (self, "State %s requested", + gst_element_state_get_name (state)); + + self->target_state = state; + state_ret = gst_element_set_state (self->transcodebin, state); + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, + "Failed to change to requested state %s", + gst_element_state_get_name (state)), NULL); +} + +static void +element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + const GstStructure *s; + + s = gst_message_get_structure (msg); + if (gst_structure_has_name (s, "redirect")) { + const gchar *new_location; + + new_location = gst_structure_get_string (s, "new-location"); + if (!new_location) { + const GValue *locations_list, *location_val; + guint i, size; + + locations_list = gst_structure_get_value (s, "locations"); + size = gst_value_list_get_size (locations_list); + for (i = 0; i < size; ++i) { + const GstStructure *location_s; + + location_val = gst_value_list_get_value (locations_list, i); + if (!GST_VALUE_HOLDS_STRUCTURE (location_val)) + continue; + + location_s = (const GstStructure *) g_value_get_boxed (location_val); + if (!gst_structure_has_name (location_s, "redirect")) + continue; + + new_location = gst_structure_get_string (location_s, "new-location"); + if (new_location) + break; + } + } + + if (new_location) { + GST_FIXME_OBJECT (self, "Handle redirection to '%s'", new_location); + } + } +} + + +static gpointer +gst_transcoder_main (gpointer data) +{ + GstTranscoder *self = GST_TRANSCODER (data); + GstBus *bus; + GSource *source; + GSource *bus_source; + + GST_TRACE_OBJECT (self, "Starting main thread"); + + g_main_context_push_thread_default (self->context); + + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self, + NULL); + g_source_attach (source, self->context); + g_source_unref (source); + + self->bus = bus = gst_element_get_bus (self->transcodebin); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, self->context); + + g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + G_CALLBACK (state_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::clock-lost", + G_CALLBACK (clock_lost_cb), self); + g_signal_connect (G_OBJECT (bus), "message::duration-changed", + G_CALLBACK (duration_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::latency", + G_CALLBACK (latency_cb), self); + g_signal_connect (G_OBJECT (bus), "message::request-state", + G_CALLBACK (request_state_cb), self); + g_signal_connect (G_OBJECT (bus), "message::element", + G_CALLBACK (element_cb), self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + self->is_eos = FALSE; + self->is_live = FALSE; + + GST_TRACE_OBJECT (self, "Starting main loop"); + g_main_loop_run (self->loop); + GST_TRACE_OBJECT (self, "Stopped main loop"); + + g_source_destroy (bus_source); + g_source_unref (bus_source); + gst_object_unref (bus); + + remove_tick_source (self); + + g_main_context_pop_thread_default (self->context); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + if (self->transcodebin) { + gst_element_set_state (self->transcodebin, GST_STATE_NULL); + g_clear_object (&self->transcodebin); + } + + GST_TRACE_OBJECT (self, "Stopped main thread"); + + return NULL; +} + +static gpointer +gst_transcoder_init_once (G_GNUC_UNUSED gpointer user_data) +{ + gst_init (NULL, NULL); + + GST_DEBUG_CATEGORY_INIT (gst_transcoder_debug, "gst-transcoder", 0, + "GstTranscoder"); + gst_transcoder_error_quark (); + + return NULL; +} + +static GstEncodingProfile * +create_encoding_profile (const gchar * pname) +{ + GstEncodingProfile *profile; + GValue value = G_VALUE_INIT; + + g_value_init (&value, GST_TYPE_ENCODING_PROFILE); + + if (!gst_value_deserialize (&value, pname)) { + g_value_reset (&value); + + return NULL; + } + + profile = g_value_dup_object (&value); + g_value_reset (&value); + + return profile; +} + +/** + * gst_transcoder_new: + * @source_uri: The URI of the media stream to transcode + * @dest_uri: The URI of the destination of the transcoded stream + * @encoding_profile: The serialized #GstEncodingProfile defining the output + * format. Have a look at the #GstEncodingProfile documentation to find more + * about the serialization format. + * + * Returns: a new #GstTranscoder instance + */ +GstTranscoder * +gst_transcoder_new (const gchar * source_uri, + const gchar * dest_uri, const gchar * encoding_profile) +{ + GstEncodingProfile *profile; + + profile = create_encoding_profile (encoding_profile); + + return gst_transcoder_new_full (source_uri, dest_uri, profile, NULL); +} + +/** + * gst_transcoder_new_full: + * @source_uri: The URI of the media stream to transcode + * @dest_uri: The URI of the destination of the transcoded stream + * @profile: The #GstEncodingProfile defining the output format + * have a look at the #GstEncodingProfile documentation to find more + * about the serialization format. + * @signal_dispatcher: The #GstTranscoderSignalDispatcher to be used + * to dispatch the various signals. + * + * Returns: a new #GstTranscoder instance + */ +GstTranscoder * +gst_transcoder_new_full (const gchar * source_uri, + const gchar * dest_uri, GstEncodingProfile * profile, + GstTranscoderSignalDispatcher * signal_dispatcher) +{ + static GOnce once = G_ONCE_INIT; + + g_once (&once, gst_transcoder_init_once, NULL); + + g_return_val_if_fail (source_uri, NULL); + g_return_val_if_fail (dest_uri, NULL); + + return g_object_new (GST_TYPE_TRANSCODER, "src-uri", source_uri, + "dest-uri", dest_uri, "profile", profile, + "signal-dispatcher", signal_dispatcher, NULL); +} + +typedef struct +{ + GError **user_error; + GMutex m; + GCond cond; + + gboolean done; + +} RunSyncData; + +static void +_error_cb (GstTranscoder * self, GError * error, GstStructure * details, + RunSyncData * data) +{ + g_mutex_lock (&data->m); + data->done = TRUE; + if (data->user_error && (*data->user_error) == NULL) + g_propagate_error (data->user_error, error); + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->m); +} + +static void +_done_cb (GstTranscoder * self, RunSyncData * data) +{ + g_mutex_lock (&data->m); + data->done = TRUE; + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->m); +} + +/** + * gst_transcoder_run: + * @self: The GstTranscoder to run + * @error: (allow-none): An error to be set if transcoding fails + * + * Run the transcoder task synchonously. You can connect + * to the 'position' signal to get information about the + * progress of the transcoding. + */ +gboolean +gst_transcoder_run (GstTranscoder * self, GError ** error) +{ + RunSyncData data = { 0, }; + + g_mutex_init (&data.m); + g_cond_init (&data.cond); + + g_signal_connect (self, "error", G_CALLBACK (_error_cb), &data); + g_signal_connect (self, "done", G_CALLBACK (_done_cb), &data); + gst_transcoder_run_async (self); + + g_mutex_lock (&data.m); + while (!data.done) { + g_cond_wait (&data.cond, &data.m); + } + g_mutex_unlock (&data.m); + + if (data.user_error) { + g_propagate_error (error, *data.user_error); + + return FALSE; + } + + return TRUE; +} + +/** + * gst_transcoder_run_async: + * @self: The GstTranscoder to run + * + * Run the transcoder task asynchronously. You should connect + * to the 'done' signal to be notified about when the + * transcoding is done, and to the 'error' signal to be + * notified about any error. + */ +void +gst_transcoder_run_async (GstTranscoder * self) +{ + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Play"); + + if (!self->profile) { + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "No \"profile\" provided"), NULL); + + return; + } + + self->target_state = GST_STATE_PLAYING; + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING); + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "Could not start transcoding"), NULL); + return; + } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) { + self->is_live = TRUE; + GST_DEBUG_OBJECT (self, "Pipeline is live"); + } + + return; +} + +static gboolean +gst_transcoder_set_position_update_interval_internal (gpointer user_data) +{ + GstTranscoder *self = user_data; + + GST_OBJECT_LOCK (self); + + if (self->tick_source) { + remove_tick_source (self); + add_tick_source (self); + } + + GST_OBJECT_UNLOCK (self); + + return G_SOURCE_REMOVE; +} + +/** + * gst_transcoder_set_position_update_interval: + * @self: #GstTranscoder instance + * @interval: interval in ms + * + * Set interval in milliseconds between two position-updated signals. + * Pass 0 to stop updating the position. + */ +void +gst_transcoder_set_position_update_interval (GstTranscoder * self, + guint interval) +{ + g_return_if_fail (GST_IS_TRANSCODER (self)); + g_return_if_fail (interval <= 10000); + + GST_OBJECT_LOCK (self); + self->position_update_interval_ms = interval; + GST_OBJECT_UNLOCK (self); + + gst_transcoder_set_position_update_interval_internal (self); +} + +/** + * gst_transcoder_get_position_update_interval: + * @self: #GstTranscoder instance + * + * Returns: current position update interval in milliseconds + */ +guint +gst_transcoder_get_position_update_interval (GstTranscoder * self) +{ + g_return_val_if_fail (GST_IS_TRANSCODER (self), + DEFAULT_POSITION_UPDATE_INTERVAL_MS); + + return self->position_update_interval_ms; +} + +/** + * gst_transcoder_get_source_uri: + * @self: #GstTranscoder instance + * + * Gets the URI of the currently-transcoding stream. + * + * Returns: (transfer full): a string containing the URI of the + * source stream. g_free() after usage. + */ +gchar * +gst_transcoder_get_source_uri (GstTranscoder * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI); + + g_object_get (self, "src-uri", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_dest_uri: + * @self: #GstTranscoder instance + * + * Gets the URI of the destination of the transcoded stream. + * + * Returns: (transfer full): a string containing the URI of the + * destination of the transcoded stream. g_free() after usage. + */ +gchar * +gst_transcoder_get_dest_uri (GstTranscoder * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI); + + g_object_get (self, "dest-uri", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_position: + * @self: #GstTranscoder instance + * + * Returns: the absolute position time, in nanoseconds, of the + * transcoding stream. + */ +GstClockTime +gst_transcoder_get_position (GstTranscoder * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_POSITION); + + g_object_get (self, "position", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_duration: + * @self: #GstTranscoder instance + * + * Retrieves the duration of the media stream that self represents. + * + * Returns: the duration of the transcoding media stream, in + * nanoseconds. + */ +GstClockTime +gst_transcoder_get_duration (GstTranscoder * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_DURATION); + + g_object_get (self, "duration", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_pipeline: + * @self: #GstTranscoder instance + * + * Returns: (transfer full): The internal uritranscodebin instance + */ +GstElement * +gst_transcoder_get_pipeline (GstTranscoder * self) +{ + GstElement *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL); + + g_object_get (self, "pipeline", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_avoid_reencoding: + * @self: The #GstTranscoder to check whether reencoding is avoided or not. + * + * Returns: %TRUE if the transcoder tries to avoid reencoding streams where + * reencoding is not strictly needed, %FALSE otherwise. + */ +gboolean +gst_transcoder_get_avoid_reencoding (GstTranscoder * self) +{ + gboolean val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), FALSE); + + g_object_get (self->transcodebin, "avoid-reencoding", &val, NULL); + + return val; +} + +/** + * gst_transcoder_set_avoid_reencoding: + * @self: The #GstTranscoder to set whether reencoding should be avoided or not. + * @avoid_reencoding: %TRUE if the transcoder should try to avoid reencoding + * streams where * reencoding is not strictly needed, %FALSE otherwise. + */ +void +gst_transcoder_set_avoid_reencoding (GstTranscoder * self, + gboolean avoid_reencoding) +{ + g_return_if_fail (GST_IS_TRANSCODER (self)); + + g_object_set (self->transcodebin, "avoid-reencoding", avoid_reencoding, NULL); +} + +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +GType +gst_transcoder_error_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_TRANSCODER_ERROR_FAILED), "GST_TRANSCODER_ERROR_FAILED", + "failed"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstTranscoderError", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_transcoder_error_get_name: + * @error: a #GstTranscoderError + * + * Gets a string representing the given error. + * + * Returns: (transfer none): a string with the given error. + */ +const gchar * +gst_transcoder_error_get_name (GstTranscoderError error) +{ + switch (error) { + case GST_TRANSCODER_ERROR_FAILED: + return "failed"; + } + + g_assert_not_reached (); + return NULL; +} + +G_DEFINE_INTERFACE (GstTranscoderSignalDispatcher, + gst_transcoder_signal_dispatcher, G_TYPE_OBJECT); + +static void +gst_transcoder_signal_dispatcher_default_init (G_GNUC_UNUSED + GstTranscoderSignalDispatcherInterface * iface) +{ + +} + +static void +gst_transcoder_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, void (*emitter) (gpointer data), gpointer data, + GDestroyNotify destroy) +{ + GstTranscoderSignalDispatcherInterface *iface; + + if (!self) { + emitter (data); + if (destroy) + destroy (data); + return; + } + + g_return_if_fail (GST_IS_TRANSCODER_SIGNAL_DISPATCHER (self)); + iface = GST_TRANSCODER_SIGNAL_DISPATCHER_GET_INTERFACE (self); + g_return_if_fail (iface->dispatch != NULL); + + iface->dispatch (self, transcoder, emitter, data, destroy); +} + +struct _GstTranscoderGMainContextSignalDispatcher +{ + GObject parent; + GMainContext *application_context; +}; + +struct _GstTranscoderGMainContextSignalDispatcherClass +{ + GObjectClass parent_class; +}; + +static void + gst_transcoder_g_main_context_signal_dispatcher_interface_init + (GstTranscoderSignalDispatcherInterface * iface); + +enum +{ + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_0, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstTranscoderGMainContextSignalDispatcher, + gst_transcoder_g_main_context_signal_dispatcher, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, + gst_transcoder_g_main_context_signal_dispatcher_interface_init)); + +static GParamSpec + * g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; + +static void +gst_transcoder_g_main_context_signal_dispatcher_finalize (GObject * object) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + if (self->application_context) + g_main_context_unref (self->application_context); + + G_OBJECT_CLASS + (gst_transcoder_g_main_context_signal_dispatcher_parent_class)->finalize + (object); +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + self->application_context = g_value_dup_boxed (value); + if (!self->application_context) + self->application_context = g_main_context_ref_thread_default (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + g_value_set_boxed (value, self->application_context); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void + gst_transcoder_g_main_context_signal_dispatcher_class_init + (GstTranscoderGMainContextSignalDispatcherClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = + gst_transcoder_g_main_context_signal_dispatcher_finalize; + gobject_class->set_property = + gst_transcoder_g_main_context_signal_dispatcher_set_property; + gobject_class->get_property = + gst_transcoder_g_main_context_signal_dispatcher_get_property; + + g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT] = + g_param_spec_boxed ("application-context", "Application Context", + "Application GMainContext to dispatch signals to", G_TYPE_MAIN_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST, + g_main_context_signal_dispatcher_param_specs); +} + +static void + gst_transcoder_g_main_context_signal_dispatcher_init + (G_GNUC_UNUSED GstTranscoderGMainContextSignalDispatcher * self) +{ +} + +typedef struct +{ + void (*emitter) (gpointer data); + gpointer data; + GDestroyNotify destroy; +} GMainContextSignalDispatcherData; + +static gboolean +g_main_context_signal_dispatcher_dispatch_gsourcefunc (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + data->emitter (data->data); + + return G_SOURCE_REMOVE; +} + +static void +g_main_context_signal_dispatcher_dispatch_destroy (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + if (data->destroy) + data->destroy (data->data); + g_free (data); +} + +/* *INDENT-OFF* */ +static void +gst_transcoder_g_main_context_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * iface, + G_GNUC_UNUSED GstTranscoder * transcoder, void (*emitter) (gpointer data), + gpointer data, GDestroyNotify destroy) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (iface); + GMainContextSignalDispatcherData *gsourcefunc_data = + g_new0 (GMainContextSignalDispatcherData, 1); + + gsourcefunc_data->emitter = emitter; + gsourcefunc_data->data = data; + gsourcefunc_data->destroy = destroy; + + g_main_context_invoke_full (self->application_context, + G_PRIORITY_DEFAULT, g_main_context_signal_dispatcher_dispatch_gsourcefunc, + gsourcefunc_data, g_main_context_signal_dispatcher_dispatch_destroy); +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_interface_init (GstTranscoderSignalDispatcherInterface * iface) +{ + iface->dispatch = gst_transcoder_g_main_context_signal_dispatcher_dispatch; +} +/* *INDENT-ON* */ + +/** + * gst_transcoder_g_main_context_signal_dispatcher_new: + * @application_context: (allow-none): GMainContext to use or %NULL + * + * Returns: (transfer full): + */ +GstTranscoderSignalDispatcher * +gst_transcoder_g_main_context_signal_dispatcher_new (GMainContext * + application_context) +{ + return g_object_new (GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, + "application-context", application_context, NULL); +} diff --git a/gst-libs/gst/transcoder/gsttranscoder.h b/gst-libs/gst/transcoder/gsttranscoder.h new file mode 100644 index 000000000..9cc74a420 --- /dev/null +++ b/gst-libs/gst/transcoder/gsttranscoder.h @@ -0,0 +1,141 @@ +#ifndef __GST_TRANSCODER_H +#define __GST_TRANSCODER_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> +#include "transcoder-prelude.h" + +G_BEGIN_DECLS + +typedef struct _GstTranscoderSignalDispatcher GstTranscoderSignalDispatcher; +typedef struct _GstTranscoderSignalDispatcherInterface GstTranscoderSignalDispatcherInterface; + +/*********** Error definitions ************/ +#define GST_TRANSCODER_ERROR (gst_transcoder_error_quark ()) +#define GST_TYPE_TRANSCODER_ERROR (gst_transcoder_error_get_type ()) + +/** + * GstTranscoderError: + * @GST_TRANSCODER_ERROR_FAILED: generic error. + */ +typedef enum { + GST_TRANSCODER_ERROR_FAILED = 0 +} GstTranscoderError; + +GST_TRANSCODER_API +GQuark gst_transcoder_error_quark (void); +GST_TRANSCODER_API +GType gst_transcoder_error_get_type (void); +GST_TRANSCODER_API +const gchar * gst_transcoder_error_get_name (GstTranscoderError error); + +/*********** GstTranscoder definition ************/ +#define GST_TYPE_TRANSCODER (gst_transcoder_get_type ()) +#define GST_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER, GstTranscoder)) +#define GST_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER, GstTranscoderClass)) +#define GST_IS_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER)) +#define GST_IS_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER)) +#define GST_TRANSCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER, GstTranscoderClass)) + +typedef struct _GstTranscoder GstTranscoder; +typedef struct _GstTranscoderClass GstTranscoderClass; +typedef struct _GstTranscoderPrivate GstTranscoderPrivate; + +GST_TRANSCODER_API +GType gst_transcoder_get_type (void); + +GST_TRANSCODER_API +GstTranscoder * gst_transcoder_new (const gchar * source_uri, + const gchar * dest_uri, + const gchar * encoding_profile); + +GST_TRANSCODER_API +GstTranscoder * gst_transcoder_new_full (const gchar * source_uri, + const gchar * dest_uri, + GstEncodingProfile *profile, + GstTranscoderSignalDispatcher *signal_dispatcher); + +GST_TRANSCODER_API +gboolean gst_transcoder_run (GstTranscoder *self, + GError ** error); + +GST_TRANSCODER_API +void gst_transcoder_set_cpu_usage (GstTranscoder *self, + gint cpu_usage); + +GST_TRANSCODER_API +void gst_transcoder_run_async (GstTranscoder *self); + +GST_TRANSCODER_API +void gst_transcoder_set_position_update_interval (GstTranscoder *self, + guint interval); + +GST_TRANSCODER_API +gchar * gst_transcoder_get_source_uri (GstTranscoder * self); + +GST_TRANSCODER_API +gchar * gst_transcoder_get_dest_uri (GstTranscoder * self); + +GST_TRANSCODER_API +guint gst_transcoder_get_position_update_interval (GstTranscoder *self); + +GST_TRANSCODER_API +GstClockTime gst_transcoder_get_position (GstTranscoder * self); + +GST_TRANSCODER_API +GstClockTime gst_transcoder_get_duration (GstTranscoder * self); + +GST_TRANSCODER_API +GstElement * gst_transcoder_get_pipeline (GstTranscoder * self); + +GST_TRANSCODER_API +gboolean gst_transcoder_get_avoid_reencoding (GstTranscoder * self); +GST_TRANSCODER_API +void gst_transcoder_set_avoid_reencoding (GstTranscoder * self, + gboolean avoid_reencoding); + + +/****************** Signal dispatcher *******************************/ + +#define GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER (gst_transcoder_signal_dispatcher_get_type ()) +#define GST_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcher)) +#define GST_IS_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER)) +#define GST_TRANSCODER_SIGNAL_DISPATCHER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcherInterface)) + +struct _GstTranscoderSignalDispatcherInterface { + GTypeInterface parent_iface; + + void (*dispatch) (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, + void (*emitter) (gpointer data), + gpointer data, + GDestroyNotify destroy); +}; + +typedef struct _GstTranscoderGMainContextSignalDispatcher GstTranscoderGMainContextSignalDispatcher; +typedef struct _GstTranscoderGMainContextSignalDispatcherClass GstTranscoderGMainContextSignalDispatcherClass; + +GST_TRANSCODER_API +GType gst_transcoder_signal_dispatcher_get_type (void); + +#define GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (gst_transcoder_g_main_context_signal_dispatcher_get_type ()) +#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcher)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST(obj) ((GstTranscoderGMainContextSignalDispatcher*)(obj)) + +GST_TRANSCODER_API +GType gst_transcoder_g_main_context_signal_dispatcher_get_type (void); + +GST_TRANSCODER_API +GstTranscoderSignalDispatcher * gst_transcoder_g_main_context_signal_dispatcher_new (GMainContext * application_context); + +G_END_DECLS + +#endif diff --git a/gst-libs/gst/transcoder/meson.build b/gst-libs/gst/transcoder/meson.build new file mode 100644 index 000000000..47076d417 --- /dev/null +++ b/gst-libs/gst/transcoder/meson.build @@ -0,0 +1,33 @@ +sources = files(['gsttranscoder.c']) +headers = files(['gsttranscoder.h', 'transcoder-prelude.h']) + +install_headers(headers, subdir : 'gstreamer-' + api_version + '/gst/transcoder') + +gst_transcoder = library('gsttranscoder-' + api_version, + sources, + install: true, + include_directories : [configinc, libsinc], + dependencies: [gst_dep, gstpbutils_dep], + c_args: gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API', '-DBUILDING_GST_TRANSCODER'], + soversion : soversion, +) +if build_gir + transcoder_gir = gnome.generate_gir(gst_transcoder, + sources : sources + headers, + nsversion : api_version, + namespace : 'GstTranscoder', + identifier_prefix : 'Gst', + symbol_prefix : 'gst_', + includes : ['GObject-2.0', + 'Gst-' + api_version, + 'GstPbutils-' + api_version], + dependencies: [gst_dep, gstpbutils_dep], + install : true, + extra_args : ['--add-init-section=extern gboolean gst_init(gint *argc, gchar **argv); gst_init(NULL,NULL);'] + ) +endif + +gst_transcoder_dep = declare_dependency(link_with: gst_transcoder, + dependencies : [gst_dep, gstpbutils_dep], + include_directories : [libsinc] +)
\ No newline at end of file diff --git a/gst-libs/gst/transcoder/transcoder-prelude.h b/gst-libs/gst/transcoder/transcoder-prelude.h new file mode 100644 index 000000000..ba153dd16 --- /dev/null +++ b/gst-libs/gst/transcoder/transcoder-prelude.h @@ -0,0 +1,36 @@ +/* GStreamer Transcoder Library + * + * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com> + * + * transcoder-prelude.h: prelude include header for the gst-transcoder library + * + * 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_TRANSCODER_PRELUDE_H__ +#define __GST_TRANSCODER_PRELUDE_H__ + +#include <gst/gst.h> + +#ifndef GST_TRANSCODER_API +# ifdef BUILDING_GST_TRANSCODER +# define GST_TRANSCODER_API GST_API_EXPORT /* from config.h */ +# else +# define GST_TRANSCODER_API GST_API_IMPORT +# endif +#endif + +#endif /* __GST_TRANSCODER_PRELUDE_H__ */ |