documentation: fixed a heap o' typos
[platform/upstream/gstreamer.git] / tools / gst-transcoder.c
1 /* GStreamer
2  *
3  * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include <string.h>
22
23 #include "utils.h"
24 #include <gst/transcoder/gsttranscoder.h>
25
26 static const gchar *HELP_SUMMARY =
27     "gst-transcoder-1.0 transcodes a stream defined by its first <input-uri>\n"
28     "argument to the place defined by its second <output-uri> argument\n"
29     "into the format described in its third <encoding-format> argument,\n"
30     "or using the given <output-uri> file extension.\n"
31     "\n"
32     "The <encoding-format> argument:\n"
33     "===============================\n"
34     "\n"
35     "If the encoding format is not defined, it will be guessed with\n"
36     "the given <output-uri> file extension."
37     "\n"
38     "<encoding-format> describe the media format into which the\n"
39     "input stream is going to be transcoded. We have two different\n"
40     "ways of describing the format:\n"
41     "\n"
42     "GstEncodingProfile serialization format\n"
43     "---------------------------------------\n"
44     "\n"
45     "GStreamer encoding profiles can be described with a quite extensive\n"
46     "syntax which is described in the GstEncodingProfile documentation.\n"
47     "\n"
48     "The simple case looks like:\n"
49     "\n"
50     "    muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps\n"
51     "\n"
52     "Name and category of serialized GstEncodingTarget\n"
53     "-------------------------------------------------\n"
54     "\n"
55     "Encoding targets describe well known formats which\n"
56     "those are provided in '.gep' files. You can list\n"
57     "available ones using the `--list-targets` argument.\n";
58
59 typedef struct
60 {
61   gint cpu_usage, rate;
62   gboolean list;
63   GstEncodingProfile *profile;
64   gchar *src_uri, *dest_uri, *encoding_format, *size;
65   gchar *framerate;
66 } Settings;
67
68 static void
69 position_updated_cb (GstTranscoder * transcoder, GstClockTime pos)
70 {
71   GstClockTime dur = -1;
72   gchar status[64] = { 0, };
73
74   g_object_get (transcoder, "duration", &dur, NULL);
75
76   memset (status, ' ', sizeof (status) - 1);
77
78   if (pos != -1 && dur > 0 && dur != -1) {
79     gchar dstr[32], pstr[32];
80
81     /* FIXME: pretty print in nicer format */
82     g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
83     pstr[9] = '\0';
84     g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
85     dstr[9] = '\0';
86     g_print ("%s / %s %s\r", pstr, dstr, status);
87   }
88 }
89
90 static GList *
91 get_profiles_of_type (GstEncodingProfile * profile, GType profile_type)
92 {
93   GList *tmp, *profiles = NULL;
94
95   if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
96     for (tmp = (GList *)
97         gst_encoding_container_profile_get_profiles
98         (GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next) {
99       if (G_OBJECT_TYPE (tmp->data) == profile_type)
100         profiles = g_list_prepend (profiles, tmp->data);
101     }
102   } else if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) {
103     profiles = g_list_prepend (profiles, profile);
104   }
105
106   return profiles;
107 }
108
109 static gboolean
110 set_video_settings (Settings * settings)
111 {
112   GList *video_profiles, *tmp;
113   gchar *p, *tmpstr, **vsize;
114   gint width = 0, height = 0;
115   GValue framerate = G_VALUE_INIT;
116
117   if (!settings->size && !settings->framerate)
118     return TRUE;
119
120   if (settings->size) {
121     p = tmpstr = g_strdup (settings->size);
122
123     for (; *p; ++p)
124       *p = g_ascii_tolower (*p);
125
126     vsize = g_strsplit (tmpstr, "x", -1);
127     g_free (tmpstr);
128
129     if (!vsize[1] || vsize[2]) {
130       g_strfreev (vsize);
131       error ("Video size should be in the form: WxH, got %s", settings->size);
132
133       return FALSE;
134     }
135
136     width = g_ascii_strtoull (vsize[0], NULL, 0);
137     height = g_ascii_strtoull (vsize[1], NULL, 10);
138     g_strfreev (vsize);
139   }
140
141   if (settings->framerate) {
142     g_value_init (&framerate, GST_TYPE_FRACTION);
143     if (!gst_value_deserialize (&framerate, settings->framerate)) {
144       error ("Video framerate should be either a fraction or an integer"
145           " not: %s", settings->framerate);
146       return FALSE;
147     }
148   }
149
150   video_profiles = get_profiles_of_type (settings->profile,
151       GST_TYPE_ENCODING_VIDEO_PROFILE);
152   for (tmp = video_profiles; tmp; tmp = tmp->next) {
153     GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data);
154
155     if (!rest)
156       rest = gst_caps_new_empty_simple ("video/x-raw");
157     else
158       rest = gst_caps_copy (rest);
159
160     if (settings->size) {
161       gst_caps_set_simple (rest, "width", G_TYPE_INT, width,
162           "height", G_TYPE_INT, height, NULL);
163     }
164     if (settings->framerate)
165       gst_caps_set_value (rest, "framerate", &framerate);
166
167     gst_encoding_profile_set_restriction (tmp->data, rest);
168   }
169
170   return TRUE;
171 }
172
173 static gboolean
174 set_audio_settings (Settings * settings)
175 {
176   GList *audio_profiles, *tmp;
177
178   if (settings->rate < 0)
179     return TRUE;
180
181   audio_profiles =
182       get_profiles_of_type (settings->profile, GST_TYPE_ENCODING_AUDIO_PROFILE);
183   for (tmp = audio_profiles; tmp; tmp = tmp->next) {
184     GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data);
185
186     if (!rest)
187       rest = gst_caps_new_empty_simple ("audio/x-raw");
188     else
189       rest = gst_caps_copy (rest);
190
191     gst_caps_set_simple (rest, "rate", G_TYPE_INT, settings->rate, NULL);
192     gst_encoding_profile_set_restriction (tmp->data, rest);
193   }
194
195   return TRUE;
196 }
197
198 static void
199 list_encoding_targets (void)
200 {
201   GList *tmp, *targets = gst_encoding_list_all_targets (NULL);
202
203   for (tmp = targets; tmp; tmp = tmp->next) {
204     GstEncodingTarget *target = tmp->data;
205     GList *usable_profiles = get_usable_profiles (target);
206
207     if (usable_profiles) {
208       GList *tmpprof;
209
210       g_print ("\n%s (%s): %s\n * Profiles:\n",
211           gst_encoding_target_get_name (target),
212           gst_encoding_target_get_category (target),
213           gst_encoding_target_get_description (target));
214
215       for (tmpprof = usable_profiles; tmpprof; tmpprof = tmpprof->next)
216         g_print ("     - %s: %s",
217             gst_encoding_profile_get_name (tmpprof->data),
218             gst_encoding_profile_get_description (tmpprof->data));
219
220       g_print ("\n");
221       g_list_free (usable_profiles);
222     }
223   }
224
225   g_list_free_full (targets, (GDestroyNotify) g_object_unref);
226 }
227
228 static void
229 _error_cb (GstTranscoder * transcoder, GError * err, GstStructure * details)
230 {
231   if (g_error_matches (err, GST_CORE_ERROR, GST_CORE_ERROR_PAD)) {
232     GstPadLinkReturn lret;
233     GType type;
234
235     if (details && gst_structure_get (details, "linking-error",
236             GST_TYPE_PAD_LINK_RETURN, &lret,
237             "msg-source-type", G_TYPE_GTYPE, &type, NULL) &&
238         type == g_type_from_name ("GstTranscodeBin")) {
239       error ("\nCould not setup transcoding pipeline,"
240           " make sure that your transcoding format parameters"
241           " are compatible with the input stream.");
242
243       return;
244     }
245   }
246
247   error ("\nFAILURE: %s", err->message);
248 }
249
250 static void
251 _warning_cb (GstTranscoder * transcoder, GError * error, GstStructure * details)
252 {
253   gboolean cant_encode;
254   GstCaps *caps = NULL;
255   gchar *stream_id = NULL;
256
257   if (details && gst_structure_get (details, "can-t-encode-stream",
258           G_TYPE_BOOLEAN, &cant_encode, "stream-caps", GST_TYPE_CAPS,
259           &caps, "stream-id", G_TYPE_STRING, &stream_id, NULL)) {
260     gchar *source_uri = gst_transcoder_get_source_uri (transcoder);
261
262     warn ("WARNING: Input stream %s: WON'T BE ENCODED.\n"
263         "Make sure the encoding settings are valid and that"
264         " any preset you set actually exists.\n"
265         "For more information about that stream, you can inspect"
266         " the source stream with:\n\n"
267         "    gst-discoverer-1.0 -v %s\n", stream_id, source_uri);
268     gst_caps_unref (caps);
269     g_free (stream_id);;
270     g_free (source_uri);;
271
272     return;
273   }
274   warn ("Got warning: %s", error->message);
275 }
276
277 int
278 main (int argc, char *argv[])
279 {
280   gint res = 0;
281   GError *err = NULL;
282   GstTranscoder *transcoder;
283   GOptionContext *ctx;
284   Settings settings = {
285     .cpu_usage = 100,
286     .rate = -1,
287     .encoding_format = NULL,
288     .size = NULL,
289     .framerate = NULL,
290   };
291   GOptionEntry options[] = {
292     {"cpu-usage", 'c', 0, G_OPTION_ARG_INT, &settings.cpu_usage,
293         "The CPU usage to target in the transcoding process", NULL},
294     {"list-targets", 'l', G_OPTION_ARG_NONE, 0, &settings.list,
295         "List all encoding targets", NULL},
296     {"size", 's', 0, G_OPTION_ARG_STRING, &settings.size,
297         "set frame size (WxH or abbreviation)", NULL},
298     {"audio-rate", 'r', 0, G_OPTION_ARG_INT, &settings.rate,
299         "set audio sampling rate (in Hz)", NULL},
300     {"framerate", 'f', 0, G_OPTION_ARG_STRING, &settings.framerate,
301         "set video framerate as a fraction (24/1 for 24fps)"
302           " or a single number (24 for 24fps))", NULL},
303     {"video-encoder", 'v', 0, G_OPTION_ARG_STRING, &settings.size,
304         "The video encoder to use.", NULL},
305     {NULL}
306   };
307
308   g_set_prgname ("gst-transcoder");
309
310   ctx = g_option_context_new ("<source uri> <destination uri> "
311       "[<encoding format>[/<encoding profile name>]]");
312   g_option_context_set_summary (ctx, HELP_SUMMARY);
313
314   g_option_context_add_main_entries (ctx, options, NULL);
315   g_option_context_add_group (ctx, gst_init_get_option_group ());
316
317   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
318     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
319     g_clear_error (&err);
320     g_option_context_free (ctx);
321     return 1;
322   }
323   gst_pb_utils_init ();
324
325   if (settings.list) {
326     list_encoding_targets ();
327     return 0;
328   }
329
330   if (argc < 3 || argc > 4) {
331     g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
332     g_option_context_free (ctx);
333
334     return -1;
335   }
336   g_option_context_free (ctx);
337
338   settings.src_uri = ensure_uri (argv[1]);
339   settings.dest_uri = ensure_uri (argv[2]);
340
341   if (argc == 3) {
342     settings.encoding_format = get_file_extension (settings.dest_uri);
343
344     if (!settings.encoding_format)
345       goto no_extension;
346   } else {
347     settings.encoding_format = argv[3];
348   }
349
350   settings.profile = create_encoding_profile (settings.encoding_format);
351
352   if (!settings.profile) {
353     error ("Could not find any encoding format for %s\n",
354         settings.encoding_format);
355     warn ("You can list available targets using %s --list-targets", argv[0]);
356     res = 1;
357     goto done;
358   }
359
360   g_print ("Encoding to:\n\n");
361   describe_encoding_profile (settings.profile);
362   if (!set_video_settings (&settings)) {
363     res = -1;
364     goto done;
365   }
366
367   if (!set_audio_settings (&settings)) {
368     res = -1;
369     goto done;
370   }
371
372   transcoder = gst_transcoder_new_full (settings.src_uri, settings.dest_uri,
373       settings.profile, NULL);
374   gst_transcoder_set_avoid_reencoding (transcoder, TRUE);
375
376   gst_transcoder_set_cpu_usage (transcoder, settings.cpu_usage);
377   g_signal_connect (transcoder, "position-updated",
378       G_CALLBACK (position_updated_cb), NULL);
379   g_signal_connect (transcoder, "warning", G_CALLBACK (_warning_cb), NULL);
380   g_signal_connect (transcoder, "error", G_CALLBACK (_error_cb), NULL);
381
382   g_assert (transcoder);
383
384   ok ("Starting transcoding...");
385   gst_transcoder_run (transcoder, &err);
386   if (!err)
387     ok ("\nDONE.");
388
389 done:
390   g_free (settings.dest_uri);
391   g_free (settings.src_uri);
392
393   return res;
394
395 no_extension:
396   error ("No <encoding-format> specified and no extension"
397       " available in the output target: %s", settings.dest_uri);
398   res = 1;
399
400   goto done;
401 }