bdremux initial commit
[bdremux.git] / bdremux.c
1 /***************************************************************************
2  *   Copyright (C) 2011 by Andreas Frisch                                  *
3  *   fraxinas@opendreambox.org                                             *
4  *                                                                         *
5  * This program is licensed under the Creative Commons                     *
6  * Attribution-NonCommercial-ShareAlike 3.0 Unported                       *
7  * License. To view a copy of this license, visit                          *
8  * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to   *
9  * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA.   *
10  *                                                                         *
11  * Alternatively, this program may be distributed and executed on          *
12  * hardware which is licensed by Dream Multimedia GmbH.                    *
13  *                                                                         *
14  * This program is NOT free software. It is open source, you are allowed   *
15  * to modify it (if you keep the license), but it may not be commercially  *
16  * distributed other than under the conditions noted above.                *
17  *                                                                         *
18  ***************************************************************************/
19
20 // gcc -Wall -g `pkg-config gstreamer-0.10 --cflags --libs` bdremux.c -o bdremux
21
22 #include <gst/gst.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <fcntl.h>
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gprintf.h>
29 #include <getopt.h>
30
31 #include <byteswap.h>
32 #include <netinet/in.h>
33
34 #ifndef BYTE_ORDER
35 #error no byte order defined!
36 #endif
37
38 #define CLOCK_BASE 9LL
39 #define CLOCK_FREQ (CLOCK_BASE * 10000)
40
41 #define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \
42             GST_MSECOND/10, CLOCK_BASE))
43 #define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \
44             CLOCK_BASE, GST_MSECOND/10))
45
46 #define MAX_PIDS 8
47 #define DEFAULT_QUEUE_SIZE 48*1024*1024
48
49 GST_DEBUG_CATEGORY (bdremux_debug);
50 #define GST_CAT_DEFAULT bdremux_debug
51
52 typedef struct _App App;
53
54 typedef struct _Segment
55 {
56   int index;
57   guint64 in_pts;
58   guint64 out_pts;
59 } segment_t;
60
61 struct _App
62 {
63   gchar *in_filename;
64   gchar *out_filename;
65   gchar *cuts_filename;
66   gchar *epmap_filename;
67   gboolean enable_indexing;
68   gboolean enable_cutlist;
69   GstElement *pipeline;
70   GstElement *filesrc;
71   GstElement *tsdemux;
72   GstElement *queue;
73   GstElement *videoparser;
74   GstElement *audioparsers[MAX_PIDS];
75   GstElement *m2tsmux;
76   GstElement *filesink;
77   GstIndex *index;
78   gulong buffer_handler_id;
79   gint a_source_pids[MAX_PIDS], a_sink_pids[MAX_PIDS];
80   guint no_source_pids, no_sink_pids;
81   guint requested_pid_count;
82   gboolean auto_pids;
83
84   GMainLoop *loop;
85   gboolean is_seekable;
86   int current_segment;
87   int segment_count;
88   segment_t *seek_segments;
89
90   guint queue_cb_handler_id;
91   guint queue_size;
92   
93   FILE *f_epmap;
94 };
95
96 App s_app;
97
98 static void
99 bdremux_errout(gchar *string)
100 {
101   g_print("ERROR: %s\n", string);
102   GST_ERROR (string);
103   exit(1);
104 }
105
106 static gboolean
107 load_cutlist (App * app)
108 {
109   FILE *f;
110   int segment_i = 0;
111
112   f = fopen (app->cuts_filename, "rb");
113
114   if (f) {
115     GST_INFO ("cutfile found! loading cuts...");
116     while (1) {
117       unsigned long long where;
118       unsigned int what;
119
120       if (!fread (&where, sizeof (where), 1, f))
121         break;
122       if (!fread (&what, sizeof (what), 1, f))
123         break;
124
125 #if BYTE_ORDER == LITTLE_ENDIAN
126       where = bswap_64 (where);
127 #endif
128       what = ntohl (what);
129       GST_DEBUG ("where= %lld, what=%i", where, what);
130       if (what > 3)
131         break;
132
133       if (what == 0) {
134         app->segment_count++;
135         app->seek_segments =
136             (segment_t *) realloc (app->seek_segments,
137             app->segment_count * sizeof (segment_t));
138         app->seek_segments[segment_i].index = segment_i;
139         app->seek_segments[segment_i].in_pts = where;
140         app->seek_segments[segment_i].out_pts = -1;
141       }
142       if (what == 1 && segment_i < app->segment_count) {
143         app->seek_segments[segment_i].out_pts = where;
144         segment_i++;
145       }
146     }
147     fclose (f);
148   } else
149     GST_WARNING ("cutfile not found!");
150 // 
151   return TRUE;
152 }
153
154 static gboolean
155 do_seek (App * app)
156 {
157   gint64 in_pos, out_pos;
158   gfloat rate = 1.0;
159   GstFormat fmt = GST_FORMAT_TIME;
160   GstSeekFlags flags = 0;
161   int ret;
162
163   if (app->current_segment >= app->segment_count) {
164     GST_WARNING ("seek segment not found!");
165     return FALSE;
166   }
167
168   GST_INFO ("do_seek...");
169   flags |= GST_SEEK_FLAG_FLUSH;
170 //      flags |= GST_SEEK_FLAG_ACCURATE;
171   flags |= GST_SEEK_FLAG_KEY_UNIT;
172   flags |= GST_SEEK_FLAG_SEGMENT;
173
174   gst_element_query_position ((app->pipeline), &fmt, &in_pos);
175   GST_DEBUG ("do_seek::initial gst_element_query_position = %lld ms",
176       in_pos / 1000000);
177
178   in_pos =
179       MPEGTIME_TO_GSTTIME (app->seek_segments[app->current_segment].in_pts);
180   GST_DEBUG ("do_seek::in_time for segment %i = %lld ms", app->current_segment,
181       in_pos / 1000000);
182
183   out_pos = -1;
184 //       MPEGTIME_TO_GSTTIME (app->seek_segments[app->current_segment].out_pts);
185   GST_DEBUG ("do_seek::out_time for segment %i = %lld ms", app->current_segment,
186       out_pos / 1000000);
187
188   ret = gst_element_seek ((app->pipeline), rate, GST_FORMAT_TIME, flags,
189       GST_SEEK_TYPE_SET, in_pos, GST_SEEK_TYPE_SET, out_pos);
190
191   gst_element_query_position ((app->pipeline), &fmt, &in_pos);
192   GST_DEBUG
193       ("do_seek::seek command returned %i. new gst_element_query_position = %lld ms",
194       ret, in_pos / 1000000);
195
196   if (ret)
197     app->current_segment++;
198
199   return ret;
200 }
201
202 static gboolean
203 bus_message (GstBus * bus, GstMessage * message, App * app)
204 {
205   gchar *sourceName;
206   GstObject *source;
207   gchar *string;
208   GstState current_state;
209
210   if (!message)
211     return FALSE;
212   source = GST_MESSAGE_SRC (message);
213   if (!GST_IS_OBJECT (source))
214     return FALSE;
215   sourceName = gst_object_get_name (source);
216
217   if (gst_message_get_structure (message))
218     string = gst_structure_to_string (gst_message_get_structure (message));
219   else
220     string = g_strdup (GST_MESSAGE_TYPE_NAME (message));
221   GST_DEBUG("gst_message from %s: %s", sourceName, string);
222   g_free (string);
223
224   switch (GST_MESSAGE_TYPE (message)) {
225     case GST_MESSAGE_ERROR:
226     {
227       GError *gerror;
228       gchar *debug;
229
230       gst_message_parse_error (message, &gerror, &debug);
231       gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
232       g_error_free (gerror);
233       g_free (debug);
234
235       g_main_loop_quit (app->loop);
236       break;
237     }
238     case GST_MESSAGE_WARNING:
239     {
240       GError *gerror;
241       gchar *debug;
242
243       gst_message_parse_warning (message, &gerror, &debug);
244       gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
245       g_error_free (gerror);
246       g_free (debug);
247
248 //       g_main_loop_quit (app->loop);
249       break;
250     }
251     case GST_MESSAGE_EOS:
252       g_message ("received EOS");
253       g_main_loop_quit (app->loop);
254       break;
255     case GST_MESSAGE_ASYNC_DONE:
256       break;
257     case GST_MESSAGE_ELEMENT:
258     {
259       const GstStructure *msgstruct = gst_message_get_structure (message);
260       if (msgstruct) {
261         const gchar *eventname = gst_structure_get_name (msgstruct);
262         if (!strcmp (eventname, "seekable"))
263           app->is_seekable = TRUE;
264       }
265       break;
266     }
267     case GST_MESSAGE_STATE_CHANGED:
268     {
269       GstState old_state, new_state;
270       GstStateChange transition;
271       if (GST_MESSAGE_SRC (message) != GST_OBJECT (app->tsdemux))
272         break;
273
274       gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
275       transition = (GstStateChange) GST_STATE_TRANSITION (old_state, new_state);
276
277       switch (transition) {
278         case GST_STATE_CHANGE_NULL_TO_READY:
279           break;
280         case GST_STATE_CHANGE_READY_TO_PAUSED:
281           break;
282         case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
283         {
284
285         }
286           break;
287         case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
288           break;
289         case GST_STATE_CHANGE_PAUSED_TO_READY:
290           break;
291         case GST_STATE_CHANGE_READY_TO_NULL:
292           break;
293       }
294       break;
295     }
296     case GST_MESSAGE_SEGMENT_DONE:
297     {
298       GST_DEBUG ("GST_MESSAGE_SEGMENT_DONE!!!");
299       do_seek (app);
300     }
301     default:
302       break;
303   }
304   gst_element_get_state (app->pipeline, &current_state, NULL, 0);
305   if (app->current_segment == 0 && app->segment_count /*&& app->is_seekable*/
306       && current_state == GST_STATE_PLAYING)
307     do_seek (app);
308   GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(app->pipeline),GST_DEBUG_GRAPH_SHOW_ALL,"bdremux_pipelinegraph_message");
309   return TRUE;
310 }
311
312
313 static void
314 entry_added (GstIndex * index, GstIndexEntry * entry, App * app)
315 {
316   switch (entry->type) {
317     case GST_INDEX_ENTRY_ID:
318       GST_DEBUG ("id %d describes writer %s\n", entry->id,
319           GST_INDEX_ID_DESCRIPTION (entry));
320       break;
321     case GST_INDEX_ENTRY_FORMAT:
322       GST_DEBUG ("%d: registered format %d for %s\n", entry->id,
323           GST_INDEX_FORMAT_FORMAT (entry), GST_INDEX_FORMAT_KEY (entry));
324       break;
325     case GST_INDEX_ENTRY_ASSOCIATION:
326     {
327       gint i;
328       if (entry->id == 1 && GST_INDEX_NASSOCS (entry) == 2) {
329         g_fprintf (app->f_epmap, "entrypoint: %" G_GINT64_FORMAT " ",
330             GST_INDEX_ASSOC_VALUE (entry, 0));
331         g_fprintf (app->f_epmap, "%" G_GINT64_FORMAT "\n", GST_INDEX_ASSOC_VALUE (entry, 1));
332       } else {
333         g_print ("GST_INDEX_ENTRY_ASSOCIATION %p, %d: %08x ", entry, entry->id,
334             GST_INDEX_ASSOC_FLAGS (entry));
335         for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
336           g_print ("%d %" G_GINT64_FORMAT " ", GST_INDEX_ASSOC_FORMAT (entry,
337                   i), GST_INDEX_ASSOC_VALUE (entry, i));
338         }
339         g_print ("\n");
340       }
341       break;
342     }
343     default:
344       break;
345   }
346 }
347
348 static void
349 pad_block_cb (GstPad * pad, gboolean blocked, App * app)
350 {
351   GST_DEBUG("pad_block_cb %s:%s = %i", GST_DEBUG_PAD_NAME(pad), blocked);
352
353   if (!blocked)
354     return;
355
356 //   gst_pad_set_blocked_async (pad, FALSE, pad_block_cb, NULL);
357 }
358
359 static void mux_pad_has_caps_cb(GstPad *pad, GParamSpec * unused, App * app)
360 {
361         GstCaps *caps;
362
363         g_object_get (G_OBJECT (pad), "caps", &caps, NULL);
364
365         if (caps)
366         {
367                  g_print("%s:%s has CAPS: %s\n", GST_DEBUG_PAD_NAME(pad), gst_caps_to_string(caps));
368                  gst_caps_unref (caps);
369         }
370 }
371
372 static void
373 queue_filled_cb (GstElement * element, App * app)
374 {
375   GST_DEBUG ("queue_filled_cb");
376
377   if (app->auto_pids)
378   {
379     GstPad *queue_srcpad = NULL;
380     gchar srcpadname[9];
381     int i, ret;
382     GST_INFO ("First time queue overrun -> UNBLOCKING all pads and start muxing! (have %i PIDS @ mux)", app->requested_pid_count);
383      for (i = 0; i < app->no_sink_pids; i++)
384      {
385        g_sprintf (srcpadname, "src%d", app->a_sink_pids[i]);
386        queue_srcpad = gst_element_get_static_pad(app->queue, srcpadname);
387        ret = gst_pad_set_blocked_async (queue_srcpad, FALSE, (GstPadBlockCallback) pad_block_cb, app);
388        GST_DEBUG ("UNBLOCKING %s returned %i", srcpadname, ret);
389      }
390   }
391   g_signal_handler_disconnect(G_OBJECT(element),
392                         app->queue_cb_handler_id);
393
394 }
395
396 static void
397 demux_pad_added_cb (GstElement * element, GstPad * demuxpad, App * app)
398 {
399   GstPad *parser_sinkpad = NULL, *parser_srcpad = NULL, *queue_sinkpad = NULL, *queue_srcpad = NULL, *mux_sinkpad = NULL;
400   GstStructure *s;
401   GstCaps *caps = gst_pad_get_caps (demuxpad);
402
403   gchar *demuxpadname, sinkpadname[10], srcpadname[9];
404   guint sourcepid;
405   int i, ret;
406
407   s = gst_caps_get_structure (caps, 0);
408   demuxpadname = gst_pad_get_name (demuxpad);
409   GST_DEBUG ("demux_pad_added_cb %s:%s", GST_DEBUG_PAD_NAME(demuxpad));
410
411   if (g_ascii_strncasecmp (demuxpadname, "video", 5) == 0) {
412     sscanf (demuxpadname + 6, "%x", &sourcepid);
413     if (app->auto_pids) {
414       app->a_source_pids[0] = sourcepid;
415       app->a_sink_pids[0] = sourcepid;
416       app->no_sink_pids++;
417       app->no_source_pids++;
418     }
419     if (sourcepid == app->a_source_pids[0] && app->videoparser == NULL) {
420       if (gst_structure_has_name (s, "video/mpeg")) {
421         app->videoparser = gst_element_factory_make ("mpegvideoparse", "videoparse");
422         if (!app->videoparser) {
423           bdremux_errout("mpegvideoparse not found! please install gst-plugin-mpegvideoparse!");
424         }
425       }
426       else if (gst_structure_has_name (s, "video/x-h264")) {
427         app->videoparser = gst_element_factory_make ("h264parse", "videoparse");
428         if (!app->videoparser) {
429           bdremux_errout("h264parse not found! please install gst-plugin-videoparsersbad!");
430         }
431       }
432       gst_bin_add (GST_BIN (app->pipeline), app->videoparser);
433       gst_element_set_state (app->videoparser, GST_STATE_PLAYING);
434       parser_sinkpad = gst_element_get_static_pad (app->videoparser, "sink");
435       parser_srcpad = gst_element_get_static_pad (app->videoparser, "src");
436       g_sprintf (sinkpadname, "sink%d", app->a_sink_pids[0]);
437       g_sprintf (srcpadname, "src%d", app->a_sink_pids[0]);
438       queue_sinkpad = gst_element_get_request_pad (app->queue, sinkpadname);
439       queue_srcpad = gst_element_get_static_pad(app->queue, srcpadname);
440       g_sprintf (sinkpadname, "sink_%d", app->a_sink_pids[0]);
441       mux_sinkpad = gst_element_get_request_pad (app->m2tsmux, sinkpadname);
442       app->requested_pid_count++;
443       if (app->requested_pid_count <= app->no_source_pids)
444         {
445                  ret = gst_pad_set_blocked_async (queue_srcpad, TRUE, (GstPadBlockCallback) pad_block_cb, app);
446                  GST_DEBUG ("BLOCKING %s returned %i", srcpadname, ret);
447         }
448       if (gst_pad_link (demuxpad, parser_sinkpad) == 0)
449       {
450         if (gst_pad_link (parser_srcpad, queue_sinkpad) == 0)
451         {
452           if (gst_pad_link (queue_srcpad, mux_sinkpad) == 0) {
453             g_print
454                 ("linked: Source PID %d to %s\n",
455                 app->a_source_pids[0], sinkpadname);
456                 g_signal_connect (G_OBJECT (mux_sinkpad), "notify::caps", G_CALLBACK (mux_pad_has_caps_cb), app);
457           } else {
458             bdremux_errout(g_strdup_printf("Couldn't link %s:%s to %s:%s", GST_DEBUG_PAD_NAME(queue_srcpad), GST_DEBUG_PAD_NAME(mux_sinkpad)));
459           }
460         } else {
461           bdremux_errout(g_strdup_printf("Couldn't link %s:%s to %s:%s @%p", GST_DEBUG_PAD_NAME(parser_srcpad), GST_DEBUG_PAD_NAME(queue_sinkpad), queue_sinkpad));
462         }
463       } else {
464         bdremux_errout(g_strdup_printf("Couldn't link %s:%s to %s:%s", GST_DEBUG_PAD_NAME(demuxpad), GST_DEBUG_PAD_NAME(parser_sinkpad)));
465       }
466     }
467   } else if (g_ascii_strncasecmp (demuxpadname, "audio", 5) == 0) {
468     sscanf (demuxpadname + 6, "%x", &sourcepid);
469     if (app->auto_pids)
470     {
471       if (app->no_source_pids == 0)
472         i = 1;
473       else
474         i = app->no_source_pids;
475       app->a_source_pids[i] = sourcepid;
476       if (app->a_sink_pids[i] == -1)
477       {
478         app->a_sink_pids[i] = sourcepid;
479         app->no_sink_pids++;
480       }
481       app->no_source_pids++;
482     }
483     for (i = 1; i < app->no_source_pids; i++) {
484       if (sourcepid == app->a_source_pids[i]) {
485         if (gst_structure_has_name (s, "audio/mpeg")) {
486           app->audioparsers[i] = gst_element_factory_make ("mpegaudioparse", NULL);
487           if (!app->audioparsers[i]) {
488             bdremux_errout("mpegaudioparse not found! please install gst-plugin-mpegaudioparse!");
489           }
490         }
491         else if (gst_structure_has_name (s, "audio/x-ac3")) {
492           app->audioparsers[i] = gst_element_factory_make ("ac3parse", NULL);
493           if (!app->audioparsers[i]) {
494             bdremux_errout("mpegaudioparse not found! please install gst-plugin-audioparses!");
495           }
496         }
497         else if (gst_structure_has_name (s, "audio/x-dts")) {
498           app->audioparsers[i] = gst_element_factory_make ("dcaparse", NULL);
499           if (!app->audioparsers[i]) {
500             bdremux_errout("dcaparse not found! please install gst-plugin-audioparses!");
501           }
502         }
503         else {
504           bdremux_errout(g_strdup_printf("could not find parser for audio stream with pid 0x%04x!", sourcepid));
505         }
506         gst_bin_add (GST_BIN (app->pipeline), app->audioparsers[i]);
507         gst_element_set_state (app->audioparsers[i], GST_STATE_PLAYING);
508         parser_sinkpad = gst_element_get_static_pad (app->audioparsers[i], "sink");
509         parser_srcpad = gst_element_get_static_pad (app->audioparsers[i], "src");
510         g_sprintf (sinkpadname, "sink%d", app->a_sink_pids[i]);
511         g_sprintf (srcpadname, "src%d", app->a_sink_pids[i]);
512         queue_sinkpad = gst_element_get_request_pad (app->queue, sinkpadname);
513         queue_srcpad = gst_element_get_static_pad(app->queue, srcpadname);
514         g_sprintf (sinkpadname, "sink_%d", app->a_sink_pids[i]);
515         mux_sinkpad = gst_element_get_request_pad (app->m2tsmux, sinkpadname);
516         app->requested_pid_count++;
517         if (app->requested_pid_count <= app->no_source_pids)
518         {
519                  ret = gst_pad_set_blocked_async (queue_srcpad, TRUE, (GstPadBlockCallback) pad_block_cb, app);
520           GST_DEBUG ("BLOCKING %s returned %i", srcpadname, ret);
521         }
522         if (gst_pad_link (demuxpad, parser_sinkpad) == 0
523             && gst_pad_link (parser_srcpad, queue_sinkpad) == 0
524             && gst_pad_link (queue_srcpad, mux_sinkpad) == 0) {
525           g_print
526               ("linked: Source PID %d to %s\n",
527               app->a_source_pids[i], sinkpadname);
528               g_signal_connect (G_OBJECT (mux_sinkpad), "notify::caps", G_CALLBACK (mux_pad_has_caps_cb), app);
529         } else
530           bdremux_errout (g_strdup_printf("Couldn't link audio PID 0x%04x to sink PID 0x%04x",
531               app->a_source_pids[i], app->a_sink_pids[i]));
532         break;
533       }
534     }
535   } else
536     GST_INFO ("Ignoring pad %s!", demuxpadname);
537   
538   if (parser_sinkpad)
539     gst_object_unref (parser_sinkpad);
540   if (parser_srcpad)
541     gst_object_unref (parser_srcpad);
542   if (queue_sinkpad)
543     gst_object_unref (queue_sinkpad);
544   if (queue_srcpad)
545     gst_object_unref (queue_srcpad);
546   if (mux_sinkpad)
547     gst_object_unref (mux_sinkpad);
548   if (caps)
549     gst_caps_unref (caps);
550
551 //   g_print("app->requested_pid_count = %i, app->no_source_pids = %i\n", app->requested_pid_count, app->no_source_pids);
552   if (!app->auto_pids && app->requested_pid_count == app->no_source_pids)
553   {
554      GST_INFO("All %i source PIDs have been linked to the mux -> UNBLOCKING all pads and start muxing", app->requested_pid_count);
555      for (i = 0; i < app->no_sink_pids; i++)
556      {
557        g_sprintf (srcpadname, "src%d", app->a_sink_pids[i]);
558        queue_srcpad = gst_element_get_static_pad(app->queue, srcpadname);
559        ret = gst_pad_set_blocked_async (queue_srcpad, FALSE, (GstPadBlockCallback) pad_block_cb, app);
560        GST_DEBUG ("UNBLOCKING %s returned %i", srcpadname, ret);
561      }
562   }
563
564   g_free (demuxpadname);
565   GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(app->pipeline),GST_DEBUG_GRAPH_SHOW_ALL,"bdremux_pipelinegraph_pad_added");
566 }
567
568 static gint
569 get_pid (gchar * s_pid)
570 {
571   gint pid = -1;
572   if (g_ascii_strncasecmp (s_pid, "0x", 2) == 0) {
573     sscanf (s_pid + 2, "%x", &pid);
574     return pid;
575   } else
576     return atoi (s_pid);
577 }
578
579 static void
580 parse_pid_list (gint * array, guint * count, char *string)
581 {
582   gchar **split;
583   gint i = 0;
584   if (!string)
585     return;
586   GST_DEBUG("parse_pid_list %s, count=%i", string, *count);
587   split = g_strsplit_set (string, ".,", MAX_PIDS);
588   for (i = 0; i < MAX_PIDS; i++) {
589     if (!split[i] || strlen (split[i]) == 0)
590       break;
591     array[*count] = get_pid (split[i]);
592     (*count)++;
593   }
594   g_strfreev (split);
595   GST_DEBUG("parse_pid_list %s, count=%i", string, *count);
596 }
597
598 static gboolean
599 parse_options (int argc, char *argv[], App * app)
600 {
601   int opt;
602
603   const gchar *optionsString = "vecq:s:r:?";
604   struct option optionsTable[] = {
605     {"entrypoints", optional_argument, NULL, 'e'},
606     {"cutlist", optional_argument, NULL, 'c'},
607     {"queue-size", required_argument, NULL, 'q'},
608     {"source-pids", required_argument, NULL, 's'},
609     {"result-pids", required_argument, NULL, 'r'},
610     {"help", no_argument, NULL, '?'},
611     {"version", no_argument, NULL, 'v'},
612     {NULL, 0, NULL, 0}
613   };
614
615   if (argc == 1)
616     goto usage;
617
618   if (argc > 2)
619     app->in_filename = g_strdup (argv[1]);
620   app->out_filename = g_strdup (argv[2]);
621
622   while ((opt =
623           getopt_long (argc, argv, optionsString, optionsTable, NULL)) >= 0) {
624     switch (opt) {
625       case 'e':
626         app->enable_indexing = TRUE;
627         if (optarg != NULL) {
628           app->epmap_filename = g_strdup(optarg);
629           GST_DEBUG ("arbitrary epmap_filename=%s", app->epmap_filename);
630         }
631         else
632         {
633            GST_DEBUG ("display ep map on stdout");
634         }
635         break;
636       case 'c':
637         app->enable_cutlist = TRUE;
638                 if (optarg != NULL) {
639           app->cuts_filename = g_strdup(optarg);
640           GST_DEBUG ("arbitrary cuts_filename=%s", app->cuts_filename);
641                 }
642           else {
643             app->cuts_filename = g_strconcat (app->in_filename, ".cuts", NULL);   
644             GST_DEBUG ("enigma2-style cuts_filename=%s", app->cuts_filename);
645           }
646           
647         break;
648       case 'q':
649         app->queue_size = atoi(optarg);
650         GST_DEBUG("arbitrary queue size=%i", app->queue_size);
651         break;
652       case 's':
653         parse_pid_list (app->a_source_pids, &app->no_source_pids, optarg);
654         app->auto_pids = FALSE;
655         break;
656       case 'r':
657         parse_pid_list (app->a_sink_pids, &app->no_sink_pids, optarg);
658         break;
659       case 'v':
660       {
661         const gchar *nano_str;
662         guint major, minor, micro, nano;
663         gst_version (&major, &minor, &micro, &nano);
664
665         if (nano == 1)
666           nano_str = "(GIT)";
667         else if (nano == 2)
668           nano_str = "(Prerelease)";
669         else
670           nano_str = "";
671
672         g_print ("bdremux 0.1 is linked against GStreamer %d.%d.%d %s\n",
673             major, minor, micro, nano_str);
674         exit (0);
675       }
676       case '?':
677         goto usage;
678         break;
679       default:
680         break;
681     }
682   }
683   return TRUE;
684
685 usage:
686   g_print
687       ("bdremux - a blu-ray movie stream remuxer <fraxinas@opendreambox.org>\n"
688       "\n"
689       "Usage: %s source_stream.ts output_stream.m2ts [OPTION...]\n"
690       "\n"
691       "Optional arguments:\n"
692       "  -e, --entrypoints               Generate and display the SPN/PTS map\n"
693       "  -c, --cutlist                   use enigma2's $source_stream.ts.cuts file\n"
694       "  -q, --queue-size=INT            max size of queue in bytes (default=%i)\n"
695       "  -s, --source-pids=STRING        list of PIDs to be considered\n"
696       "  -r, --result-pids=STRING        list of PIDs in resulting stream\n"
697       "     PIDs can be supplied in decimal or hexadecimal form (0x prefixed)\n"
698       "     the lists are supposed to be comma-seperated with the Video PID\n"
699       "     as the first element followed by 1-7 Audio PIDs.\n"
700       "     If omitted, the first video and all audio elementary streams are\n"
701       "     carried over, keeping their PIDs (this may require a larger queue size).\n"
702       "\n"
703       "Help options:\n"
704       "  -?, --help                      Show this help message\n"
705       "  -v, --version                   Display GSTREAMER version\n"
706       "\n"
707       "Example: %s in.ts out.m2ts -e -s0x40,0x4A,0x4C -r0x1011,0x1100,0x1101\n"
708       "  Will extract the video elementary stream with PID 0x40 and the audio\n"
709       "  streams with PIDs 0x41 and 0x4C from the file in.ts and write new\n"
710       "  remultiplexed streams with PID numbers 0x1011 for video and 0x1100\n"
711       "  and 0x1101 for audio into the file out.m2ts while showing a map\n"
712       "  of entrypoints on stdout.\n",
713       argv[0], DEFAULT_QUEUE_SIZE, argv[0]);
714   exit (0);
715   return TRUE;
716 }
717
718 int
719 main (int argc, char *argv[])
720 {
721   App *app = &s_app;
722   GstBus *bus;
723   int i;
724
725   app->is_seekable = FALSE;
726   app->enable_cutlist = FALSE;
727   app->segment_count = 0;
728   app->current_segment = 0;
729
730   app->epmap_filename = NULL;
731   app->f_epmap = NULL;
732   
733   app->no_source_pids = 0;
734   app->no_sink_pids = 0;
735   app->requested_pid_count = 0;
736   app->auto_pids = TRUE;
737   for (i = 0; i < MAX_PIDS; i++) {
738     app->a_sink_pids[i] = -1;
739   }
740   app->queue_size = DEFAULT_QUEUE_SIZE;
741
742   gst_init (NULL, NULL);
743   GST_DEBUG_CATEGORY_INIT (bdremux_debug, "BDREMUX", GST_DEBUG_BOLD|GST_DEBUG_FG_YELLOW|GST_DEBUG_BG_BLUE, "blu-ray movie stream remuxer");
744   parse_options (argc, argv, app);
745
746   
747   if (app->epmap_filename) {
748   app->f_epmap = fopen (app->epmap_filename, "w");
749   }
750   else
751     app->f_epmap = stdout;
752
753   if (!app->f_epmap) {
754     bdremux_errout (g_strdup_printf("could not open %s for writing entry point map! (%i)", app->epmap_filename, errno));
755   }
756     
757   if (app->enable_cutlist)
758     load_cutlist (app);
759
760   for (i = 0; i < app->segment_count; i++) {
761     GST_INFO ("segment count %i index %i in_pts %lld out_pts %lld", i,
762         app->seek_segments[i].index, app->seek_segments[i].in_pts,
763         app->seek_segments[i].out_pts);
764   }
765
766   for (i = 0; i < app->no_source_pids; i++) {
767     if (app->no_sink_pids <= i)
768       app->a_sink_pids[app->no_sink_pids++] = app->a_source_pids[i];
769     GST_DEBUG
770         ("source pid [%i] = 0x%04x, sink pid [%i] = 0x%04x app->no_sink_pids=%i",
771         i, app->a_source_pids[i], i, app->a_sink_pids[i], app->no_sink_pids);
772   }
773
774   app->loop = g_main_loop_new (NULL, TRUE);
775
776   app->pipeline = gst_pipeline_new ("blu-ray movie stream remuxer");
777   g_assert (app->pipeline);
778
779   app->filesrc = gst_element_factory_make ("filesrc", "filesrc");
780   app->tsdemux = gst_element_factory_make ("mpegtsdemux", "tsdemux");
781   if (!app->tsdemux) {
782     bdremux_errout("mpegtsdemux not found! please install gst-plugin-mpegtsdemux!");
783   }
784
785   app->m2tsmux = gst_element_factory_make ("mpegtsmux", "m2tsmux");
786   if (!app->m2tsmux) {
787     bdremux_errout("mpegtsmux not found! please install gst-plugin-mpegtsmux!");
788   }
789
790   app->filesink = gst_element_factory_make ("filesink", "filesink");
791
792   app->queue = gst_element_factory_make ("multiqueue", "multiqueue");
793
794   app->videoparser = NULL;
795
796   gst_bin_add_many (GST_BIN (app->pipeline), app->filesrc, app->tsdemux, app->queue,
797       app->m2tsmux, app->filesink, NULL);
798
799   g_object_set (G_OBJECT (app->filesrc), "location", app->in_filename, NULL);
800
801   g_object_set (G_OBJECT (app->queue), "max-size-bytes", app->queue_size, NULL);
802   g_object_set (G_OBJECT (app->queue), "max-size-buffers", 0, NULL);
803
804   g_object_set (G_OBJECT (app->m2tsmux), "m2ts-mode", TRUE, NULL);
805   g_object_set (G_OBJECT (app->m2tsmux), "alignment", 32, NULL);
806
807   g_object_set (G_OBJECT (app->filesink), "location", app->out_filename, NULL);
808
809   gst_element_link (app->filesrc, app->tsdemux);
810
811   gst_element_link (app->m2tsmux, app->filesink);
812
813   g_signal_connect (app->tsdemux, "pad-added", G_CALLBACK (demux_pad_added_cb),
814       app);
815
816   app->queue_cb_handler_id = g_signal_connect (app->queue, "overrun", G_CALLBACK (queue_filled_cb), app);
817
818   if (app->enable_indexing) {
819     app->index = gst_index_factory_make ("memindex");
820     if (app->index) {
821       g_signal_connect (G_OBJECT (app->index), "entry_added",
822           G_CALLBACK (entry_added), app);
823       g_object_set (G_OBJECT (app->index), "resolver", 1, NULL);
824       gst_element_set_index (app->m2tsmux, app->index);
825     }
826   }
827
828   bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
829
830   gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
831
832   gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
833
834   g_main_loop_run (app->loop);
835
836   g_message ("stopping");
837
838   gst_element_set_state (app->pipeline, GST_STATE_NULL);
839
840   gst_object_unref (bus);
841   g_main_loop_unref (app->loop);
842
843   if (app->epmap_filename) {
844     fclose (app->f_epmap);
845   }
846
847   return 0;
848 }