/* $Id: OgmMux.c,v 1.7 2004/03/08 11:32:48 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 void OgmMuxThread( void * );
static int OgmStart( HBOgmMux * );
static int OgmFlush( HBOgmMux *, int );
static int OgmEnd( HBOgmMux * );
struct HBOgmMux
{
HBHandle *handle;
HBTitle *title;
volatile int die;
HBThread *thread;
FILE *file;
int i_tk;
struct
{
HBFifo *fifo;
int codec;
ogg_stream_state os;
int i_packet_no;
} tk[100]; /* The first who set more than 100 stream !! */
};
HBOgmMux * HBOgmMuxInit( HBHandle * handle, HBTitle * title )
{
HBOgmMux *ogm = malloc( sizeof( HBOgmMux ) );
ogm->handle = handle;
ogm->title = title;
ogm->file = NULL;
ogm->i_tk = 0;
ogm->die = 0;
ogm->thread = HBThreadInit( "ogm muxer", OgmMuxThread, ogm, HB_NORMAL_PRIORITY );
return ogm;
}
void HBOgmMuxClose( HBOgmMux ** _ogm )
{
HBOgmMux *ogm = *_ogm;
ogm->die = 1;
HBThreadClose( &ogm->thread );
free( ogm );
*_ogm = NULL;
}
static int OgmDataWait( HBOgmMux *ogm )
{
int i;
for( i = 0; i < ogm->i_tk; i++ )
{
while( !ogm->die && HBFifoSize( ogm->tk[i].fifo ) <= 0 )
{
HBSnooze( 10000 );
}
}
return ogm->die ? -1 : 0;
}
static void OgmMuxThread( void * _this )
{
HBOgmMux *ogm = _this;
HBTitle *title = ogm->title;
int i;
/* Open output file */
if( ( ogm->file = fopen( title->file, "w" ) ) == NULL )
{
HBLog( "HBOgmMux: failed to open `%s'", title->file );
/* FIXME */
HBErrorOccured( ogm->handle, HB_ERROR_AVI_WRITE );
return;
}
HBLog( "HBOgmMux: `%s' opened", title->file );
/* Wait for data in each fifo */
HBLog( "HBOgmMux: waiting video/audio data" );
if( OgmDataWait( ogm ) < 0 )
{
HBLog( "HBOgmMux: exiting" );
fclose( ogm->file );
unlink( title->file );
return;
}
if( OgmStart( ogm ) < 0 )
{
HBLog( "HBOgmMux: failed to write headers" );
fclose( ogm->file );
unlink( title->file );
return;
}
HBLog( "HBOgmMux: headers written" );
for( ;; )
{
HBBuffer *buffer;
ogg_packet op;
int i_tk;
/* Wait data */
if( OgmDataWait( ogm ) < 0 )
{
break;
}
/* Choose the right track to write (interleaved data) */
for( i = 0, i_tk = -1; i < ogm->i_tk; i++ )
{
if( i_tk < 0 ||
HBFifoPosition( ogm->tk[i].fifo ) < HBFifoPosition( ogm->tk[i_tk].fifo ) )
{
i_tk = i;
}
}
buffer = HBFifoPop( ogm->tk[i_tk].fifo );
switch( ( ogm->tk[i_tk].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 = ogm->tk[i_tk].i_packet_no;
op.packetno = ogm->tk[i_tk].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 = ogm->tk[i_tk].i_packet_no * 1152;
op.packetno = ogm->tk[i_tk].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( &ogm->tk[i_tk].os, &op );
for( ;; )
{
ogg_page og;
if( ogg_stream_pageout( &ogm->tk[i_tk].os, &og ) == 0 )
{
break;
}
if( fwrite( og.header, og.header_len, 1, ogm->file ) <= 0 ||
fwrite( og.body, og.body_len, 1, ogm->file ) <= 0 )
{
HBLog( "HBOgmMux: write failed" );
break;
}
}
free( op.packet );
}
HBBufferClose( &buffer );
}
if( OgmEnd( ogm ) < 0 )
{
HBLog( "HBOgmMux: flush failed" );
}
fclose( ogm->file );
HBLog( "HBOgmMux: `%s' closed", title->file );
}
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 );
}
static int OgmFlush( HBOgmMux *ogm, int i_tk )
{
for( ;; )
{
ogg_page og;
if( ogg_stream_flush( &ogm->tk[i_tk].os, &og ) == 0 )
{
break;
}
if( fwrite( og.header, og.header_len, 1, ogm->file ) <= 0 ||
fwrite( og.body, og.body_len, 1, ogm->file ) <= 0 )
{
return -1;
}
}
return 0;
}
static int OgmStart( HBOgmMux *ogm )
{
HBTitle *title = ogm->title;
int i;
ogg_packet op;
/* Init track */
ogm->tk[0].codec = title->codec;
ogm->tk[0].fifo = title->outFifo;
ogm->tk[0].i_packet_no = 0;
ogg_stream_init (&ogm->tk[0].os, 0 );
for( i = 1; i < HBListCount( title->ripAudioList ) + 1; i++ )
{
HBAudio *audio = HBListItemAt( title->ripAudioList, i - 1 );
ogm->tk[i].codec = audio->outCodec;
ogm->tk[i].fifo = audio->outFifo;
ogm->tk[i].i_packet_no = 0;
ogg_stream_init (&ogm->tk[i].os, i );
}
ogm->i_tk = 1 + HBListCount( title->ripAudioList );
/* Wait data for each track */
for( i = 0; i < ogm->i_tk; i++ )
{
while( !ogm->die &&
( ( ogm->tk[i].codec == HB_CODEC_VORBIS && HBFifoSize( ogm->tk[i].fifo ) <= 3 ) ||
HBFifoSize( ogm->tk[i].fifo ) <= 0 ) )
{
HBSnooze( 10000 );
}
}
if( ogm->die )
{
return -1;
}
/* First pass: all b_o_s packets */
for( i = 0; i < ogm->i_tk; i++ )
{
ogg_stream_header_t h;
memset( &h, 0, sizeof( ogg_stream_header_t ) );
switch( ogm->tk[i].codec )
{
case HB_CODEC_FFMPEG:
case HB_CODEC_XVID:
case HB_CODEC_X264:
h.i_packet_type = 0x01;
memcpy( h.stream_type, "video ", 8 );
if( ogm->tk[i].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 = ogm->tk[i].i_packet_no++;
ogg_stream_packetin( &ogm->tk[i].os, &op );
break;
case HB_CODEC_MP3:
{
HBAudio *audio = HBListItemAt( title->ripAudioList, i - 1 );
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 = ogm->tk[i].i_packet_no++;
ogg_stream_packetin( &ogm->tk[i].os, &op );
break;
}
case HB_CODEC_VORBIS:
{
HBBuffer *h = HBFifoPop( ogm->tk[i].fifo );
memcpy( &op, h->data, sizeof( ogg_packet ) );
op.packet = h->data + sizeof( ogg_packet );
ogg_stream_packetin( &ogm->tk[i].os, &op );
break;
}
case HB_CODEC_AAC:
break;
default:
HBLog( "unhandled codec" );
break;
}
OgmFlush( ogm, i );
}
/* second pass: all non b_o_s packets */
for( i = 0; i < ogm->i_tk; i++ )
{
if( ogm->tk[i].codec == HB_CODEC_VORBIS )
{
HBBuffer *h;
int j;
for( j = 0; j < 2; j++ )
{
HBFifoWait( ogm->tk[i].fifo );
h = HBFifoPop( ogm->tk[i].fifo );
memcpy( &op, h->data, sizeof( ogg_packet ) );
op.packet = h->data + sizeof( ogg_packet );
ogg_stream_packetin( &ogm->tk[i].os, &op );
OgmFlush( ogm, i );
}
}
#if 0
else
{
/* Home made commentary */
op.packet = "\003Handbrake";
op.bytes = strlen( "\003Handbrake" );;
op.b_o_s = 0;
op.e_o_s = 0;
op.granulepos = 0;
op.packetno = ogm->tk[i].i_packet_no++;
ogg_stream_packetin( &ogm->tk[i].os, &op );
OgmFlush( ogm, i );
}
#endif
}
return 0;
}
static int OgmEnd( HBOgmMux *ogm )
{
int i;
for( i = 0; i < ogm->i_tk; i++ )
{
if( OgmFlush( ogm, i ) < 0 )
{
return -1;
}
}
return 0;
}