/* $Id: OgmMux.c,v 1.13 2004/05/13 21:10:56 titer Exp $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #include "HBInternal.h" #include static int OgmStart( HBMux * ); static int OgmMux( HBMux *, void *, HBBuffer * ); static int OgmEnd( HBMux * ); struct HBMux { HB_MUX_COMMON_MEMBERS HBHandle * handle; HBTitle * title; FILE * file; }; typedef struct { int codec; ogg_stream_state os; int i_packet_no; } OgmMuxData; typedef struct __attribute__((__packed__)) { uint8_t i_packet_type; char stream_type[8]; char sub_type[4]; int32_t i_size; int64_t i_time_unit; int64_t i_samples_per_unit; int32_t i_default_len; int32_t i_buffer_size; int16_t i_bits_per_sample; int16_t i_padding_0; // hum hum union { struct { int32_t i_width; int32_t i_height; } video; struct { int16_t i_channels; int16_t i_block_align; int32_t i_avgbytespersec; } audio; } header; } ogg_stream_header_t; #define SetWLE( p, v ) _SetWLE( (uint8_t*)p, v) static void _SetWLE( uint8_t *p, uint16_t i_dw ) { p[1] = ( i_dw >> 8 )&0xff; p[0] = ( i_dw )&0xff; } #define SetDWLE( p, v ) _SetDWLE( (uint8_t*)p, v) static void _SetDWLE( uint8_t *p, uint32_t i_dw ) { p[3] = ( i_dw >> 24 )&0xff; p[2] = ( i_dw >> 16 )&0xff; p[1] = ( i_dw >> 8 )&0xff; p[0] = ( i_dw )&0xff; } #define SetQWLE( p, v ) _SetQWLE( (uint8_t*)p, v) static void _SetQWLE( uint8_t *p, uint64_t i_qw ) { SetDWLE( p, i_qw&0xffffffff ); SetDWLE( p+4, ( i_qw >> 32)&0xffffffff ); } HBMux * HBOgmMuxInit( HBHandle * handle, HBTitle * title ) { HBMux * m; HBAudio * audio; int i; if( !( m = calloc( sizeof( HBMux ), 1 ) ) ) { HBLog( "HBOgmMux: malloc failed, gonna crash" ); return NULL; } m->start = OgmStart; m->muxVideo = OgmMux; m->muxAudio = OgmMux; m->end = OgmEnd; m->handle = handle; m->title = title; /* Alloc muxer data */ title->muxData = calloc( sizeof( OgmMuxData ), 1 ); for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); audio->muxData = calloc( sizeof( OgmMuxData ), 1 ); } return m; } void HBOgmMuxClose( HBMux ** _m ) { HBMux * m = *_m; HBTitle * title = m->title; HBAudio * audio; int i; /* Free muxer data */ free( title->muxData ); for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); free( audio->muxData ); } free( m ); *_m = NULL; } static int OgmFlush( HBMux * m, OgmMuxData * muxData ) { for( ;; ) { ogg_page og; if( ogg_stream_flush( &muxData->os, &og ) == 0 ) { break; } if( fwrite( og.header, og.header_len, 1, m->file ) <= 0 || fwrite( og.body, og.body_len, 1, m->file ) <= 0 ) { return -1; } } return 0; } static int OgmStart( HBMux * m ) { HBTitle * title = m->title; HBAudio * audio; OgmMuxData * muxData; ogg_packet op; ogg_stream_header_t h; int i; /* Open output file */ if( ( m->file = fopen( title->file, "wb" ) ) == NULL ) { HBLog( "HBOgmMux: failed to open `%s'", title->file ); /* FIXME */ HBErrorOccured( m->handle, HB_ERROR_AVI_WRITE ); return -1; } HBLog( "HBOgmMux: `%s' opened", title->file ); /* Init tracks */ /* Video */ muxData = (OgmMuxData *) title->muxData; muxData->codec = title->codec; muxData->i_packet_no = 0; ogg_stream_init( &muxData->os, 0 ); /* Audio */ for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { HBAudio * audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); muxData = (OgmMuxData *) audio->muxData; muxData->codec = audio->outCodec; muxData->i_packet_no = 0; ogg_stream_init( &muxData->os, i + 1 ); } /* First pass: all b_o_s packets */ /* Video */ muxData = (OgmMuxData *) title->muxData; memset( &h, 0, sizeof( ogg_stream_header_t ) ); h.i_packet_type = 0x01; memcpy( h.stream_type, "video ", 8 ); if( muxData->codec == HB_CODEC_X264 ) { memcpy( h.sub_type, "H264", 4 ); } else { memcpy( h.sub_type, "XVID", 4 ); } SetDWLE( &h.i_size, sizeof( ogg_stream_header_t ) - 1); SetQWLE( &h.i_time_unit, (int64_t)10*1000*1000*(int64_t)title->rateBase/(int64_t)title->rate ); SetQWLE( &h.i_samples_per_unit, 1 ); SetDWLE( &h.i_default_len, 0 ); SetDWLE( &h.i_buffer_size, 1024*1024 ); SetWLE ( &h.i_bits_per_sample, 0 ); SetDWLE( &h.header.video.i_width, title->outWidth ); SetDWLE( &h.header.video.i_height, title->outHeight ); op.packet = (char*)&h; op.bytes = sizeof( ogg_stream_header_t ); op.b_o_s = 1; op.e_o_s = 0; op.granulepos = 0; op.packetno = muxData->i_packet_no++; ogg_stream_packetin( &muxData->os, &op ); OgmFlush( m, muxData ); /* Audio */ for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); muxData = (OgmMuxData *) audio->muxData; memset( &h, 0, sizeof( ogg_stream_header_t ) ); switch( muxData->codec ) { case HB_CODEC_MP3: { h.i_packet_type = 0x01; memcpy( h.stream_type, "audio ", 8 ); memcpy( h.sub_type, "55 ", 4 ); SetDWLE( &h.i_size, sizeof( ogg_stream_header_t ) - 1); SetQWLE( &h.i_time_unit, 0 ); SetQWLE( &h.i_samples_per_unit, audio->outSampleRate ); SetDWLE( &h.i_default_len, 1 ); SetDWLE( &h.i_buffer_size, 30*1024 ); SetWLE ( &h.i_bits_per_sample, 0 ); SetDWLE( &h.header.audio.i_channels, 2 ); SetDWLE( &h.header.audio.i_block_align, 0 ); SetDWLE( &h.header.audio.i_avgbytespersec, audio->outBitrate / 8 ); op.packet = (char*)&h; op.bytes = sizeof( ogg_stream_header_t ); op.b_o_s = 1; op.e_o_s = 0; op.granulepos = 0; op.packetno = muxData->i_packet_no++; ogg_stream_packetin( &muxData->os, &op ); break; } case HB_CODEC_VORBIS: { HBBuffer *h = HBFifoPop( audio->outFifo ); memcpy( &op, h->data, sizeof( ogg_packet ) ); op.packet = h->data + sizeof( ogg_packet ); ogg_stream_packetin( &muxData->os, &op ); break; } default: HBLog( "HBOgmMux: unhandled codec" ); break; } OgmFlush( m, muxData ); } /* second pass: all non b_o_s packets */ for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); if( audio->outCodec == HB_CODEC_VORBIS ) { HBBuffer *h; int j; muxData = (OgmMuxData *) audio->muxData; for( j = 0; j < 2; j++ ) { h = HBFifoPop( audio->outFifo ); memcpy( &op, h->data, sizeof( ogg_packet ) ); op.packet = h->data + sizeof( ogg_packet ); ogg_stream_packetin( &muxData->os, &op ); OgmFlush( m, muxData ); } } } HBLog( "HBOgmMux: headers written" ); return 0; } static int OgmMux( HBMux * m, void * _muxData, HBBuffer * buffer ) { OgmMuxData * muxData = (OgmMuxData *) _muxData; ogg_packet op; switch( muxData->codec ) { case HB_CODEC_FFMPEG: case HB_CODEC_XVID: case HB_CODEC_X264: op.bytes = buffer->size + 1; op.packet = malloc( op.bytes ); op.packet[0] = buffer->keyFrame ? 0x08 : 0x00; memcpy( &op.packet[1], buffer->data, buffer->size ); op.b_o_s = 0; op.e_o_s = 0; op.granulepos = muxData->i_packet_no; op.packetno = muxData->i_packet_no++; break; case HB_CODEC_MP3: op.bytes = buffer->size + 1; op.packet = malloc( op.bytes ); op.packet[0] = 0x08; memcpy( &op.packet[1], buffer->data, buffer->size ); op.b_o_s = 0; op.e_o_s = 0; op.granulepos = muxData->i_packet_no * 1152; op.packetno = muxData->i_packet_no++; break; case HB_CODEC_VORBIS: memcpy( &op, buffer->data, sizeof( ogg_packet ) ); op.packet = malloc( op.bytes ); memcpy( op.packet, buffer->data + sizeof( ogg_packet ), op.bytes ); break; default: HBLog( "HBOgmMux: unhandled codec" ); op.bytes = 0; op.packet = NULL; break; } if( op.packet ) { ogg_stream_packetin( &muxData->os, &op ); for( ;; ) { ogg_page og; if( ogg_stream_pageout( &muxData->os, &og ) == 0 ) { break; } if( fwrite( og.header, og.header_len, 1, m->file ) <= 0 || fwrite( og.body, og.body_len, 1, m->file ) <= 0 ) { HBLog( "HBOgmMux: write failed" ); break; } } free( op.packet ); } return 0; } static int OgmEnd( HBMux * m ) { HBTitle * title = m->title; HBAudio * audio; OgmMuxData * muxData; int i; muxData = (OgmMuxData *) title->muxData; if( OgmFlush( m, muxData ) < 0 ) { return -1; } for( i = 0; i < HBListCount( title->ripAudioList ); i++ ) { audio = (HBAudio *) HBListItemAt( title->ripAudioList, i ); muxData = (OgmMuxData *) audio->muxData; if( OgmFlush( m, muxData ) < 0 ) { return -1; } } fclose( m->file ); HBLog( "HBOgmMux: `%s' closed", title->file ); return 0; }