• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  *
3  * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
4  * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:gsttranscoder
24  * @short_description: High level API to transcode media files
25  * from one format to any other format using the GStreamer framework.
26  * @symbols:
27  *   - gst_transcoder_error_quark
28  */
29 
30 #include "gsttranscoder.h"
31 #include "gsttranscoder-private.h"
32 
33 static GOnce once = G_ONCE_INIT;
34 
35 GST_DEBUG_CATEGORY_STATIC (gst_transcoder_debug);
36 #define GST_CAT_DEFAULT gst_transcoder_debug
37 
38 #define DEFAULT_URI NULL
39 #define DEFAULT_POSITION GST_CLOCK_TIME_NONE
40 #define DEFAULT_DURATION GST_CLOCK_TIME_NONE
41 #define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100
42 #define DEFAULT_AVOID_REENCODING   FALSE
43 
44 GQuark
gst_transcoder_error_quark(void)45 gst_transcoder_error_quark (void)
46 {
47   static GQuark quark;
48 
49   if (!quark)
50     quark = g_quark_from_static_string ("gst-transcoder-error-quark");
51 
52   return quark;
53 }
54 
55 enum
56 {
57   PROP_0,
58   PROP_SRC_URI,
59   PROP_DEST_URI,
60   PROP_PROFILE,
61   PROP_POSITION,
62   PROP_DURATION,
63   PROP_PIPELINE,
64   PROP_POSITION_UPDATE_INTERVAL,
65   PROP_AVOID_REENCODING,
66   PROP_LAST
67 };
68 
69 struct _GstTranscoder
70 {
71   GstObject parent;
72 
73   GstEncodingProfile *profile;
74   gchar *source_uri;
75   gchar *dest_uri;
76 
77   GThread *thread;
78   GCond cond;
79   GMainContext *context;
80   GMainLoop *loop;
81 
82   GstElement *transcodebin;
83   GstBus *bus;
84   GstState target_state, current_state;
85   gboolean is_live, is_eos;
86   GSource *tick_source, *ready_timeout_source;
87 
88   guint position_update_interval_ms;
89   gint wanted_cpu_usage;
90 
91   GstClockTime last_duration;
92 
93   GstTranscoderState app_state;
94 
95   GstBus *api_bus;
96   GstTranscoderSignalAdapter *signal_adapter;
97   GstTranscoderSignalAdapter *sync_signal_adapter;
98 };
99 
100 struct _GstTranscoderClass
101 {
102   GstObjectClass parent_class;
103 };
104 
105 #define parent_class gst_transcoder_parent_class
106 G_DEFINE_TYPE (GstTranscoder, gst_transcoder, GST_TYPE_OBJECT);
107 
108 static GParamSpec *param_specs[PROP_LAST] = { NULL, };
109 
110 static void gst_transcoder_dispose (GObject * object);
111 static void gst_transcoder_finalize (GObject * object);
112 static void gst_transcoder_set_property (GObject * object, guint prop_id,
113     const GValue * value, GParamSpec * pspec);
114 static void gst_transcoder_get_property (GObject * object, guint prop_id,
115     GValue * value, GParamSpec * pspec);
116 static void gst_transcoder_constructed (GObject * object);
117 
118 static gpointer gst_transcoder_main (gpointer data);
119 
120 static gboolean gst_transcoder_set_position_update_interval_internal (gpointer
121     user_data);
122 
123 
124 /**
125  * gst_transcoder_set_cpu_usage:
126  * @self: The GstTranscoder to limit CPU usage on.
127  * @cpu_usage: The percentage of the CPU the process running the transcoder
128  * should try to use. It takes into account the number of cores available.
129  *
130  * Sets @cpu_usage as target percentage CPU usage of the process running the
131  * transcoding task. It will modulate the transcoding speed to reach that target
132  * usage.
133  */
134 void
gst_transcoder_set_cpu_usage(GstTranscoder * self,gint cpu_usage)135 gst_transcoder_set_cpu_usage (GstTranscoder * self, gint cpu_usage)
136 {
137   GST_OBJECT_LOCK (self);
138   self->wanted_cpu_usage = cpu_usage;
139   if (self->transcodebin)
140     g_object_set (self->transcodebin, "cpu-usage", cpu_usage, NULL);
141   GST_OBJECT_UNLOCK (self);
142 }
143 
144 static void
gst_transcoder_init(GstTranscoder * self)145 gst_transcoder_init (GstTranscoder * self)
146 {
147   GST_TRACE_OBJECT (self, "Initializing");
148 
149   self = gst_transcoder_get_instance_private (self);
150 
151   g_cond_init (&self->cond);
152 
153   self->context = g_main_context_new ();
154   self->loop = g_main_loop_new (self->context, FALSE);
155   self->api_bus = gst_bus_new ();
156   self->wanted_cpu_usage = 100;
157 
158   self->position_update_interval_ms = DEFAULT_POSITION_UPDATE_INTERVAL_MS;
159 
160   GST_TRACE_OBJECT (self, "Initialized");
161 }
162 
163 static void
gst_transcoder_class_init(GstTranscoderClass * klass)164 gst_transcoder_class_init (GstTranscoderClass * klass)
165 {
166   GObjectClass *gobject_class = (GObjectClass *) klass;
167 
168   gobject_class->set_property = gst_transcoder_set_property;
169   gobject_class->get_property = gst_transcoder_get_property;
170   gobject_class->dispose = gst_transcoder_dispose;
171   gobject_class->finalize = gst_transcoder_finalize;
172   gobject_class->constructed = gst_transcoder_constructed;
173 
174   param_specs[PROP_SRC_URI] =
175       g_param_spec_string ("src-uri", "URI", "Source URI", DEFAULT_URI,
176       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
177 
178   param_specs[PROP_DEST_URI] =
179       g_param_spec_string ("dest-uri", "URI", "Source URI", DEFAULT_URI,
180       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
181 
182   param_specs[PROP_PROFILE] =
183       g_param_spec_object ("profile", "Profile",
184       "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE,
185       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
186 
187   param_specs[PROP_POSITION] =
188       g_param_spec_uint64 ("position", "Position", "Current Position",
189       0, G_MAXUINT64, DEFAULT_POSITION,
190       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
191 
192   param_specs[PROP_DURATION] =
193       g_param_spec_uint64 ("duration", "Duration", "Duration",
194       0, G_MAXUINT64, DEFAULT_DURATION,
195       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
196 
197   param_specs[PROP_PIPELINE] =
198       g_param_spec_object ("pipeline", "Pipeline",
199       "GStreamer pipeline that is used",
200       GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
201 
202   param_specs[PROP_POSITION_UPDATE_INTERVAL] =
203       g_param_spec_uint ("position-update-interval", "Position update interval",
204       "Interval in milliseconds between two position-updated signals."
205       "Pass 0 to stop updating the position.",
206       0, 10000, DEFAULT_POSITION_UPDATE_INTERVAL_MS,
207       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
208 
209   /**
210    * GstTranscoder:avoid-reencoding:
211    *
212    * See #encodebin:avoid-reencoding
213    */
214   param_specs[PROP_AVOID_REENCODING] =
215       g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding",
216       "Whether to re-encode portions of compatible video streams that lay on segment boundaries",
217       DEFAULT_AVOID_REENCODING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
218 
219   g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
220 }
221 
222 static void
gst_transcoder_dispose(GObject * object)223 gst_transcoder_dispose (GObject * object)
224 {
225   GstTranscoder *self = GST_TRANSCODER (object);
226 
227   GST_TRACE_OBJECT (self, "Stopping main thread");
228 
229   GST_OBJECT_LOCK (self);
230   if (self->loop) {
231     g_main_loop_quit (self->loop);
232     GST_OBJECT_UNLOCK (self);
233 
234     g_thread_join (self->thread);
235 
236     GST_OBJECT_LOCK (self);
237     self->thread = NULL;
238 
239     g_main_loop_unref (self->loop);
240     self->loop = NULL;
241 
242     g_main_context_unref (self->context);
243     self->context = NULL;
244 
245     gst_clear_object (&self->signal_adapter);
246     gst_clear_object (&self->sync_signal_adapter);
247     GST_OBJECT_UNLOCK (self);
248   } else {
249     GST_OBJECT_UNLOCK (self);
250   }
251 
252   G_OBJECT_CLASS (parent_class)->dispose (object);
253 }
254 
255 static void
gst_transcoder_finalize(GObject * object)256 gst_transcoder_finalize (GObject * object)
257 {
258   GstTranscoder *self = GST_TRANSCODER (object);
259 
260   GST_TRACE_OBJECT (self, "Finalizing");
261 
262   g_free (self->source_uri);
263   g_free (self->dest_uri);
264   g_cond_clear (&self->cond);
265 
266   G_OBJECT_CLASS (parent_class)->finalize (object);
267 }
268 
269 static void
gst_transcoder_constructed(GObject * object)270 gst_transcoder_constructed (GObject * object)
271 {
272   GstTranscoder *self = GST_TRANSCODER (object);
273 
274   GST_TRACE_OBJECT (self, "Constructed");
275 
276   self->transcodebin =
277       gst_element_factory_make ("uritranscodebin", "uritranscodebin");
278 
279   g_object_set (self->transcodebin, "source-uri", self->source_uri,
280       "dest-uri", self->dest_uri, "profile", self->profile,
281       "cpu-usage", self->wanted_cpu_usage, NULL);
282 
283   GST_OBJECT_LOCK (self);
284   self->thread = g_thread_new ("GstTranscoder", gst_transcoder_main, self);
285   while (!self->loop || !g_main_loop_is_running (self->loop))
286     g_cond_wait (&self->cond, GST_OBJECT_GET_LOCK (self));
287   GST_OBJECT_UNLOCK (self);
288 
289   G_OBJECT_CLASS (parent_class)->constructed (object);
290 }
291 
292 static void
gst_transcoder_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)293 gst_transcoder_set_property (GObject * object, guint prop_id,
294     const GValue * value, GParamSpec * pspec)
295 {
296   GstTranscoder *self = GST_TRANSCODER (object);
297 
298   switch (prop_id) {
299     case PROP_SRC_URI:{
300       GST_OBJECT_LOCK (self);
301       g_free (self->source_uri);
302       self->source_uri = g_value_dup_string (value);
303       GST_DEBUG_OBJECT (self, "Set source_uri=%s", self->source_uri);
304       GST_OBJECT_UNLOCK (self);
305       break;
306     }
307     case PROP_DEST_URI:{
308       GST_OBJECT_LOCK (self);
309       g_free (self->dest_uri);
310       self->dest_uri = g_value_dup_string (value);
311       GST_DEBUG_OBJECT (self, "Set dest_uri=%s", self->dest_uri);
312       GST_OBJECT_UNLOCK (self);
313       break;
314     }
315     case PROP_POSITION_UPDATE_INTERVAL:
316       GST_OBJECT_LOCK (self);
317       self->position_update_interval_ms = g_value_get_uint (value);
318       GST_DEBUG_OBJECT (self, "Set position update interval=%u ms",
319           g_value_get_uint (value));
320       GST_OBJECT_UNLOCK (self);
321 
322       gst_transcoder_set_position_update_interval_internal (self);
323       break;
324     case PROP_PROFILE:
325       GST_OBJECT_LOCK (self);
326       self->profile = g_value_dup_object (value);
327       GST_OBJECT_UNLOCK (self);
328       break;
329     case PROP_AVOID_REENCODING:
330       g_object_set (self->transcodebin, "avoid-reencoding",
331           g_value_get_boolean (value), NULL);
332       break;
333     default:
334       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
335       break;
336   }
337 }
338 
339 static void
gst_transcoder_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)340 gst_transcoder_get_property (GObject * object, guint prop_id,
341     GValue * value, GParamSpec * pspec)
342 {
343   GstTranscoder *self = GST_TRANSCODER (object);
344 
345   switch (prop_id) {
346     case PROP_SRC_URI:
347       GST_OBJECT_LOCK (self);
348       g_value_set_string (value, self->source_uri);
349       GST_OBJECT_UNLOCK (self);
350       break;
351     case PROP_DEST_URI:
352       GST_OBJECT_LOCK (self);
353       g_value_set_string (value, self->dest_uri);
354       GST_OBJECT_UNLOCK (self);
355       break;
356     case PROP_POSITION:{
357       gint64 position = 0;
358 
359       if (self->is_eos)
360         position = self->last_duration;
361       else
362         gst_element_query_position (self->transcodebin, GST_FORMAT_TIME,
363             &position);
364       g_value_set_uint64 (value, position);
365       GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT,
366           GST_TIME_ARGS (g_value_get_uint64 (value)));
367       break;
368     }
369     case PROP_DURATION:{
370       gint64 duration = 0;
371 
372       gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME,
373           &duration);
374       g_value_set_uint64 (value, duration);
375       GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT,
376           GST_TIME_ARGS (g_value_get_uint64 (value)));
377       break;
378     }
379     case PROP_PIPELINE:
380       g_value_set_object (value, self->transcodebin);
381       break;
382     case PROP_POSITION_UPDATE_INTERVAL:
383       GST_OBJECT_LOCK (self);
384       g_value_set_uint (value,
385           gst_transcoder_get_position_update_interval (self));
386       GST_OBJECT_UNLOCK (self);
387       break;
388     case PROP_PROFILE:
389       GST_OBJECT_LOCK (self);
390       g_value_set_object (value, self->profile);
391       GST_OBJECT_UNLOCK (self);
392       break;
393     case PROP_AVOID_REENCODING:
394     {
395       gboolean avoid_reencoding;
396 
397       g_object_get (self->transcodebin, "avoid-reencoding", &avoid_reencoding,
398           NULL);
399       g_value_set_boolean (value, avoid_reencoding);
400       break;
401     }
402     default:
403       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
404       break;
405   }
406 }
407 
408 /*
409  * Works same as gst_structure_set to set field/type/value triplets on message data
410  */
411 static void
api_bus_post_message(GstTranscoder * self,GstTranscoderMessage message_type,const gchar * firstfield,...)412 api_bus_post_message (GstTranscoder * self, GstTranscoderMessage message_type,
413     const gchar * firstfield, ...)
414 {
415   GstStructure *message_data = NULL;
416   GstMessage *msg = NULL;
417   va_list varargs;
418 
419   GST_INFO ("Posting API-bus message-type: %s",
420       gst_transcoder_message_get_name (message_type));
421   message_data = gst_structure_new (GST_TRANSCODER_MESSAGE_DATA,
422       GST_TRANSCODER_MESSAGE_DATA_TYPE, GST_TYPE_TRANSCODER_MESSAGE,
423       message_type, NULL);
424 
425   va_start (varargs, firstfield);
426   gst_structure_set_valist (message_data, firstfield, varargs);
427   va_end (varargs);
428 
429   msg = gst_message_new_custom (GST_MESSAGE_APPLICATION,
430       GST_OBJECT (self), message_data);
431   GST_DEBUG ("Created message with payload: [ %" GST_PTR_FORMAT " ]",
432       message_data);
433   gst_bus_post (self->api_bus, msg);
434 }
435 
436 static gboolean
main_loop_running_cb(gpointer user_data)437 main_loop_running_cb (gpointer user_data)
438 {
439   GstTranscoder *self = GST_TRANSCODER (user_data);
440 
441   GST_TRACE_OBJECT (self, "Main loop running now");
442 
443   GST_OBJECT_LOCK (self);
444   g_cond_signal (&self->cond);
445   GST_OBJECT_UNLOCK (self);
446 
447   return G_SOURCE_REMOVE;
448 }
449 
450 static gboolean
tick_cb(gpointer user_data)451 tick_cb (gpointer user_data)
452 {
453   GstTranscoder *self = GST_TRANSCODER (user_data);
454   gint64 position;
455 
456   if (self->target_state < GST_STATE_PAUSED)
457     return G_SOURCE_CONTINUE;
458 
459   if (!gst_element_query_position (self->transcodebin, GST_FORMAT_TIME,
460           &position)) {
461     GST_LOG_OBJECT (self, "Could not query position");
462     return G_SOURCE_CONTINUE;
463   }
464 
465   GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
466 
467   api_bus_post_message (self, GST_TRANSCODER_MESSAGE_POSITION_UPDATED,
468       GST_TRANSCODER_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, position,
469       NULL);
470 
471   return G_SOURCE_CONTINUE;
472 }
473 
474 static void
add_tick_source(GstTranscoder * self)475 add_tick_source (GstTranscoder * self)
476 {
477   if (self->tick_source)
478     return;
479 
480   if (!self->position_update_interval_ms)
481     return;
482 
483   self->tick_source = g_timeout_source_new (self->position_update_interval_ms);
484   g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL);
485   g_source_attach (self->tick_source, self->context);
486 }
487 
488 static void
remove_tick_source(GstTranscoder * self)489 remove_tick_source (GstTranscoder * self)
490 {
491   if (!self->tick_source)
492     return;
493 
494   g_source_destroy (self->tick_source);
495   g_source_unref (self->tick_source);
496   self->tick_source = NULL;
497 }
498 
499 static void
dump_dot_file(GstTranscoder * self,const gchar * name)500 dump_dot_file (GstTranscoder * self, const gchar * name)
501 {
502   gchar *full_name;
503 
504   full_name = g_strdup_printf ("gst-transcoder.%p.%s", self, name);
505 
506   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->transcodebin),
507       GST_DEBUG_GRAPH_SHOW_ALL, full_name);
508 
509   g_free (full_name);
510 }
511 
512 static void
error_cb(G_GNUC_UNUSED GstBus * bus,GstMessage * msg,gpointer user_data)513 error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
514 {
515   GError *err;
516   GstTranscoder *self = GST_TRANSCODER (user_data);
517   gchar *name, *debug, *message;
518   GstStructure *details = NULL;
519 
520   dump_dot_file (self, "error");
521 
522   gst_message_parse_error (msg, &err, &debug);
523   gst_message_parse_error_details (msg, (const GstStructure **) &details);
524 
525   if (!details)
526     details = gst_structure_new_empty ("details");
527   else
528     details = gst_structure_copy (details);
529 
530   name = gst_object_get_path_string (msg->src);
531   message = gst_error_get_message (err->domain, err->code);
532 
533   gst_structure_set (details, "debug", G_TYPE_STRING, debug,
534       "msg-source-element-name", G_TYPE_STRING, "name",
535       "msg-source-type", G_TYPE_GTYPE, G_OBJECT_TYPE (msg->src),
536       "msg-error", G_TYPE_STRING, message, NULL);
537 
538   api_bus_post_message (self, GST_TRANSCODER_MESSAGE_ERROR,
539       GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err,
540       GST_TRANSCODER_MESSAGE_DATA_ISSUE_DETAILS, GST_TYPE_STRUCTURE, details,
541       NULL);
542 
543   gst_structure_free (details);
544   g_clear_error (&err);
545   g_free (debug);
546   g_free (name);
547   g_free (message);
548 }
549 
550 static void
warning_cb(G_GNUC_UNUSED GstBus * bus,GstMessage * msg,gpointer user_data)551 warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
552 {
553   GstTranscoder *self = GST_TRANSCODER (user_data);
554   GError *err, *transcoder_err;
555   gchar *name, *debug, *message, *full_message;
556   const GstStructure *details = NULL;
557 
558   dump_dot_file (self, "warning");
559 
560   gst_message_parse_warning (msg, &err, &debug);
561   gst_message_parse_warning_details (msg, &details);
562 
563   name = gst_object_get_path_string (msg->src);
564   message = gst_error_get_message (err->domain, err->code);
565 
566   if (debug)
567     full_message =
568         g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message,
569         err->message, debug);
570   else
571     full_message =
572         g_strdup_printf ("Warning from element %s: %s\n%s", name, message,
573         err->message);
574 
575   GST_WARNING_OBJECT (self, "WARNING: from element %s: %s", name, err->message);
576   if (debug != NULL)
577     GST_WARNING_OBJECT (self, "Additional debug info: %s", debug);
578 
579   transcoder_err =
580       g_error_new_literal (GST_TRANSCODER_ERROR, GST_TRANSCODER_ERROR_FAILED,
581       full_message);
582 
583   api_bus_post_message (self, GST_TRANSCODER_MESSAGE_WARNING,
584       GST_TRANSCODER_MESSAGE_DATA_WARNING, G_TYPE_ERROR, transcoder_err,
585       GST_TRANSCODER_MESSAGE_DATA_ISSUE_DETAILS, GST_TYPE_STRUCTURE, details,
586       NULL);
587 
588   g_clear_error (&transcoder_err);
589   g_clear_error (&err);
590   g_free (debug);
591   g_free (name);
592   g_free (full_message);
593   g_free (message);
594 }
595 
596 static void
notify_state_changed(GstTranscoder * self,GstTranscoderState new_state)597 notify_state_changed (GstTranscoder * self, GstTranscoderState new_state)
598 {
599   if (new_state == self->app_state)
600     return;
601 
602   GST_DEBUG_OBJECT (self, "Notifying new state: %s",
603       gst_transcoder_state_get_name (new_state));
604   self->app_state = new_state;
605   api_bus_post_message (self, GST_TRANSCODER_MESSAGE_STATE_CHANGED,
606       GST_TRANSCODER_MESSAGE_DATA_STATE, GST_TYPE_TRANSCODER_STATE, new_state,
607       NULL);
608 }
609 
610 static void
eos_cb(G_GNUC_UNUSED GstBus * bus,G_GNUC_UNUSED GstMessage * msg,gpointer user_data)611 eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
612     gpointer user_data)
613 {
614   GstTranscoder *self = GST_TRANSCODER (user_data);
615 
616   GST_DEBUG_OBJECT (self, "End of stream");
617 
618   gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME,
619       (gint64 *) & self->last_duration);
620   tick_cb (self);
621   remove_tick_source (self);
622 
623   notify_state_changed (self, GST_TRANSCODER_STATE_STOPPED);
624   api_bus_post_message (self, GST_TRANSCODER_MESSAGE_DONE, NULL, NULL);
625   self->is_eos = TRUE;
626 }
627 
628 static void
clock_lost_cb(G_GNUC_UNUSED GstBus * bus,G_GNUC_UNUSED GstMessage * msg,gpointer user_data)629 clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
630     gpointer user_data)
631 {
632   GstTranscoder *self = GST_TRANSCODER (user_data);
633   GstStateChangeReturn state_ret;
634 
635   GST_DEBUG_OBJECT (self, "Clock lost");
636   if (self->target_state >= GST_STATE_PLAYING) {
637     state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PAUSED);
638     if (state_ret != GST_STATE_CHANGE_FAILURE)
639       state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING);
640 
641     if (state_ret == GST_STATE_CHANGE_FAILURE) {
642       GError *err = g_error_new (GST_TRANSCODER_ERROR,
643           GST_TRANSCODER_ERROR_FAILED, "Failed to handle clock loss");
644       api_bus_post_message (self, GST_TRANSCODER_MESSAGE_ERROR,
645           GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err, NULL);
646       g_error_free (err);
647     }
648   }
649 }
650 
651 static void
state_changed_cb(G_GNUC_UNUSED GstBus * bus,GstMessage * msg,gpointer user_data)652 state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
653     gpointer user_data)
654 {
655   GstTranscoder *self = GST_TRANSCODER (user_data);
656   GstState old_state, new_state, pending_state;
657 
658   gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
659 
660   if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->transcodebin)) {
661     gchar *transition_name;
662 
663     GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s",
664         gst_element_state_get_name (old_state),
665         gst_element_state_get_name (new_state),
666         gst_element_state_get_name (pending_state));
667 
668     transition_name = g_strdup_printf ("%s_%s",
669         gst_element_state_get_name (old_state),
670         gst_element_state_get_name (new_state));
671     dump_dot_file (self, transition_name);
672     g_free (transition_name);
673 
674     self->current_state = new_state;
675 
676     if (new_state == GST_STATE_PAUSED
677         && pending_state == GST_STATE_VOID_PENDING) {
678       remove_tick_source (self);
679       notify_state_changed (self, GST_TRANSCODER_STATE_PAUSED);
680     }
681 
682     if (new_state == GST_STATE_PLAYING
683         && pending_state == GST_STATE_VOID_PENDING) {
684       add_tick_source (self);
685       notify_state_changed (self, GST_TRANSCODER_STATE_PLAYING);
686     }
687   }
688 }
689 
690 static void
duration_changed_cb(G_GNUC_UNUSED GstBus * bus,G_GNUC_UNUSED GstMessage * msg,gpointer user_data)691 duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
692     gpointer user_data)
693 {
694   GstTranscoder *self = GST_TRANSCODER (user_data);
695   gint64 duration;
696 
697   if (gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME,
698           &duration)) {
699     api_bus_post_message (self, GST_TRANSCODER_MESSAGE_DURATION_CHANGED,
700         GST_TRANSCODER_MESSAGE_DATA_DURATION, GST_TYPE_CLOCK_TIME,
701         duration, NULL);
702   }
703 }
704 
705 static void
latency_cb(G_GNUC_UNUSED GstBus * bus,G_GNUC_UNUSED GstMessage * msg,gpointer user_data)706 latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
707     gpointer user_data)
708 {
709   GstTranscoder *self = GST_TRANSCODER (user_data);
710 
711   GST_DEBUG_OBJECT (self, "Latency changed");
712 
713   gst_bin_recalculate_latency (GST_BIN (self->transcodebin));
714 }
715 
716 static void
request_state_cb(G_GNUC_UNUSED GstBus * bus,GstMessage * msg,gpointer user_data)717 request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
718     gpointer user_data)
719 {
720   GstTranscoder *self = GST_TRANSCODER (user_data);
721   GstState state;
722   GstStateChangeReturn state_ret;
723 
724   gst_message_parse_request_state (msg, &state);
725 
726   GST_DEBUG_OBJECT (self, "State %s requested",
727       gst_element_state_get_name (state));
728 
729   self->target_state = state;
730   state_ret = gst_element_set_state (self->transcodebin, state);
731   if (state_ret == GST_STATE_CHANGE_FAILURE) {
732     GError *err = g_error_new (GST_TRANSCODER_ERROR,
733         GST_TRANSCODER_ERROR_FAILED,
734         "Failed to change to requested state %s",
735         gst_element_state_get_name (state));
736 
737     api_bus_post_message (self, GST_TRANSCODER_MESSAGE_ERROR,
738         GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err, NULL);
739     g_error_free (err);
740   }
741 }
742 
743 static void
element_cb(G_GNUC_UNUSED GstBus * bus,GstMessage * msg,gpointer user_data)744 element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
745 {
746   GstTranscoder *self = GST_TRANSCODER (user_data);
747   const GstStructure *s;
748 
749   s = gst_message_get_structure (msg);
750   if (gst_structure_has_name (s, "redirect")) {
751     const gchar *new_location;
752 
753     new_location = gst_structure_get_string (s, "new-location");
754     if (!new_location) {
755       const GValue *locations_list, *location_val;
756       guint i, size;
757 
758       locations_list = gst_structure_get_value (s, "locations");
759       size = gst_value_list_get_size (locations_list);
760       for (i = 0; i < size; ++i) {
761         const GstStructure *location_s;
762 
763         location_val = gst_value_list_get_value (locations_list, i);
764         if (!GST_VALUE_HOLDS_STRUCTURE (location_val))
765           continue;
766 
767         location_s = (const GstStructure *) g_value_get_boxed (location_val);
768         if (!gst_structure_has_name (location_s, "redirect"))
769           continue;
770 
771         new_location = gst_structure_get_string (location_s, "new-location");
772         if (new_location)
773           break;
774       }
775     }
776 
777     if (new_location) {
778       GST_FIXME_OBJECT (self, "Handle redirection to '%s'", new_location);
779     }
780   }
781 }
782 
783 
784 static gpointer
gst_transcoder_main(gpointer data)785 gst_transcoder_main (gpointer data)
786 {
787   GstTranscoder *self = GST_TRANSCODER (data);
788   GstBus *bus;
789   GSource *source;
790 
791   GST_TRACE_OBJECT (self, "Starting main thread");
792 
793   g_main_context_push_thread_default (self->context);
794 
795   source = g_idle_source_new ();
796   g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
797       NULL);
798   g_source_attach (source, self->context);
799   g_source_unref (source);
800 
801   self->bus = bus = gst_element_get_bus (self->transcodebin);
802   gst_bus_add_signal_watch (bus);
803 
804   g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
805       self);
806   g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
807       self);
808   g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self);
809   g_signal_connect (G_OBJECT (bus), "message::state-changed",
810       G_CALLBACK (state_changed_cb), self);
811   g_signal_connect (G_OBJECT (bus), "message::clock-lost",
812       G_CALLBACK (clock_lost_cb), self);
813   g_signal_connect (G_OBJECT (bus), "message::duration-changed",
814       G_CALLBACK (duration_changed_cb), self);
815   g_signal_connect (G_OBJECT (bus), "message::latency",
816       G_CALLBACK (latency_cb), self);
817   g_signal_connect (G_OBJECT (bus), "message::request-state",
818       G_CALLBACK (request_state_cb), self);
819   g_signal_connect (G_OBJECT (bus), "message::element",
820       G_CALLBACK (element_cb), self);
821 
822   self->target_state = GST_STATE_NULL;
823   self->current_state = GST_STATE_NULL;
824   self->is_eos = FALSE;
825   self->is_live = FALSE;
826   self->app_state = GST_TRANSCODER_STATE_STOPPED;
827 
828   GST_TRACE_OBJECT (self, "Starting main loop");
829   g_main_loop_run (self->loop);
830   GST_TRACE_OBJECT (self, "Stopped main loop");
831 
832   gst_bus_remove_signal_watch (bus);
833   gst_object_unref (bus);
834 
835   remove_tick_source (self);
836 
837   g_main_context_pop_thread_default (self->context);
838 
839   self->target_state = GST_STATE_NULL;
840   self->current_state = GST_STATE_NULL;
841   if (self->transcodebin) {
842     gst_element_set_state (self->transcodebin, GST_STATE_NULL);
843     g_clear_object (&self->transcodebin);
844   }
845 
846   GST_TRACE_OBJECT (self, "Stopped main thread");
847 
848   return NULL;
849 }
850 
851 static gpointer
gst_transcoder_init_once(G_GNUC_UNUSED gpointer user_data)852 gst_transcoder_init_once (G_GNUC_UNUSED gpointer user_data)
853 {
854   GST_DEBUG_CATEGORY_INIT (gst_transcoder_debug, "gst-transcoder", 0,
855       "GstTranscoder");
856   gst_transcoder_error_quark ();
857 
858   return NULL;
859 }
860 
861 static GstEncodingProfile *
create_encoding_profile(const gchar * pname)862 create_encoding_profile (const gchar * pname)
863 {
864   GstEncodingProfile *profile;
865   GValue value = G_VALUE_INIT;
866 
867   g_value_init (&value, GST_TYPE_ENCODING_PROFILE);
868 
869   if (!gst_value_deserialize (&value, pname)) {
870     g_value_reset (&value);
871 
872     return NULL;
873   }
874 
875   profile = g_value_dup_object (&value);
876   g_value_reset (&value);
877 
878   return profile;
879 }
880 
881 /**
882  * gst_transcoder_new:
883  * @source_uri: The URI of the media stream to transcode
884  * @dest_uri: The URI of the destination of the transcoded stream
885  * @encoding_profile: The serialized #GstEncodingProfile defining the output
886  * format. Have a look at the #GstEncodingProfile documentation to find more
887  * about the serialization format.
888  *
889  * Returns: a new #GstTranscoder instance
890  */
891 GstTranscoder *
gst_transcoder_new(const gchar * source_uri,const gchar * dest_uri,const gchar * encoding_profile)892 gst_transcoder_new (const gchar * source_uri,
893     const gchar * dest_uri, const gchar * encoding_profile)
894 {
895   GstEncodingProfile *profile;
896 
897   g_once (&once, gst_transcoder_init_once, NULL);
898 
899   g_return_val_if_fail (source_uri, NULL);
900   g_return_val_if_fail (dest_uri, NULL);
901   g_return_val_if_fail (encoding_profile, NULL);
902 
903   profile = create_encoding_profile (encoding_profile);
904 
905   return gst_transcoder_new_full (source_uri, dest_uri, profile);
906 }
907 
908 /**
909  * gst_transcoder_new_full:
910  * @source_uri: The URI of the media stream to transcode
911  * @dest_uri: The URI of the destination of the transcoded stream
912  * @profile: The #GstEncodingProfile defining the output format
913  * have a look at the #GstEncodingProfile documentation to find more
914  * about the serialization format.
915  *
916  * Returns: a new #GstTranscoder instance
917  */
918 GstTranscoder *
gst_transcoder_new_full(const gchar * source_uri,const gchar * dest_uri,GstEncodingProfile * profile)919 gst_transcoder_new_full (const gchar * source_uri,
920     const gchar * dest_uri, GstEncodingProfile * profile)
921 {
922   g_once (&once, gst_transcoder_init_once, NULL);
923 
924   g_return_val_if_fail (source_uri, NULL);
925   g_return_val_if_fail (dest_uri, NULL);
926 
927   return g_object_new (GST_TYPE_TRANSCODER, "src-uri", source_uri,
928       "dest-uri", dest_uri, "profile", profile, NULL);
929 }
930 
931 typedef struct
932 {
933   GError *error;
934   GMainLoop *loop;
935 } RunSyncData;
936 
937 static void
_error_cb(RunSyncData * data,GError * error,GstStructure * details)938 _error_cb (RunSyncData * data, GError * error, GstStructure * details)
939 {
940   if (data->error == NULL)
941     data->error = g_error_copy (error);
942 
943   if (data->loop) {
944     g_main_loop_quit (data->loop);
945     data->loop = NULL;
946   }
947 }
948 
949 static void
_done_cb(RunSyncData * data)950 _done_cb (RunSyncData * data)
951 {
952   if (data->loop) {
953     g_main_loop_quit (data->loop);
954     data->loop = NULL;
955   }
956 }
957 
958 /**
959  * gst_transcoder_run:
960  * @self: The GstTranscoder to run
961  * @error: (allow-none): An error to be set if transcoding fails
962  *
963  * Run the transcoder task synchonously. You can connect
964  * to the 'position' signal to get information about the
965  * progress of the transcoding.
966  */
967 gboolean
gst_transcoder_run(GstTranscoder * self,GError ** error)968 gst_transcoder_run (GstTranscoder * self, GError ** error)
969 {
970   RunSyncData data = { 0, };
971   GstTranscoderSignalAdapter *signal_adapter;
972 
973   g_return_val_if_fail (GST_IS_TRANSCODER (self), FALSE);
974 
975   signal_adapter = gst_transcoder_get_signal_adapter (self, NULL);
976 
977   data.loop = g_main_loop_new (NULL, FALSE);
978   g_signal_connect_swapped (signal_adapter, "error", G_CALLBACK (_error_cb),
979       &data);
980   g_signal_connect_swapped (signal_adapter, "done", G_CALLBACK (_done_cb),
981       &data);
982   gst_transcoder_run_async (self);
983 
984   if (!data.error)
985     g_main_loop_run (data.loop);
986 
987   gst_element_set_state (self->transcodebin, GST_STATE_NULL);
988   g_object_unref (signal_adapter);
989 
990   if (data.error) {
991     if (error)
992       g_propagate_error (error, data.error);
993 
994     return FALSE;
995   }
996 
997   return TRUE;
998 }
999 
1000 /**
1001  * gst_transcoder_run_async:
1002  * @self: The GstTranscoder to run
1003  *
1004  * Run the transcoder task asynchronously. You should connect
1005  * to the 'done' signal to be notified about when the
1006  * transcoding is done, and to the 'error' signal to be
1007  * notified about any error.
1008  */
1009 void
gst_transcoder_run_async(GstTranscoder * self)1010 gst_transcoder_run_async (GstTranscoder * self)
1011 {
1012   GstStateChangeReturn state_ret;
1013 
1014   g_return_if_fail (GST_IS_TRANSCODER (self));
1015 
1016   GST_DEBUG_OBJECT (self, "Play");
1017 
1018   if (!self->profile) {
1019     GError *err = g_error_new (GST_TRANSCODER_ERROR,
1020         GST_TRANSCODER_ERROR_FAILED, "No \"profile\" provided");
1021 
1022     api_bus_post_message (self, GST_TRANSCODER_MESSAGE_ERROR,
1023         GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err, NULL);
1024     g_error_free (err);
1025 
1026     return;
1027   }
1028 
1029   self->target_state = GST_STATE_PLAYING;
1030   state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING);
1031 
1032   if (state_ret == GST_STATE_CHANGE_FAILURE) {
1033     GError *err = g_error_new (GST_TRANSCODER_ERROR,
1034         GST_TRANSCODER_ERROR_FAILED, "Could not start transcoding");
1035     api_bus_post_message (self, GST_TRANSCODER_MESSAGE_ERROR,
1036         GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err, NULL);
1037     g_error_free (err);
1038 
1039     return;
1040   } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
1041     self->is_live = TRUE;
1042     GST_DEBUG_OBJECT (self, "Pipeline is live");
1043   }
1044 
1045   return;
1046 }
1047 
1048 static gboolean
gst_transcoder_set_position_update_interval_internal(gpointer user_data)1049 gst_transcoder_set_position_update_interval_internal (gpointer user_data)
1050 {
1051   GstTranscoder *self = user_data;
1052 
1053   GST_OBJECT_LOCK (self);
1054 
1055   if (self->tick_source) {
1056     remove_tick_source (self);
1057     add_tick_source (self);
1058   }
1059 
1060   GST_OBJECT_UNLOCK (self);
1061 
1062   return G_SOURCE_REMOVE;
1063 }
1064 
1065 /**
1066  * gst_transcoder_set_position_update_interval:
1067  * @self: #GstTranscoder instance
1068  * @interval: interval in ms
1069  *
1070  * Set interval in milliseconds between two position-updated signals.
1071  * Pass 0 to stop updating the position.
1072  */
1073 void
gst_transcoder_set_position_update_interval(GstTranscoder * self,guint interval)1074 gst_transcoder_set_position_update_interval (GstTranscoder * self,
1075     guint interval)
1076 {
1077   g_return_if_fail (GST_IS_TRANSCODER (self));
1078   g_return_if_fail (interval <= 10000);
1079 
1080   GST_OBJECT_LOCK (self);
1081   self->position_update_interval_ms = interval;
1082   GST_OBJECT_UNLOCK (self);
1083 
1084   gst_transcoder_set_position_update_interval_internal (self);
1085 }
1086 
1087 /**
1088  * gst_transcoder_get_position_update_interval:
1089  * @self: #GstTranscoder instance
1090  *
1091  * Returns: current position update interval in milliseconds
1092  */
1093 guint
gst_transcoder_get_position_update_interval(GstTranscoder * self)1094 gst_transcoder_get_position_update_interval (GstTranscoder * self)
1095 {
1096   g_return_val_if_fail (GST_IS_TRANSCODER (self),
1097       DEFAULT_POSITION_UPDATE_INTERVAL_MS);
1098 
1099   return self->position_update_interval_ms;
1100 }
1101 
1102 /**
1103  * gst_transcoder_get_source_uri:
1104  * @self: #GstTranscoder instance
1105  *
1106  * Gets the URI of the currently-transcoding stream.
1107  *
1108  * Returns: (transfer full): a string containing the URI of the
1109  * source stream. g_free() after usage.
1110  */
1111 gchar *
gst_transcoder_get_source_uri(GstTranscoder * self)1112 gst_transcoder_get_source_uri (GstTranscoder * self)
1113 {
1114   gchar *val;
1115 
1116   g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI);
1117 
1118   g_object_get (self, "src-uri", &val, NULL);
1119 
1120   return val;
1121 }
1122 
1123 /**
1124  * gst_transcoder_get_dest_uri:
1125  * @self: #GstTranscoder instance
1126  *
1127  * Gets the URI of the destination of the transcoded stream.
1128  *
1129  * Returns: (transfer full): a string containing the URI of the
1130  * destination of the transcoded stream. g_free() after usage.
1131  */
1132 gchar *
gst_transcoder_get_dest_uri(GstTranscoder * self)1133 gst_transcoder_get_dest_uri (GstTranscoder * self)
1134 {
1135   gchar *val;
1136 
1137   g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI);
1138 
1139   g_object_get (self, "dest-uri", &val, NULL);
1140 
1141   return val;
1142 }
1143 
1144 /**
1145  * gst_transcoder_get_position:
1146  * @self: #GstTranscoder instance
1147  *
1148  * Returns: the absolute position time, in nanoseconds, of the
1149  * transcoding stream.
1150  */
1151 GstClockTime
gst_transcoder_get_position(GstTranscoder * self)1152 gst_transcoder_get_position (GstTranscoder * self)
1153 {
1154   GstClockTime val;
1155 
1156   g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_POSITION);
1157 
1158   g_object_get (self, "position", &val, NULL);
1159 
1160   return val;
1161 }
1162 
1163 /**
1164  * gst_transcoder_get_duration:
1165  * @self: #GstTranscoder instance
1166  *
1167  * Retrieves the duration of the media stream that self represents.
1168  *
1169  * Returns: the duration of the transcoding media stream, in
1170  * nanoseconds.
1171  */
1172 GstClockTime
gst_transcoder_get_duration(GstTranscoder * self)1173 gst_transcoder_get_duration (GstTranscoder * self)
1174 {
1175   GstClockTime val;
1176 
1177   g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_DURATION);
1178 
1179   g_object_get (self, "duration", &val, NULL);
1180 
1181   return val;
1182 }
1183 
1184 /**
1185  * gst_transcoder_get_pipeline:
1186  * @self: #GstTranscoder instance
1187  *
1188  * Returns: (transfer full): The internal uritranscodebin instance
1189  */
1190 GstElement *
gst_transcoder_get_pipeline(GstTranscoder * self)1191 gst_transcoder_get_pipeline (GstTranscoder * self)
1192 {
1193   GstElement *val;
1194 
1195   g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL);
1196 
1197   g_object_get (self, "pipeline", &val, NULL);
1198 
1199   return val;
1200 }
1201 
1202 /**
1203  * gst_transcoder_get_avoid_reencoding:
1204  * @self: The #GstTranscoder to check whether reencoding is avoided or not.
1205  *
1206  * Returns: %TRUE if the transcoder tries to avoid reencoding streams where
1207  * reencoding is not strictly needed, %FALSE otherwise.
1208  */
1209 gboolean
gst_transcoder_get_avoid_reencoding(GstTranscoder * self)1210 gst_transcoder_get_avoid_reencoding (GstTranscoder * self)
1211 {
1212   gboolean val;
1213 
1214   g_return_val_if_fail (GST_IS_TRANSCODER (self), FALSE);
1215 
1216   g_object_get (self->transcodebin, "avoid-reencoding", &val, NULL);
1217 
1218   return val;
1219 }
1220 
1221 /**
1222  * gst_transcoder_set_avoid_reencoding:
1223  * @self: The #GstTranscoder to set whether reencoding should be avoided or not.
1224  * @avoid_reencoding: %TRUE if the transcoder should try to avoid reencoding
1225  * streams where * reencoding is not strictly needed, %FALSE otherwise.
1226  */
1227 void
gst_transcoder_set_avoid_reencoding(GstTranscoder * self,gboolean avoid_reencoding)1228 gst_transcoder_set_avoid_reencoding (GstTranscoder * self,
1229     gboolean avoid_reencoding)
1230 {
1231   g_return_if_fail (GST_IS_TRANSCODER (self));
1232 
1233   g_object_set (self->transcodebin, "avoid-reencoding", avoid_reencoding, NULL);
1234 }
1235 
1236 /**
1237  * gst_transcoder_error_get_name:
1238  * @error: a #GstTranscoderError
1239  *
1240  * Gets a string representing the given error.
1241  *
1242  * Returns: (transfer none): a string with the given error.
1243  */
1244 const gchar *
gst_transcoder_error_get_name(GstTranscoderError error)1245 gst_transcoder_error_get_name (GstTranscoderError error)
1246 {
1247   switch (error) {
1248     case GST_TRANSCODER_ERROR_FAILED:
1249       return "failed";
1250   }
1251 
1252   g_assert_not_reached ();
1253   return NULL;
1254 }
1255 
1256 /**
1257  * gst_transcoder_get_message_bus:
1258  * @transcoder: #GstTranscoder instance
1259  *
1260  * GstTranscoder API exposes a #GstBus instance which purpose is to provide data
1261  * structures representing transcoder-internal events in form of #GstMessage-s of
1262  * type GST_MESSAGE_APPLICATION.
1263  *
1264  * Each message carries a "transcoder-message" field of type #GstTranscoderMessage.
1265  * Further fields of the message data are specific to each possible value of
1266  * that enumeration.
1267  *
1268  * Applications can consume the messages asynchronously within their own
1269  * event-loop / UI-thread etc. Note that in case the application does not
1270  * consume the messages, the bus will accumulate these internally and eventually
1271  * fill memory. To avoid that, the bus has to be set "flushing".
1272  *
1273  * Returns: (transfer full): The transcoder message bus instance
1274  *
1275  * Since: 1.20
1276  */
1277 GstBus *
gst_transcoder_get_message_bus(GstTranscoder * self)1278 gst_transcoder_get_message_bus (GstTranscoder * self)
1279 {
1280   g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL);
1281 
1282   return g_object_ref (self->api_bus);
1283 }
1284 
1285 /**
1286  * gst_transcoder_get_sync_signal_adapter:
1287  * @self: (transfer none): #GstTranscoder instance to emit signals synchronously
1288  * for.
1289  *
1290  * Gets the #GstTranscoderSignalAdapter attached to @self to emit signals from
1291  * its thread of emission.
1292  *
1293  * Returns: (transfer full): The #GstTranscoderSignalAdapter to connect signal
1294  * handlers to.
1295  *
1296  * Since: 1.20
1297  */
1298 GstTranscoderSignalAdapter *
gst_transcoder_get_sync_signal_adapter(GstTranscoder * self)1299 gst_transcoder_get_sync_signal_adapter (GstTranscoder * self)
1300 {
1301   g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL);
1302 
1303   GST_OBJECT_LOCK (self);
1304   if (!self->sync_signal_adapter)
1305     self->sync_signal_adapter =
1306         gst_transcoder_signal_adapter_new_sync_emit (self);
1307   GST_OBJECT_UNLOCK (self);
1308 
1309   return g_object_ref (self->sync_signal_adapter);
1310 }
1311 
1312 /**
1313  * gst_transcoder_get_signal_adapter:
1314  * @self: (transfer none): #GstTranscoder instance to emit signals for.
1315  * @context: (nullable): A #GMainContext on which the main-loop will process
1316  *                       transcoder bus messages on. Can be NULL (thread-default
1317  *                       context will be used then).
1318  *
1319  * Gets the #GstTranscoderSignalAdapter attached to @self if it is attached to
1320  * the right #GMainContext. If no #GstTranscoderSignalAdapter has been created
1321  * yet, it will be created and returned, other calls will return that same
1322  * adapter until it is destroyed, at which point, a new one can be attached the
1323  * same way.
1324  *
1325  * Returns: (transfer full)(nullable): The #GstTranscoderSignalAdapter to
1326  * connect signal handlers to.
1327  *
1328  * Since: 1.20
1329  */
1330 GstTranscoderSignalAdapter *
gst_transcoder_get_signal_adapter(GstTranscoder * self,GMainContext * context)1331 gst_transcoder_get_signal_adapter (GstTranscoder * self, GMainContext * context)
1332 {
1333   g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL);
1334 
1335   if (!context)
1336     context = g_main_context_get_thread_default ();
1337   if (!context)
1338     context = g_main_context_default ();
1339 
1340   GST_OBJECT_LOCK (self);
1341   if (!self->signal_adapter) {
1342     self->signal_adapter = gst_transcoder_signal_adapter_new (self, context);
1343   } else if (g_source_get_context (self->signal_adapter->source) != context) {
1344     GST_WARNING_OBJECT (self, "Trying to get an adapter for a different "
1345         "GMainContext than the one attached, this is not possible");
1346     GST_OBJECT_UNLOCK (self);
1347 
1348     return NULL;
1349   }
1350   GST_OBJECT_UNLOCK (self);
1351 
1352   return g_object_ref (self->signal_adapter);
1353 }
1354 
1355 /**
1356  * gst_transcoder_message_get_name:
1357  * @message: a #GstTranscoderMessage
1358  *
1359  * Returns (transfer none): The message name
1360  *
1361  * Since: 1.20
1362  */
1363 const gchar *
gst_transcoder_message_get_name(GstTranscoderMessage message)1364 gst_transcoder_message_get_name (GstTranscoderMessage message)
1365 {
1366   GEnumClass *enum_class;
1367   GEnumValue *enum_value;
1368   enum_class = g_type_class_ref (GST_TYPE_TRANSCODER_MESSAGE);
1369   enum_value = g_enum_get_value (enum_class, message);
1370   g_assert (enum_value != NULL);
1371   g_type_class_unref (enum_class);
1372   return enum_value->value_name;
1373 }
1374 
1375 
1376 #define PARSE_MESSAGE_FIELD(msg, field, value_type, value) G_STMT_START { \
1377     const GstStructure *data = NULL;                                      \
1378     g_return_if_fail (gst_transcoder_is_transcoder_message (msg));                \
1379     data = gst_message_get_structure (msg);                               \
1380     if (!gst_structure_get (data, field, value_type, value, NULL)) {      \
1381       g_error ("Could not parse field from structure: %s", field);        \
1382     }                                                                     \
1383 } G_STMT_END
1384 
1385 /**
1386  * gst_transcoder_is_transcoder_message:
1387  * @msg: A #GstMessage
1388  *
1389  * Returns: A #gboolean indicating whether the passes message represents a #GstTranscoder message or not.
1390  *
1391  * Since: 1.20
1392  */
1393 gboolean
gst_transcoder_is_transcoder_message(GstMessage * msg)1394 gst_transcoder_is_transcoder_message (GstMessage * msg)
1395 {
1396   const GstStructure *data = NULL;
1397   g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE);
1398 
1399   data = gst_message_get_structure (msg);
1400   g_return_val_if_fail (data, FALSE);
1401 
1402   return g_str_equal (gst_structure_get_name (data),
1403       GST_TRANSCODER_MESSAGE_DATA);
1404 }
1405 
1406 /**
1407  * gst_transcoder_message_parse_duration:
1408  * @msg: A #GstMessage
1409  * @duration: (out): the resulting duration
1410  *
1411  * Parse the given duration @msg and extract the corresponding #GstClockTime
1412  *
1413  * Since: 1.20
1414  */
1415 void
gst_transcoder_message_parse_duration(GstMessage * msg,GstClockTime * duration)1416 gst_transcoder_message_parse_duration (GstMessage * msg,
1417     GstClockTime * duration)
1418 {
1419   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_DURATION,
1420       GST_TYPE_CLOCK_TIME, duration);
1421 }
1422 
1423 /**
1424  * gst_transcoder_message_parse_position:
1425  * @msg: A #GstMessage
1426  * @position: (out): the resulting position
1427  *
1428  * Parse the given position @msg and extract the corresponding #GstClockTime
1429  *
1430  * Since: 1.20
1431  */
1432 void
gst_transcoder_message_parse_position(GstMessage * msg,GstClockTime * position)1433 gst_transcoder_message_parse_position (GstMessage * msg,
1434     GstClockTime * position)
1435 {
1436   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_POSITION,
1437       GST_TYPE_CLOCK_TIME, position);
1438 }
1439 
1440 /**
1441  * gst_transcoder_message_parse_state:
1442  * @msg: A #GstMessage
1443  * @state: (out): the resulting state
1444  *
1445  * Parse the given state @msg and extract the corresponding #GstTranscoderState
1446  *
1447  * Since: 1.20
1448  */
1449 void
gst_transcoder_message_parse_state(GstMessage * msg,GstTranscoderState * state)1450 gst_transcoder_message_parse_state (GstMessage * msg,
1451     GstTranscoderState * state)
1452 {
1453   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_STATE,
1454       GST_TYPE_TRANSCODER_STATE, state);
1455 }
1456 
1457 /**
1458  * gst_transcoder_message_parse_error:
1459  * @msg: A #GstMessage
1460  * @error: (out): the resulting error
1461  * @details: (out): (transfer none): A GstStructure containing extra details about the error
1462  *
1463  * Parse the given error @msg and extract the corresponding #GError
1464  *
1465  * Since: 1.20
1466  */
1467 void
gst_transcoder_message_parse_error(GstMessage * msg,GError * error,GstStructure ** details)1468 gst_transcoder_message_parse_error (GstMessage * msg, GError * error,
1469     GstStructure ** details)
1470 {
1471   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_ERROR, G_TYPE_ERROR,
1472       error);
1473   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_ISSUE_DETAILS,
1474       GST_TYPE_STRUCTURE, details);
1475 }
1476 
1477 /**
1478  * gst_transcoder_message_parse_warning:
1479  * @msg: A #GstMessage
1480  * @error: (out): the resulting warning
1481  * @details: (out): (transfer none): A GstStructure containing extra details about the warning
1482  *
1483  * Parse the given error @msg and extract the corresponding #GError warning
1484  *
1485  * Since: 1.20
1486  */
1487 void
gst_transcoder_message_parse_warning(GstMessage * msg,GError * error,GstStructure ** details)1488 gst_transcoder_message_parse_warning (GstMessage * msg, GError * error,
1489     GstStructure ** details)
1490 {
1491   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_WARNING, G_TYPE_ERROR,
1492       error);
1493   PARSE_MESSAGE_FIELD (msg, GST_TRANSCODER_MESSAGE_DATA_ISSUE_DETAILS,
1494       GST_TYPE_STRUCTURE, details);
1495 }
1496 
1497 /**
1498  * gst_transcoder_state_get_name:
1499  * @state: a #GstTranscoderState
1500  *
1501  * Gets a string representing the given state.
1502  *
1503  * Returns: (transfer none): a string with the name of the state.
1504  *
1505  * Since: 1.20
1506  */
1507 const gchar *
gst_transcoder_state_get_name(GstTranscoderState state)1508 gst_transcoder_state_get_name (GstTranscoderState state)
1509 {
1510   switch (state) {
1511     case GST_TRANSCODER_STATE_STOPPED:
1512       return "stopped";
1513     case GST_TRANSCODER_STATE_PAUSED:
1514       return "paused";
1515     case GST_TRANSCODER_STATE_PLAYING:
1516       return "playing";
1517   }
1518 
1519   g_assert_not_reached ();
1520   return NULL;
1521 }
1522