1 /*******************************************************************************
2  lqt_remux.c
3 
4  libquicktime - A library for reading and writing quicktime/avi/mp4 files.
5  http://libquicktime.sourceforge.net
6 
7  Copyright (C) 2002 Heroine Virtual Ltd.
8  Copyright (C) 2002-2011 Members of the libquicktime project.
9 
10  This library is free software; you can redistribute it and/or modify it under
11  the terms of the GNU Lesser General Public License as published by the Free
12  Software Foundation; either version 2.1 of the License, or (at your option)
13  any later version.
14 
15  This library is distributed in the hope that it will be useful, but WITHOUT
16  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18  details.
19 
20  You should have received a copy of the GNU Lesser General Public License along
21  with this library; if not, write to the Free Software Foundation, Inc., 51
22  Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 *******************************************************************************/
24 
25 #include <quicktime/lqt.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 
print_usage()30 static void print_usage()
31   {
32   fprintf(stderr, "Usage: lqtremux [-pre <outprefix>] infile\n");
33   fprintf(stderr, "       lqtremux infile1 infile2 ... outfile\n");
34   }
35 
36 typedef struct
37   {
38   const lqt_compression_info_t * ci;
39   quicktime_t * in_file;
40   quicktime_t * out_file;
41   double time;
42   int timescale;
43 
44   int in_index;
45   int out_index;
46   int eof;
47   } track_t;
48 
49 typedef struct
50   {
51   quicktime_t * file;
52   int num_audio_tracks;
53   int num_video_tracks;
54   } file_t;
55 
56 /* Global variables */
57 track_t * audio_tracks = NULL;
58 track_t * video_tracks = NULL;
59 int total_tracks = 0;
60 file_t * files;
61 int num_files;
62 
63 int nfiles;
64 int num_audio_tracks, num_video_tracks;
65 quicktime_t * file;
66 
67 int prefix_len;
68 char * prefix = NULL;
69 
70 lqt_codec_info_t ** audio_encoders;
71 lqt_codec_info_t ** video_encoders;
72 
find_audio_encoder(lqt_compression_id_t id)73 static lqt_codec_info_t * find_audio_encoder(lqt_compression_id_t id)
74   {
75   int i = 0;
76 
77   while(audio_encoders[i])
78     {
79     if(audio_encoders[i]->compression_id == id)
80       return audio_encoders[i];
81     i++;
82     }
83   return NULL;
84   }
85 
find_video_encoder(lqt_compression_id_t id)86 static lqt_codec_info_t * find_video_encoder(lqt_compression_id_t id)
87   {
88   int i = 0;
89 
90   while(video_encoders[i])
91     {
92     // fprintf(stderr, "find_video_encoder %s\n", video_encoders[i]->name);
93     if(video_encoders[i]->compression_id == id)
94       return video_encoders[i];
95     i++;
96     }
97   return NULL;
98   }
99 
audio_iteration(track_t * track,lqt_packet_t * p)100 static void audio_iteration(track_t * track, lqt_packet_t * p)
101   {
102   int samples_read = 0;
103   int samples_to_read = track->ci->samplerate / 2; /* We make half second audio chunks */
104 
105   while(samples_read < samples_to_read)
106     {
107     if(!lqt_read_audio_packet(track->in_file, p, track->in_index))
108       {
109       track->eof = 1;
110       return;
111       }
112 
113     //    fprintf(stderr, "Got audio packet\n");
114     //    lqt_packet_dump(p);
115 
116     lqt_write_audio_packet(track->out_file, p, track->out_index);
117 
118     samples_read += p->duration;
119     }
120 
121   track->time = (double)(p->timestamp + p->duration) / (double)(track->ci->samplerate);
122 
123   }
124 
video_iteration(track_t * track,lqt_packet_t * p)125 static void video_iteration(track_t * track, lqt_packet_t * p)
126   {
127   double test_time;
128 
129   if(!lqt_read_video_packet(track->in_file, p, track->in_index))
130     {
131     track->eof = 1;
132     return;
133     }
134 
135   // fprintf(stderr, "Got video packet\n");
136   // lqt_packet_dump(p);
137 
138   lqt_write_video_packet(track->out_file, p, track->out_index);
139 
140   test_time = (double)(p->timestamp + p->duration) / (double)(track->ci->video_timescale);
141   if(test_time > track->time)
142     track->time = test_time;
143 
144   }
145 
tracks_min_time(track_t * tracks,int num)146 static track_t * tracks_min_time(track_t * tracks, int num)
147   {
148   int i;
149   track_t * ret = NULL;
150   double min_time = 0.0;
151 
152   for(i = 0; i < num; i++)
153     {
154     if(tracks[i].in_file && !tracks[i].eof &&
155        (!ret || (tracks[i].time < min_time)))
156       {
157       min_time = tracks[i].time;
158       ret = &tracks[i];
159       }
160     }
161   return ret;
162   }
163 
init_demultiplex(char * filename)164 static int init_demultiplex(char * filename)
165   {
166   int i;
167   char * tmp_string;
168 
169   lqt_codec_info_t * codec_info;
170 
171   file = lqt_open_read(filename);
172   if(!file)
173     {
174     fprintf(stderr, "Couldn't open file %s\n", filename);
175     return -1;
176     }
177 
178   num_audio_tracks = quicktime_audio_tracks(file);
179   num_video_tracks = quicktime_video_tracks(file);
180 
181   if(!prefix) /* Create default prefix */
182     {
183     char * pos;
184     prefix = strdup(filename);
185 
186     /* Put into current directory */
187     pos = strrchr(prefix, '/');
188     if(pos)
189       {
190       pos++;
191       memmove(prefix, pos, strlen(pos)+1);
192       }
193 
194     /* Strip off extension */
195     pos = strrchr(prefix, '.');
196     if(pos)
197       *pos = '\0';
198     }
199 
200   prefix_len = strlen(prefix);
201 
202   tmp_string = malloc(prefix_len + 128);
203 
204   if(num_audio_tracks)
205     {
206     audio_tracks = calloc(num_audio_tracks, sizeof(*audio_tracks));
207     for(i = 0; i < num_audio_tracks; i++)
208       {
209       audio_tracks[i].ci = lqt_get_audio_compression_info(file, i);
210 
211       if(!audio_tracks[i].ci)
212         {
213         fprintf(stderr, "Audio track %d cannot be read compressed\n", i+1);
214         continue;
215         }
216       lqt_compression_info_dump(audio_tracks[i].ci);
217 
218       codec_info = find_audio_encoder(audio_tracks[i].ci->id);
219       if(!codec_info)
220         {
221         fprintf(stderr, "No audio encoder found for compressed writing\n");
222         continue;
223         }
224 
225       sprintf(tmp_string, "%s_audio_%02d.mov", prefix, i+1);
226       audio_tracks[i].in_file = file;
227       audio_tracks[i].out_file = lqt_open_write(tmp_string, LQT_FILE_QT);
228 
229       if(!lqt_writes_compressed(lqt_get_file_type(audio_tracks[i].out_file),
230                                 audio_tracks[i].ci, codec_info))
231         {
232         fprintf(stderr, "Audio track %d cannot be written compressed\n", i+1);
233         quicktime_close(audio_tracks[i].out_file);
234         remove(tmp_string);
235         audio_tracks[i].out_file = NULL;
236         audio_tracks[i].in_file = NULL;
237         continue;
238         }
239       total_tracks++;
240 
241       lqt_add_audio_track_compressed(audio_tracks[i].out_file,
242                                      audio_tracks[i].ci, codec_info);
243       audio_tracks[i].in_index = i;
244       audio_tracks[i].out_index = 0;
245       }
246     }
247   if(num_video_tracks)
248     {
249     video_tracks = calloc(num_video_tracks, sizeof(*video_tracks));
250     for(i = 0; i < num_video_tracks; i++)
251       {
252       video_tracks[i].ci = lqt_get_video_compression_info(file, i);
253 
254       if(!video_tracks[i].ci)
255         {
256         fprintf(stderr, "Video track %d cannot be read compressed\n", i+1);
257         continue;
258         }
259       lqt_compression_info_dump(video_tracks[i].ci);
260 
261       codec_info = find_video_encoder(video_tracks[i].ci->id);
262       if(!codec_info)
263         {
264         fprintf(stderr, "No video encoder found for compressed writing of %s\n",
265                 lqt_compression_id_to_string(video_tracks[i].ci->id));
266         continue;
267         }
268 
269       sprintf(tmp_string, "%s_video_%02d.mov", prefix, i+1);
270       video_tracks[i].in_file = file;
271       video_tracks[i].out_file = lqt_open_write(tmp_string, LQT_FILE_QT);
272 
273       if(!lqt_writes_compressed(lqt_get_file_type(video_tracks[i].out_file),
274                                 video_tracks[i].ci, codec_info))
275         {
276         fprintf(stderr, "Video track %d cannot be written compressed\n", i+1);
277         quicktime_close(video_tracks[i].out_file);
278         remove(tmp_string);
279         video_tracks[i].out_file = NULL;
280         video_tracks[i].in_file = NULL;
281         continue;
282         }
283       total_tracks++;
284 
285       lqt_add_video_track_compressed(video_tracks[i].out_file,
286                                      video_tracks[i].ci, codec_info);
287       video_tracks[i].in_index = i;
288       video_tracks[i].out_index = 0;
289       }
290     }
291 
292   free(tmp_string);
293 
294   return 1;
295   }
296 
cleanup_demultiplex()297 static void cleanup_demultiplex()
298   {
299   int i;
300   for(i = 0; i < num_audio_tracks; i++)
301     {
302     if(audio_tracks[i].out_file)
303       quicktime_close(audio_tracks[i].out_file);
304     }
305   for(i = 0; i < num_video_tracks; i++)
306     {
307     if(video_tracks[i].out_file)
308       quicktime_close(video_tracks[i].out_file);
309     }
310   quicktime_close(file);
311   }
312 
init_multiplex(char ** in_files,int num_in_files,char * out_file)313 static int init_multiplex(char ** in_files, int num_in_files, char * out_file)
314   {
315   int i, j;
316   int audio_index;
317   int video_index;
318   char * pos;
319   lqt_file_type_t type = LQT_FILE_QT;
320   lqt_codec_info_t * codec_info;
321 
322   files = calloc(num_in_files, sizeof(*files));
323   num_files = num_in_files;
324   /* Open input files */
325 
326   for(i = 0; i < num_in_files; i++)
327     {
328     files[i].file = lqt_open_read(in_files[i]);
329     if(!files[i].file)
330       {
331       fprintf(stderr, "Opening %s failed\n", in_files[i]);
332       return 0;
333       }
334     files[i].num_audio_tracks = quicktime_audio_tracks(files[i].file);
335     files[i].num_video_tracks = quicktime_video_tracks(files[i].file);
336     num_audio_tracks += files[i].num_audio_tracks;
337     num_video_tracks += files[i].num_video_tracks;
338     }
339 
340   /* Open output file */
341 
342   pos = strrchr(out_file, '.');
343   if(!pos)
344     {
345     fprintf(stderr, "Unknown file type for file %s\n", out_file);
346     return 0;
347     }
348 
349   pos++;
350   if(!strcmp(pos, "mov"))
351     type = LQT_FILE_QT;
352   else if(!strcmp(pos, "avi"))
353     type = LQT_FILE_AVI_ODML;
354   else if(!strcmp(pos, "mp4"))
355     type = LQT_FILE_MP4;
356   else if(!strcmp(pos, "m4a"))
357     type = LQT_FILE_M4A;
358   else
359     {
360     fprintf(stderr, "Unknown file type for file %s\n", out_file);
361     return 0;
362     }
363 
364   file = lqt_open_write(out_file, type);
365 
366   audio_tracks = calloc(num_audio_tracks, sizeof(*audio_tracks));
367   video_tracks = calloc(num_video_tracks, sizeof(*video_tracks));
368 
369   audio_index = 0;
370   video_index = 0;
371 
372   for(i = 0; i < num_in_files; i++)
373     {
374     for(j = 0; j < files[i].num_audio_tracks; j++)
375       {
376       audio_tracks[audio_index].ci = lqt_get_audio_compression_info(files[i].file, j);
377       if(!audio_tracks[audio_index].ci)
378         {
379         fprintf(stderr, "Audio track %d of file %s cannot be read compressed\n",
380                 j+1, in_files[i]);
381         audio_index++;
382         continue;
383         }
384 
385       codec_info = find_audio_encoder(audio_tracks[i].ci->id);
386       if(!codec_info)
387         {
388         fprintf(stderr, "No audio encoder found for compressed writing\n");
389         continue;
390         }
391 
392       if(!lqt_writes_compressed(lqt_get_file_type(file),
393                                 audio_tracks[audio_index].ci, codec_info))
394         {
395         fprintf(stderr, "Audio track %d of file %s cannot be written compressed\n",
396                 j+1, in_files[i]);
397         audio_index++;
398         continue;
399         }
400 
401       lqt_compression_info_dump(audio_tracks[i].ci);
402 
403       audio_tracks[audio_index].in_index = j;
404       audio_tracks[audio_index].out_index = audio_index;
405       audio_tracks[audio_index].in_file = files[i].file;
406       audio_tracks[audio_index].out_file = file;
407 
408       lqt_add_audio_track_compressed(audio_tracks[audio_index].out_file,
409                                      audio_tracks[audio_index].ci, codec_info);
410 
411       audio_index++;
412       total_tracks++;
413       }
414     for(j = 0; j < files[i].num_video_tracks; j++)
415       {
416       video_tracks[video_index].ci = lqt_get_video_compression_info(files[i].file, j);
417       if(!video_tracks[video_index].ci)
418         {
419         fprintf(stderr, "Video track %d of file %s cannot be read compressed\n",
420                 j+1, in_files[i]);
421         video_index++;
422         continue;
423         }
424 
425       codec_info = find_video_encoder(video_tracks[i].ci->id);
426       if(!codec_info)
427         {
428         fprintf(stderr, "No video encoder found for compressed writing of %s\n",
429                 lqt_compression_id_to_string(video_tracks[i].ci->id));
430         continue;
431         }
432 
433       if(!lqt_writes_compressed(lqt_get_file_type(file),
434                                 video_tracks[video_index].ci, codec_info))
435         {
436         fprintf(stderr, "Video track %d of file %s cannot be written compressed\n",
437                 j+1, in_files[i]);
438         video_index++;
439         continue;
440         }
441       lqt_compression_info_dump(video_tracks[i].ci);
442       video_tracks[video_index].in_index = j;
443       video_tracks[video_index].out_index = video_index;
444       video_tracks[video_index].in_file = files[i].file;
445       video_tracks[video_index].out_file = file;
446 
447       lqt_add_video_track_compressed(video_tracks[video_index].out_file,
448                                      video_tracks[video_index].ci, codec_info);
449 
450       video_index++;
451       total_tracks++;
452 
453       }
454 
455     }
456   return 1;
457   }
458 
cleanup_multiplex()459 static void cleanup_multiplex()
460   {
461   int i;
462   for(i = 0; i < num_files; i++)
463     {
464     if(files[i].file)
465       quicktime_close(files[i].file);
466     }
467   if(file)
468     quicktime_close(file);
469   }
470 
main(int argc,char ** argv)471 int main(int argc, char ** argv)
472   {
473   int i;
474   int first_file;
475   int nfiles;
476 
477   track_t * atrack, *vtrack;
478   lqt_packet_t p;
479 
480   memset(&p, 0, sizeof(p));
481 
482   if(argc < 2)
483     {
484     print_usage();
485     return 0;
486     }
487 
488   /* Parse agruments */
489 
490   nfiles = argc - 1;
491 
492   i = 1;
493   while(i < argc)
494     {
495     if(*(argv[i]) != '-')
496       {
497       first_file = i;
498       break;
499       }
500 
501     if(!strcmp(argv[i], "-pre"))
502       {
503       if(i == argc-1)
504         {
505         fprintf(stderr, "-pre requires an argument\n");
506         return -1;
507         }
508       prefix = strdup(argv[i+1]);
509       i++;
510       nfiles -= 2;
511       }
512     i++;
513     }
514 
515   if(!nfiles)
516     {
517     print_usage();
518     return -1;
519     }
520 
521   /* Query registry */
522 
523   audio_encoders = lqt_query_registry(1, 0, 1, 0);
524   video_encoders = lqt_query_registry(0, 1, 1, 0);
525 
526   if(nfiles == 1)
527     {
528     /* Demultiplex */
529     if(!init_demultiplex(argv[argc-1]))
530       return -1;
531     }
532   else
533     {
534     /* Multiplex */
535 
536     if(!init_multiplex(&argv[first_file], nfiles-1, argv[argc-1]))
537       return -1;
538     }
539 
540   if(!total_tracks)
541     {
542     fprintf(stderr, "No tracks to demultiplex\n");
543     return -1;
544     }
545 
546   /* Transmultiplex */
547 
548   while(1)
549     {
550     atrack = tracks_min_time(audio_tracks, num_audio_tracks);
551     vtrack = tracks_min_time(video_tracks, num_video_tracks);
552 
553     if(atrack && vtrack)
554       {
555       fprintf(stderr, "A: %f, V: %f\n", atrack->time, vtrack->time);
556 
557       if(atrack->time < vtrack->time)
558         audio_iteration(atrack, &p);
559       else
560         video_iteration(vtrack, &p);
561       }
562     else if(atrack)
563       audio_iteration(atrack, &p);
564     else if(vtrack)
565       video_iteration(vtrack, &p);
566     else
567       break;
568     }
569 
570   /* Cleanup */
571 
572   if(nfiles == 1)
573     cleanup_demultiplex();
574   else
575     cleanup_multiplex();
576 
577   if(files)
578     free(files);
579   if(audio_tracks)
580     free(audio_tracks);
581   if(video_tracks)
582     free(video_tracks);
583 
584   lqt_destroy_codec_info(audio_encoders);
585   lqt_destroy_codec_info(video_encoders);
586 
587   return 0;
588   }
589