1
2 /*************************************************************************
3 * @mainpage
4 * mplex - General-purpose MPEG-1/2 multiplexer.
5 * (C) 2000, 2001 Andrew Stevens <andrew.stevens@philips.com>
6 *
7 * Doxygen documentation and MPEG Z/Alpha multiplexing part by
8 * Gernot Ziegler <gz@lysator.liu.se>
9 * Constructed using mplex - MPEG-1/SYSTEMS multiplexer as starting point
10 * Copyright (C) 1994 1995 Christoph Moar
11 * Siemens ZFE ST SN 11 / T SN 6
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version. *
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 *************************************************************************/
27
28 #include <config.h>
29 #include <stdio.h>
30 #ifdef HAVE_GETOPT_H
31 #include <getopt.h>
32 #endif
33 #include <string>
34 #include <string.h>
35 #include <memory>
36 #include <sys/stat.h>
37 #if !defined(_WIN32) || defined(__MINGW32__)
38 #include <sys/param.h>
39 #endif
40 #include <ctype.h>
41 #include <math.h>
42 #include <fcntl.h>
43 #include "cpu_accel.h"
44 #include "mjpeg_types.h"
45 #include "mjpeg_logging.h"
46 #include "mpegconsts.h"
47
48 #include "interact.hpp"
49 #include "bits.hpp"
50 #include "outputstrm.hpp"
51 #include "multiplexor.hpp"
52
53
54 using std::auto_ptr;
55
56
57 /*************************************************************************
58 Command line wrapper. Basically, all the command line and file (actually
59 pipe and FIFO is what would be more normal) I/O specific sub-classes
60
61 Plus the top-level entry point. That's all!!
62
63 *************************************************************************/
64
65
66 #if !defined(HAVE_LROUND)
67 extern "C" {
68 long
lround(double x)69 lround(double x)
70 {
71 long l = ceil(x);
72 return(l);
73 }
74 };
75 #endif
76
77
78
79 class FileOutputStream : public OutputStream
80 {
81 public:
82 FileOutputStream( const char *filename_pat );
83 virtual int Open( );
84 virtual void Close();
85 virtual uint64_t SegmentSize( );
86 virtual void NextSegment();
87 virtual void Write(uint8_t *data, unsigned int len);
88
89 private:
90 FILE *strm;
91 char filename_pat[MAXPATHLEN];
92 char cur_filename[MAXPATHLEN];
93
94 };
95
96
97
FileOutputStream(const char * name_pat)98 FileOutputStream::FileOutputStream( const char *name_pat )
99 {
100 strncpy( filename_pat, name_pat, MAXPATHLEN );
101 snprintf( cur_filename, MAXPATHLEN, filename_pat, segment_num );
102 }
103
Open()104 int FileOutputStream::Open()
105 {
106 segment_len = 0;
107 strm = fopen( cur_filename, "wb" );
108 if( strm == NULL )
109 {
110 mjpeg_error_exit1( "Could not open for writing: %s", cur_filename );
111 }
112 {
113 int flags;
114
115 (void)fcntl(fileno(strm), F_GETFL, &flags);
116 (void)fcntl(fileno(strm), F_SETFL, flags & ~O_NONBLOCK);
117 }
118
119
120
121 return 0;
122 }
123
Close()124 void FileOutputStream::Close()
125 {
126 fclose(strm);
127 }
128
129
130 uint64_t
SegmentSize()131 FileOutputStream::SegmentSize()
132 {
133 return segment_len;
134 }
135
136 void
NextSegment()137 FileOutputStream::NextSegment( )
138 {
139 auto_ptr<char> prev_filename_buf( new char[strlen(cur_filename)+1] );
140 char *prev_filename = prev_filename_buf.get();
141 fclose(strm);
142 ++segment_num;
143 strcpy( prev_filename, cur_filename );
144 snprintf( cur_filename, MAXPATHLEN, filename_pat, segment_num );
145 if( strcmp( prev_filename, cur_filename ) == 0 )
146 {
147 mjpeg_error_exit1(
148 "Need to split output but there appears to be no %%d in the filename pattern %s", filename_pat );
149 }
150 strm = fopen( cur_filename, "wb" );
151 if( strm == NULL )
152 {
153 mjpeg_error_exit1( "Could not open for writing: %s", cur_filename );
154 }
155 segment_len = 0;
156 {
157 int flags;
158
159 (void)fcntl(fileno(strm), F_GETFL, &flags);
160 (void)fcntl(fileno(strm), F_SETFL, flags & ~O_NONBLOCK);
161 }
162
163 }
164
165 void
Write(uint8_t * buf,unsigned int len)166 FileOutputStream::Write( uint8_t *buf, unsigned int len )
167 {
168 if( fwrite( buf, 1, len, strm ) != len )
169 {
170 mjpeg_error_exit1( "Failed write: %s", cur_filename );
171 }
172 segment_len += static_cast<uint64_t>(len);
173 }
174
175
176
177 /********************************
178 *
179 * IFileBitStream - Input bit stream class for bit streams sourced
180 * from standard file I/O (this of course *includes* network sockets,
181 * fifo's, et al).
182 *
183 * OLAF: To hook into your PES reader/reconstructor you need to define
184 * a class like this one, where 'ReadStreamBytes' calls you code to
185 * generate the required number of bytes of ES data and transfer it
186 * to the specified buffer. The logical way to do this would be to
187 * inherit IBitStream as a base class of the top-level classes for the ES
188 * reconstructors.
189 *
190 ********************************/
191
192 class IFileBitStream : public IBitStream
193 {
194 public:
195 IFileBitStream( const char *bs_filename,
196 unsigned int buf_size = BUFFER_SIZE);
197 ~IFileBitStream();
198
199 private:
200 FILE *fileh;
201 char *filename;
ReadStreamBytes(uint8_t * buf,size_t number)202 virtual size_t ReadStreamBytes( uint8_t *buf, size_t number )
203 {
204 return fread(buf,sizeof(uint8_t), number, fileh );
205 }
EndOfStream()206 virtual bool EndOfStream() { return feof(fileh) != 0; }
207
208 };
209
210
211
IFileBitStream(const char * bs_filename,unsigned int buf_size)212 IFileBitStream::IFileBitStream( const char *bs_filename,
213 unsigned int buf_size) :
214 IBitStream()
215 {
216 if ((fileh = fopen(bs_filename, "rb")) == NULL)
217 {
218 mjpeg_error_exit1( "Unable to open file %s for reading.", bs_filename);
219 }
220
221 {
222 int flags;
223
224 (void)fcntl(fileno(fileh), F_GETFL, &flags);
225 (void)fcntl(fileno(fileh), F_SETFL, flags & ~O_NONBLOCK);
226 }
227
228 filename = strcpy( new char[strlen(bs_filename)+1], bs_filename );
229 streamname = filename;
230
231 SetBufSize(buf_size);
232 eobs = false;
233 byteidx = 0;
234 if (!ReadIntoBuffer())
235 {
236 if (buffered==0)
237 {
238 mjpeg_error_exit1( "Unable to read from %s.", bs_filename);
239 }
240 }
241 }
242
243
244 /**
245 Destructor: close the device containing the bit stream after a read
246 process
247 */
~IFileBitStream()248 IFileBitStream::~IFileBitStream()
249 {
250 if (fileh)
251 {
252 fclose(fileh);
253 delete filename;
254 }
255 fileh = 0;
256 Release();
257 }
258
259
260 /*******************************
261 *
262 * Command line job class - sets up a Multiplex Job based on command
263 * line and File I/O...
264 *
265 ******************************/
266
267 class CmdLineMultiplexJob : public MultiplexJob
268 {
269 public:
270 CmdLineMultiplexJob( unsigned int argc, char *argv[]);
271
272 private:
273 void InputStreamsFromCmdLine (unsigned int argc, char* argv[] );
274 void Usage(char *program_name);
275 bool ParseVideoOpt( const char *optarg );
276 bool ParseLpcmOpt( const char *optarg );
277 bool ParseWorkaroundOpt( const char *optarg );
278 bool ParseTimeOffset( const char *optarg );
279 bool ParseSubtitleOptions( const char *optarg );
280
281 static const char short_options[];
282
283 #if defined(HAVE_GETOPT_LONG)
284 static struct option long_options[];
285 #endif
286
287 };
288
289 const char CmdLineMultiplexJob::short_options[] =
290 "o:i:b:r:O:v:f:l:s:S:p:W:L:R:VCMhd:";
291 #if defined(HAVE_GETOPT_LONG)
292 struct option CmdLineMultiplexJob::long_options[] =
293 {
294 { "verbose", 1, 0, 'v' },
295 { "vdr-index", 1, 0, 'i' },
296 { "format", 1, 0, 'f' },
297 { "mux-bitrate", 1, 0, 'r' },
298 { "video-buffer", 1, 0, 'b' },
299 { "lpcm-params", 1, 0, 'L' },
300 { "output", 1, 0, 'o' },
301 { "sync-offset", 1, 0, 'O' },
302 { "vbr", 0, 0, 'V' },
303 { "cbr", 0, 0, 'C' },
304 { "system-headers", 0, 0, 'h' },
305 { "ignore-seqend-markers", 0, 0, 'M' },
306 { "run-in", 1, 0, 'R' },
307 { "max-segment-size", 1, 0, 'S' },
308 { "mux-limit", 1, 0, 'l' },
309 { "packets-per-pack", 1, 0, 'p' },
310 { "sector-size", 1, 0, 's' },
311 { "workarounds", 1, 0, 'W' },
312 { "help", 0, 0, '?' },
313 { "subpicture-delay", 1, 0, 'd' },
314 { 0, 0, 0, 0 }
315 };
316 #endif
317
318
CmdLineMultiplexJob(unsigned int argc,char * argv[])319 CmdLineMultiplexJob::CmdLineMultiplexJob(unsigned int argc, char *argv[]) :
320 MultiplexJob()
321
322 {
323 int n;
324 outfile_pattern = NULL;
325
326 #if defined(HAVE_GETOPT_LONG)
327
328 while( (n=getopt_long(argc,argv,short_options,long_options, NULL)) != -1 )
329 #else
330
331 while( (n=getopt(argc,argv,short_options)) != -1 )
332 #endif
333
334 {
335 switch(n)
336 {
337 case 0 :
338 break;
339 case 'o' :
340 outfile_pattern = optarg;
341 break;
342 case 'i' :
343 vdr_index_pathname = optarg;
344 break;
345 case 'v' :
346 verbose = atoi(optarg);
347 if( verbose < 0 || verbose > 2 )
348 Usage(argv[0]);
349 break;
350 case 'V' :
351 VBR = true;
352 break;
353 case 'C' :
354 CBR = true;
355 break;
356 case 'h' :
357 always_system_headers = true;
358 break;
359
360 case 'b' :
361 if( ! ParseVideoOpt( optarg ) )
362 {
363 mjpeg_error( "Illegal video decoder buffer size(s): %s",
364 optarg );
365 Usage(argv[0]);
366 }
367 break;
368 case 'L':
369 if( ! ParseLpcmOpt( optarg ) )
370 {
371 mjpeg_error( "Illegal LPCM option(s): %s", optarg );
372 Usage(argv[0]);
373 }
374 break;
375
376 case 'r':
377 data_rate = atoi(optarg);
378 if( data_rate < 0 )
379 Usage(argv[0]);
380 /* Convert from kbit/sec (user spec) to 50B/sec units... */
381 data_rate = (( data_rate * 1000 / 8 + 49) / 50 ) * 50;
382 break;
383
384 case 'R':
385 run_in_frames = atoi(optarg);
386 if( run_in_frames < 0 || run_in_frames > 100 )
387 Usage(argv[0]);
388 break;
389
390 case 'O':
391 if( ! ParseTimeOffset(optarg) )
392 {
393 mjpeg_error( "Time offset units if specified must: ms|s|mpt" );
394 Usage(argv[0]);
395 }
396 break;
397
398 case 'l' :
399 max_PTS = atoi(optarg);
400 if( max_PTS < 1 )
401 Usage(argv[0]);
402 break;
403
404 case 'p' :
405 packets_per_pack = atoi(optarg);
406 if( packets_per_pack < 1 || packets_per_pack > 100 )
407 Usage(argv[0]);
408 break;
409
410
411 case 'f' :
412 mux_format = atoi(optarg);
413 if( mux_format < MPEG_FORMAT_MPEG1 || mux_format > MPEG_FORMAT_LAST
414 )
415 Usage(argv[0]);
416 break;
417 case 's' :
418 sector_size = atoi(optarg);
419 if( sector_size < 256 || sector_size > 16384 )
420 Usage(argv[0]);
421 break;
422 case 'S' :
423 max_segment_size = atoi(optarg);
424 if( max_segment_size < 0 )
425 Usage(argv[0]);
426 break;
427 case 'M' :
428 multifile_segment = true;
429 break;
430 case 'W' :
431 if( ! ParseWorkaroundOpt( optarg ) )
432 {
433 Usage(argv[0]);
434 }
435 break;
436
437 case 'd' :
438 if( ! ParseSubtitleOptions( optarg ) )
439 {
440 mjpeg_error( "Illegal Subtitle option(s): %s", optarg );
441 Usage(argv[0]);
442 }
443 break;
444 case '?' :
445 default :
446 Usage(argv[0]);
447 break;
448 }
449 }
450 if (argc - optind < 1 || outfile_pattern == NULL)
451 {
452 Usage(argv[0]);
453 }
454 (void)mjpeg_default_handler_verbosity(verbose);
455 mjpeg_info( "mplex version %s (%s %s)",VERSION,MPLEX_VER,MPLEX_DATE );
456
457 InputStreamsFromCmdLine( argc-(optind-1), argv+optind-1);
458 }
459 /*************************************************************************
460 Usage banner for the command line wrapper.
461 *************************************************************************/
462
463
Usage(char * str)464 void CmdLineMultiplexJob::Usage(char *str)
465 {
466 fprintf( stderr,
467 "mjpegtools mplex-2 version " VERSION " (" MPLEX_VER ")\n"
468 "Usage: %s [params] -o <output filename pattern> <input file>... \n"
469 " %%d in the output file name is by segment count\n"
470 " where possible params are:\n"
471 "--verbose|-v num\n"
472 " Level of verbosity. 0 = quiet, 1 = normal 2 = verbose/debug\n"
473 "--format|-f fmt\n"
474 " Set defaults for particular MPEG profiles\n"
475 " [0 = Generic MPEG1, 1 = VCD, 2 = user-rate VCD, 3 = Generic MPEG2,\n"
476 " 4 = SVCD, 5 = user-rate SVCD\n"
477 " 6 = VCD Stills, 7 = SVCD Stills, 8 = DVD with NAV sectors, 9 = DVD]\n"
478 "--mux-bitrate|-r num\n"
479 " Specify data rate of output stream in kbit/sec\n"
480 " (default 0=Compute from source streams)\n"
481 "--video-buffer|-b num [, num...] \n"
482 " Specifies decoder buffers size in kB. [ 20...2000]\n"
483 "--lpcm-params | -L samppersec:chan:bits [, samppersec:chan:bits]\n"
484 "--mux-limit|-l num\n"
485 " Multiplex only num seconds of material (default 0=multiplex all)\n"
486 "--sync-offset|-O num ms|s|mpt|c\n"
487 " Specify offset of timestamps (video-audio) in mSec\n"
488 "--subpicture-delay|-d delay [ms|s|mpt|c] [:stream-id] [, delay[:stream-id]]\n"
489 " Specify offset of timestamps (video-subpicture) in msec (default) sec, mpt or clock-ticks\n"
490 "--sector-size|-s num\n"
491 " Specify sector size in bytes for generic formats [256..16384]\n"
492 "--vbr|-V\n"
493 " Force variable bit-rate video multiplexing\n"
494 "--cbr|-C\n"
495 " Force constant bit-rate video multiplexing\n"
496 "--run-in|-R num\n"
497 " Force a 'run-in' of exactly num frame intervals\n"
498 "--packets-per-pack|-p num\n"
499 " Number of packets per pack generic formats [1..100]\n"
500 "--system-headers|-h\n"
501 " Create System header in every pack in generic formats\n"
502 "--max-segment-size|-S size\n"
503 " Maximum size of output file(s) in Mbyte (default: 0) (no limit)\n"
504 "--ignore-seqend-markers|-M\n"
505 " Don't switch to a new output file if a sequence end marker\n"
506 " is encountered in the input video.\n"
507 "--vdr-index|-i <vdr-index-filename>\n"
508 " Generate a VDR index file with the output stream\n"
509 "--workaround|-W workaround [, workaround ]\n"
510 "--help|-?\n"
511 " Print this lot out!\n", str);
512 exit (1);
513 }
514
ParseSubtitleOptions(const char * optarg)515 bool CmdLineMultiplexJob::ParseSubtitleOptions( const char *optarg )
516 {
517 double f;
518 double persecond=1000.0;
519 const char *e;
520 uint64_t subtitle_offset;
521 uint8_t stream_id;
522 e=optarg-1;
523 do
524 {
525 e++;
526 subtitle_offset =0;
527 persecond=1000.0;
528 stream_id=0x20; // default
529 f=strtod(e,const_cast<char**>(&e));
530 if( *e ) {
531 while(isspace(*e)) e++;
532 if(!strcmp(e,"ms")) { persecond=1000.0; e+=2;}
533 else if(!strcmp(e,"s")) {persecond=1.0; e++;}
534 else if(!strcmp(e,"mpt")){ persecond=90000.0;e+=3;}
535 else if(!strcmp(e,"c")){ persecond=CLOCKS;e++;}
536 }
537 subtitle_offset = static_cast<int>(f*CLOCKS/(persecond));
538 if( subtitle_offset < 0 )
539 subtitle_offset = 0; // always positive
540 if (*e ==':'){
541 ++e;
542 int nr;
543 sscanf(e,"%hhd%n",&stream_id,&nr);
544 e+=nr;
545 mjpeg_info("Stream will be mapped to 0x%02hhX",stream_id);
546 }
547
548 subtitle_params.push_back(SubtitleStreamParams::Checked(subtitle_offset,stream_id));
549 } while (*e == ',');
550
551 return true;
552
553 }
554
ParseLpcmOpt(const char * optarg)555 bool CmdLineMultiplexJob::ParseLpcmOpt( const char *optarg )
556 {
557 char *endptr, *startptr;
558 unsigned int samples_sec;
559 unsigned int channels;
560 unsigned int bits_sample;
561 endptr = const_cast<char *>(optarg);
562 do
563 {
564 startptr = endptr;
565 samples_sec = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
566 if( startptr == endptr || *endptr != ':' )
567 return false;
568
569
570 startptr = endptr+1;
571 channels = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
572 if(startptr == endptr || *endptr != ':' )
573 return false;
574
575 startptr = endptr+1;
576 bits_sample = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
577 if( startptr == endptr )
578 return false;
579
580 LpcmParams *params = LpcmParams::Checked( samples_sec,
581 channels,
582 bits_sample );
583 if( params == 0 )
584 return false;
585 lpcm_param.push_back(params);
586 if( *endptr == ',' )
587 ++endptr;
588 } while( *endptr != '\0' );
589 return true;
590 }
591
592
ParseWorkaroundOpt(const char * optarg)593 bool CmdLineMultiplexJob::ParseWorkaroundOpt( const char *optarg )
594 {
595 char *endptr, *startptr;
596 endptr = const_cast<char *>(optarg);
597 struct { const char *longname; char shortname; bool *flag; } flag_table[] =
598 {
599 { 0, '\0', 0 }
600 };
601 for(;;)
602 {
603 // Find start of next flag...
604 while( isspace(*endptr) || *endptr == ',' )
605 ++endptr;
606 if( *endptr == '\0' )
607 break;
608 startptr = endptr;
609 // Find end of current flag...
610 while( *endptr != ' ' && *endptr != ',' && *endptr != '\0' )
611 ++endptr;
612
613 size_t len = endptr - startptr;
614
615 int flag = 0;
616 while( flag_table[flag].longname != 0 )
617 {
618 if( (len == 1 && *startptr == flag_table[flag].shortname ) ||
619 strncmp( startptr, flag_table[flag].longname, len ) == 0 )
620 {
621 *flag_table[flag].flag = true;
622 break;
623 }
624 ++flag;
625 }
626
627 if( flag_table[flag].longname == 0 )
628 {
629 std::string message( "Illegal work-around option: not one of " );
630 flag = 0;
631 char sep[] = ",";
632 while( flag_table[flag].longname != 0 )
633 {
634 message += flag_table[flag].longname;
635 message += sep;
636 message += flag_table[flag].shortname;
637 ++flag;
638 if( flag_table[flag].longname != 0 )
639 message += sep;
640 }
641 mjpeg_error( message.c_str() );
642 return false;
643 }
644
645 }
646 return true;
647 }
648
ParseVideoOpt(const char * optarg)649 bool CmdLineMultiplexJob::ParseVideoOpt( const char *optarg )
650 {
651 char *endptr, *startptr;
652 unsigned int buffer_size;
653 endptr = const_cast<char *>(optarg);
654 do
655 {
656 startptr = endptr;
657 buffer_size = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
658 if( startptr == endptr )
659 return false;
660
661 VideoParams *params = VideoParams::Checked( buffer_size );
662 if( params == 0 )
663 return false;
664 video_param.push_back(params);
665 if( *endptr == ',' )
666 ++endptr;
667 }
668 while( *endptr != '\0' );
669 return true;
670 }
671
ParseTimeOffset(const char * optarg)672 bool CmdLineMultiplexJob::ParseTimeOffset(const char *optarg)
673 {
674 double f;
675 double persecond=1000.0;
676 char *e;
677
678 f=strtod(optarg,&e);
679 if( *e ) {
680 while(isspace(*e)) e++;
681 if(!strcmp(e,"ms")) persecond=1000.0;
682 else if(!strcmp(e,"s")) persecond=1.0;
683 else if(!strcmp(e,"mpt")) persecond=90000.0;
684 else if(!strcmp(e,"c")) persecond=CLOCKS;
685 else
686 return false;
687 }
688 video_offset = static_cast<int>(f*CLOCKS/(persecond));
689 if( video_offset < 0 )
690 {
691 audio_offset = - video_offset;
692 video_offset = 0;
693 }
694 return true;
695 }
696
InputStreamsFromCmdLine(unsigned int argc,char * argv[])697 void CmdLineMultiplexJob::InputStreamsFromCmdLine(unsigned int argc, char* argv[] )
698 {
699 vector<IBitStream *> inputs;
700 unsigned int i;
701 for( i = 1; i < argc; ++i )
702 {
703 inputs.push_back( new IFileBitStream( argv[i] ) );
704 }
705 SetupInputStreams( inputs );
706 }
707
708
main(int argc,char * argv[])709 int main (int argc, char* argv[])
710 {
711 CmdLineMultiplexJob job(argc,argv);
712 FileOutputStream output( job.outfile_pattern );
713 FileOutputStream *index = job.vdr_index_pathname != 0
714 ? new FileOutputStream( job.vdr_index_pathname )
715 : 0;
716 Multiplexor mux(job, output, index );
717 mux.Multiplex();
718 if( index != 0 )
719 delete index;
720 return (0);
721 }
722
723
724