/* vcdmplex - MPEG system stream multiplexer for video CDs Copyright (C) 2000 Rainer Johanni This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #define AUDIO_BUFFER_SIZE 4096 /* Maximum size of a MPEG frame, actually a frame should be much smaller */ #define MAX_MPEG_FRAME (512*1024) /* Sector size for multiplexed output - don't change, it is the sector size of a Video CD */ #define SECTOR_SIZE 2324 /* Maximum time of the video buffer in MPEG clock ticks: The standard 46 KB MPEG-1 Buffer is sufficient for about 1/3 second of Video (at 1152KBit/s = 144 KByte/s), which is about 30000 clock ticks. We make the standard buffer a little bigger here since most VCD players should have sufficient memory today */ #define MAX_VBUFFER_TIME 45000 // #define AUDIO_BYTES (SECTOR_SIZE-25) #define AUDIO_BYTES 2279 /* Markers for MPEG System, timestamps */ #define MARKER_DTS 1 #define MARKER_SCR 2 #define MARKER_JUST_PTS 2 #define MARKER_PTS 3 #define FIRST_BYTE(x) ( ((x)>>24)&0xff ) /* File descriptors */ static FILE *mpegin; static FILE *audioin; static FILE *sysout; /* Data returned by get_m1v_frame() */ static unsigned char MPEGvbuff[MAX_MPEG_FRAME]; static long MPEG_frame_no; static long MPEG_frame_seq; static long MPEG_frame_type; static long MPEG_frame_len; /* Internal data for get_m1v_frame() */ static long lasttag, gop_start_frame, frame_no, seqhdr_seen; /* Packet data and Number of bytes in packet */ static unsigned char packet[SECTOR_SIZE]; static long npb; /* The MPEG system clock counter */ static long system_clock; /* Bitrates */ static int AudioBitRate; /* in Kbit/s */ static int VideoBitRate; /* in units of 400 bit/s */ static int MuxRate; /* in units of 400 bit/s */ static double rates[16] = { 0, 23.976, 24.0, 25.0, 29.97, 30.0, 50.0, 59.94, 60.0, 0, 0, 0, 0, 0, 0, 0 }; static int FrameRate, mpeg2, twofields; static unsigned char SeqHdr[256], SeqExt[10]; static int SeqHdrLen; static unsigned long getbits(unsigned char *data, int bitpos, int len) { unsigned long res = 0; int byte, bit, i; for(i=0;i>3; bit = bitpos&7; if(data[byte]&(0x80>>bit)) res |=1; } return res; } void check_buffer_size(int n) { if(n>=MAX_MPEG_FRAME) { fprintf(stderr,"MPEG buffer overflow - is that really a MPEG file?\n"); exit(1); } } /* openm1v: Open a MPEG 1 video stream, check params, save SeqHdr and SeqExt (for MPEG-2) */ void open_m1v(char *filename) { int HorSize, VerSize, AspectRatio, marker_bit, VBVBufferSize; int CSPF, ProgSeq; int c, i, tag, pos; mpegin = fopen(filename,"r"); if(!mpegin) { fprintf(stderr,"Error opening %s\n",filename); perror("open"); exit(1); } /* read Sequence header */ if( fread(SeqHdr,1,12,mpegin) != 12 ) { fprintf(stderr,"Error reading %s\n",filename); exit(1); } /* Check if file contains sequence header */ if(SeqHdr[0]!=0 || SeqHdr[1]!=0 || SeqHdr[2]!=1 || SeqHdr[3]!=0xb3) { fprintf(stderr,"Error: File %s is not a MPEG 1 video stream\n",filename); exit(1); } printf("Opened MPEG 1 file %s\n\n",filename); pos = 32; HorSize = getbits(SeqHdr,pos,12); pos += 12; VerSize = getbits(SeqHdr,pos,12); pos += 12; AspectRatio = getbits(SeqHdr,pos, 4); pos += 4; FrameRate = getbits(SeqHdr,pos, 4); pos += 4; VideoBitRate = getbits(SeqHdr,pos,18); pos += 18; marker_bit = getbits(SeqHdr,pos, 1); pos += 1; VBVBufferSize= getbits(SeqHdr,pos,10); pos += 10; CSPF = getbits(SeqHdr,pos, 1); printf("Horizontal size: %5d\n",HorSize); printf("Vertical size: %5d\n",VerSize); printf("Aspect ratio: %5d\n",AspectRatio); printf("Frame rate: %5d = %.3f Pictures/sec\n",FrameRate,rates[FrameRate]); printf("bitrate: %5d = %d bits/sec\n",VideoBitRate,VideoBitRate*400); if(VideoBitRate==0x3ffff) printf("*** This is variable bitrate ***\n"); printf("marker bit: %5d\n",marker_bit); printf("VBV buffer size: %5d\n",VBVBufferSize); printf("CSPF: %5d\n",CSPF); /* check if we have to load intra or non intra quantizer matrices */ SeqHdrLen = 12; c = SeqHdr[SeqHdrLen-1]; if(c&2) /* Load intra */ for(i=0;i<64;i++) SeqHdr[SeqHdrLen++] = getc(mpegin); c = SeqHdr[SeqHdrLen-1]; if(c&1) /* Load non intra */ for(i=0;i<64;i++) SeqHdr[SeqHdrLen++] = getc(mpegin); lasttag = 0; gop_start_frame = 0; frame_no = 0; MPEG_frame_len = 0; twofields = 0; mpeg2 = 0; /* Search for MPEG-2 sequence extension header */ tag = 0xffffffff; while(1) { c = getc(mpegin); if(c==EOF) { fprintf(stderr,"Unexpected EOF in header\n"); exit(1); } tag = (tag<<8) | c; /* Break if first frame or seq. ext. header is reached */ if(tag == 0x100 || tag ==0x1b5) break; } if(tag==0x1b5) { printf("*** this is a MPEG-2 stream ***\n"); mpeg2 = 1; SeqExt[0] = 0x00; SeqExt[1] = 0x00; SeqExt[2] = 0x01; SeqExt[3] = 0xb5; if( fread(SeqExt+4,1,6,mpegin) != 6 ) { fprintf(stderr,"Unexpected EOF in header\n"); exit(1); } ProgSeq = getbits(SeqExt,44,1); printf("Progressive: %5d\n",ProgSeq); twofields = (ProgSeq==0); for(i=0;i<10;i++) SeqHdr[SeqHdrLen++] = SeqExt[i]; } /* Rewind file */ fseek(mpegin,0,SEEK_SET); } int get_m1v_frame() { int c, i, nvb; char *picture_header; if(lasttag == 0) { /* First time called, do some intializations */ /* Fill up lasttag */ c = getc(mpegin); lasttag = c; c = getc(mpegin); lasttag = (lasttag<<8) | c; c = getc(mpegin); lasttag = (lasttag<<8) | c; c = getc(mpegin); lasttag = (lasttag<<8) | c; /* Number of bytes in video buffer */ nvb = 0; seqhdr_seen = 0; /* search for start of first frame */ do { MPEGvbuff[nvb++] = FIRST_BYTE(lasttag); check_buffer_size(nvb); c = getc(mpegin); if(c==EOF) { fprintf(stderr,"Unexpected EOF when searching for 1st frame\n"); exit(1); } lasttag = (lasttag<<8) | c; } while (lasttag != 0x100); } else if (lasttag == 0x1b7) { /* We are at the end */ return 1; } else { /* Number of bytes in video buffer */ nvb = 0; } /* At this point we are just before a new frame is put into the buffer, remember buffer position */ picture_header = MPEGvbuff + nvb; MPEG_frame_no = frame_no; frame_no++; MPEG_frame_seq = gop_start_frame; /* Real seq no calculated later */ /* Search up to the start of the next frame (or end of MPEG) */ do { MPEGvbuff[nvb++] = FIRST_BYTE(lasttag); check_buffer_size(nvb); c = getc(mpegin); if(c==EOF) { fprintf(stderr,"Unexpected EOF in MPEG video stream\n"); return -1; } lasttag = (lasttag<<8) | c; /* check lasttag */ /* If the file contains allready sequence headers within the MPEG stream, set seqhdr_seen to avoid duplicate seq headers */ if(lasttag == 0x1b3) seqhdr_seen = 1; /* If we encounter a GOP header, we have to remember the number of the next frame to come and we will insert a sequence header just before the GOP header */ if(lasttag == 0x1b8) { gop_start_frame = frame_no; if(!seqhdr_seen) { for(i=0;i>17) & 3; protection = (header>>16) & 1; bit_rate = (header>>12) & 0xf; frequency = (header>>10) & 3; padding = (header>> 9) & 1; /* ??? */ mode = (header>> 6) & 3; mode_extension = (header>> 4) & 3; copyright = (header>> 3) & 1; original_copy = (header>> 2) & 1; emphasis = header & 3; AudioBitRate = bitrate_index[3-layer][bit_rate]; printf("\nAudio input file properties:\n\n"); printf("layer: %3d\n",3-layer+1); printf("protection: %3d\n",protection); printf("bit_rate: %3d = %d KB/s\n",bit_rate,AudioBitRate); printf("frequency: %3d = %2.1f kHz\n",frequency, frequency_index[frequency]); printf("mode: %3d = %s\n",mode,mode_index[mode]); printf("mode_extension: %3d\n",mode_extension); printf("copyright: %3d = %s\n",copyright, copyright_index[copyright]); printf("original_copy: %3d = %s\n",original_copy, original_index[original_copy]); printf("emphasis: %3d = %s\n",emphasis, emphasis_index[emphasis]); printf("\n"); if(layer!=2) { fprintf(stderr,"*** Can not handle layer %d files!\n",3-layer+1); exit(1); } if(AudioBitRate == 0) { fprintf(stderr,"*** Audio bitrate not supported!\n"); exit(1); } numwarn = 0; if(bit_rate!=11) { fprintf(stderr,"Warning: Bitrate for VCD should be 224 KBit/sec!\n"); numwarn++; } if(frequency!=0) { fprintf(stderr,"Warning: Frequency for VCD should be 44.1 kHz!\n"); numwarn++; } if(mode!=0) { fprintf(stderr,"Warning: Mode for VCD should be Stereo!\n"); numwarn++; } if(numwarn) { fprintf(stderr,"*** The audio file does not comply with VCD requirements ***\n"); fprintf(stderr,"*** Resulting output might not be readable everywhere ***\n"); } } static void buffer_timecode (unsigned long time, unsigned char marker, unsigned char *buffer) { buffer[0] = (marker << 4) | ((time >> 29) & 0x6) | 1; buffer[1] = (time & 0x3fc00000) >> 22; buffer[2] = ((time & 0x003f8000) >> 14) | 1; buffer[3] = (time & 0x7f80) >> 7; buffer[4] = ((time & 0x007f) << 1) | 1; } static void make_pack_header() { /* zero packet */ memset(packet,0,SECTOR_SIZE); /* PACK header is 0x1ba */ packet[0] = 0; packet[1] = 0; packet[2] = 1; packet[3] = 0xba; buffer_timecode (system_clock, MARKER_SCR, packet+4 ); packet[ 9] = (0x80 | (MuxRate >>15)); packet[10] = (0xff & (MuxRate >> 7)); packet[11] = (0x01 | ((MuxRate & 0x7f)<<1)); npb = 12; } static void make_system_header(int audio) { int fixed = 0; int CSPS = 0; int audio_lock = 0; int video_lock = 0; int audio_bound; int video_bound; int stream_id; int buffer_scale; int buffer_size; if(audio) { stream_id = 0xc0; audio_bound = 1; video_bound = 0; buffer_scale = 0; buffer_size = 32; } else { stream_id = 0xe0; audio_bound = 0; video_bound = 1; buffer_scale = 1; buffer_size = 46; } /* SYSTEM header is 0x1bb */ packet[npb++] = 0; packet[npb++] = 0; packet[npb++] = 1; packet[npb++] = 0xbb; /* Length is 9 */ packet[npb++] = 0; packet[npb++] = 9; packet[npb++] = (0x80 | (MuxRate >>15)); packet[npb++] = (0xff & (MuxRate >> 7)); packet[npb++] = (0x01 | ((MuxRate & 0x7f)<<1)); packet[npb++] = ((audio_bound << 2)|(fixed << 1)|CSPS); packet[npb++] = ((audio_lock << 7)| (video_lock << 6)|0x20|video_bound); packet[npb++] = 0xff; packet[npb++] = stream_id; packet[npb++] = (0xc0 | (buffer_scale << 5) | (buffer_size >> 8)); packet[npb++] = (buffer_size & 0xff); } /* * write_pack_packet: write out a sector consisting of a pack header * and a packet, add length entries for packet * and add a pad packet if wanted */ static void write_pack_packet(int add_pad) { int i, len; /* Safety first */ if(npb>SECTOR_SIZE) { fprintf(stderr,"Internal error: sector size exceeded!\n"); exit(1); } len = npb - 18; if(len>0) { packet[16] = len>>8; packet[17] = len&0xff; } /* Pad packet to neccesary length of SECTOR_SIZE */ if( add_pad && npb <= SECTOR_SIZE-8) { /* There is space for a PAD packet */ /* PAD header is 0x1be */ packet[npb++] = 0; packet[npb++] = 0; packet[npb++] = 1; packet[npb++] = 0xbe; len = SECTOR_SIZE - npb - 2; packet[npb++] = (len>>8); packet[npb++] = (len&0xff); packet[npb++] = 0xf; /* No timestamp for this package */ while(npb SECTOR_SIZE-34) { n = (remlen>SECTOR_SIZE-18) ? SECTOR_SIZE-18 : remlen; packet[npb++] = 0xf; /* No timestamp */ for(i=0;i last_message_time+10) { last_message_time += 10; printf("%4d seconds done\n",last_message_time); } for(i=0;i max_time_diff) max_time_diff = last_buffer_time-system_clock; need_padding = last_buffer_time-system_clock > MAX_VBUFFER_TIME; if(need_padding && !use_padding_sectors) { /* We don't insert padding sectors, we just skip one sector (hopefully that works with all players) */ system_clock += tpsect; need_padding = 0; } } }