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