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