diff options
author | saintdev <[email protected]> | 2007-07-14 02:24:41 +0000 |
---|---|---|
committer | saintdev <[email protected]> | 2007-07-14 02:24:41 +0000 |
commit | d779db015e45de223ff533649d2a16319da713e0 (patch) | |
tree | e0cf952a01516a3437635f79717f3289f546fd75 | |
parent | 94f9f929935a52ddf7899ff67be5f4cc56c68759 (diff) |
Matroska muxer!
-Chapters don't work in VLC. I'll need to update the library to work-around this. Most other players should pick them up, however.
-PAR, check.
-x264 b-frames, check.
-Multi-track audio, check.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@680 b64f7644-9d1e-0410-96f1-a4d463321fa5
-rw-r--r-- | Jamfile | 3 | ||||
-rw-r--r-- | contrib/Jamfile | 15 | ||||
-rw-r--r-- | contrib/version_libmkv.txt | 1 | ||||
-rw-r--r-- | libhb/Jamfile | 2 | ||||
-rw-r--r-- | libhb/common.h | 1 | ||||
-rw-r--r-- | libhb/internal.h | 1 | ||||
-rw-r--r-- | libhb/muxcommon.c | 2 | ||||
-rw-r--r-- | libhb/muxmkv.c | 324 | ||||
-rw-r--r-- | test/test.c | 50 |
9 files changed, 382 insertions, 17 deletions
@@ -15,7 +15,8 @@ HANDBRAKE_LIBS = libhb.a contrib/lib/libmp3lame.a contrib/lib/libmpeg2.a contrib/lib/libvorbis.a contrib/lib/libvorbisenc.a contrib/lib/libogg.a contrib/lib/libsamplerate.a - contrib/lib/libx264.a contrib/lib/libxvidcore.a ; + contrib/lib/libx264.a contrib/lib/libxvidcore.a + contrib/lib/libmkv.a ; if $(OS) = UNKNOWN { diff --git a/contrib/Jamfile b/contrib/Jamfile index d133ee695..7be6aa72a 100644 --- a/contrib/Jamfile +++ b/contrib/Jamfile @@ -390,3 +390,18 @@ Wget $(SUBDIR)/zlib.tar.gz : $(SUBDIR)/version_zlib.txt ; Zlib $(SUBDIR)/lib/libz.a : $(SUBDIR)/zlib.tar.gz ; } +rule LibMkv +{ + Depends $(<) : $(>) ; + Depends lib : $(<) ; +} +actions LibMkv +{ + cd `dirname $(>)` && CONTRIB=`pwd` && + rm -rf libmkv && tar xzf libmkv.tar.gz && cd libmkv && + ./configure --disable-shared --enable-static --prefix=$CONTRIB && + make && make install && + strip -S $CONTRIB/lib/libmkv.a +} +Wget $(SUBDIR)/libmkv.tar.gz : $(SUBDIR)/version_libmkv.txt ; +LibMkv $(SUBDIR)/lib/libmkv.a : $(SUBDIR)/libmkv.tar.gz ; diff --git a/contrib/version_libmkv.txt b/contrib/version_libmkv.txt new file mode 100644 index 000000000..d8496b213 --- /dev/null +++ b/contrib/version_libmkv.txt @@ -0,0 +1 @@ +http://download.m0k.org/handbrake/contrib/libmkv-0.6.0.tar.gz diff --git a/libhb/Jamfile b/libhb/Jamfile index f3014518a..ce04831df 100644 --- a/libhb/Jamfile +++ b/libhb/Jamfile @@ -10,7 +10,7 @@ LIBHB_SRC = ipodutil.cpp common.c hb.c ports.c scan.c work.c decmpeg2.c encavcodec.c update.c demuxmpeg.c fifo.c render.c reader.c muxcommon.c muxmp4.c sync.c stream.c decsub.c deca52.c decdca.c encfaac.c declpcm.c encx264.c decavcodec.c encxvid.c -muxavi.c enclame.c muxogm.c encvorbis.c dvd.c ; +muxavi.c enclame.c muxogm.c encvorbis.c dvd.c muxmkv.c ; Library libhb : $(LIBHB_SRC) ; diff --git a/libhb/common.h b/libhb/common.h index 123e528c2..81988de3d 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -243,6 +243,7 @@ struct hb_job_s #define HB_MUX_AVI 0x040000 #define HB_MUX_OGM 0x080000 #define HB_MUX_IPOD 0x100000 +#define HB_MUX_MKV 0x200000 int mux; const char * file; diff --git a/libhb/internal.h b/libhb/internal.h index 60fb80cc8..ced8d5777 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -224,4 +224,5 @@ typedef struct hb_mux_data_s hb_mux_data_t; DECLARE_MUX( mp4 ); DECLARE_MUX( avi ); DECLARE_MUX( ogm ); +DECLARE_MUX( mkv ); diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c index 3fc13b8c7..009b81bd6 100644 --- a/libhb/muxcommon.c +++ b/libhb/muxcommon.c @@ -80,6 +80,8 @@ static void MuxerFunc( void * _mux ) case HB_MUX_OGM: m = hb_mux_ogm_init( job ); break; + case HB_MUX_MKV: + m = hb_mux_mkv_init( job ); } } diff --git a/libhb/muxmkv.c b/libhb/muxmkv.c new file mode 100644 index 000000000..653fe74ac --- /dev/null +++ b/libhb/muxmkv.c @@ -0,0 +1,324 @@ +/* $Id: $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.m0k.org/>. + It may be used under the terms of the GNU General Public License. */ + +/* libmkv header */ +#include "libmkv.h" + +#include <ogg/ogg.h> + +#include "hb.h" + +struct hb_mux_object_s +{ + HB_MUX_COMMON; + + hb_job_t * job; + + mk_Writer * file; +}; + +struct hb_mux_data_s +{ + mk_Track * track; + uint64_t prev_chapter_tc; + uint64_t max_tc; + uint16_t current_chapter; +}; + +/********************************************************************** + * MKVInit + ********************************************************************** + * Allocates hb_mux_data_t structures, create file and write headers + *********************************************************************/ +static int MKVInit( hb_mux_object_t * m ) +{ + hb_job_t * job = m->job; + hb_title_t * title = job->title; + hb_audio_t * audio; + hb_mux_data_t * mux_data; + + uint8_t *avcC = NULL; + uint8_t default_track_flag = 1; + int avcC_len, i; + ogg_packet *ogg_headers[3]; + mk_TrackConfig *track; + + track = calloc(1, sizeof(mk_TrackConfig)); + + m->file = mk_createWriter(job->file, 1000000, 1); + + /* Video track */ + mux_data = calloc(1, sizeof( hb_mux_data_t ) ); + job->mux_data = mux_data; + + track->trackType = MK_TRACK_VIDEO; + track->flagDefault = 1; + switch (job->vcodec) + { + case HB_VCODEC_X264: + track->codecID = MK_VCODEC_MP4AVC; + /* Taken from x264 muxers.c */ + avcC_len = 5 + 1 + 2 + job->config.h264.sps_length + 1 + 2 + job->config.h264.pps_length; + avcC = malloc(avcC_len); + if (avcC == NULL) + return -1; + + avcC[0] = 1; + avcC[1] = job->config.h264.sps[1]; /* AVCProfileIndication */ + avcC[2] = job->config.h264.sps[2]; /* profile_compat */ + avcC[3] = job->config.h264.sps[3]; /* AVCLevelIndication */ + avcC[4] = 0xff; // nalu size length is four bytes + avcC[5] = 0xe1; // one sps + + avcC[6] = job->config.h264.sps_length >> 8; + avcC[7] = job->config.h264.sps_length; + + memcpy(avcC+8, job->config.h264.sps, job->config.h264.sps_length); + + avcC[8+job->config.h264.sps_length] = 1; // one pps + avcC[9+job->config.h264.sps_length] = job->config.h264.pps_length >> 8; + avcC[10+job->config.h264.sps_length] = job->config.h264.pps_length; + + memcpy( avcC+11+job->config.h264.sps_length, job->config.h264.pps, job->config.h264.pps_length ); + track->codecPrivate = avcC; + track->codecPrivateSize = avcC_len; + break; + case HB_VCODEC_XVID: + case HB_VCODEC_FFMPEG: + track->codecID = MK_VCODEC_MP4ASP; + track->codecPrivate = job->config.mpeg4.bytes; + track->codecPrivateSize = job->config.mpeg4.length; + break; + default: + *job->die = 1; + hb_log("muxmkv: Unknown video codec: %x", job->vcodec); + return 0; + } + + track->video.pixelWidth = job->width; + track->video.pixelHeight = job->height; + track->video.displayHeight = job->height; + if(job->pixel_ratio) + { + track->video.displayWidth = job->width * ((double)job->pixel_aspect_width / (double)job->pixel_aspect_height); + } + else + { + track->video.displayWidth = job->width; + } + + + track->defaultDuration = (int64_t)(((float)job->vrate_base / (float)job->vrate) * 1000000000); + + mux_data->track = mk_createTrack(m->file, track); + + memset(track, 0, sizeof(mk_TrackConfig)); + + /* add the audio tracks */ + for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + { + audio = hb_list_item( title->list_audio, i ); + mux_data = malloc( sizeof( hb_mux_data_t ) ); + audio->mux_data = mux_data; + + switch (job->acodec) + { + case HB_ACODEC_AC3: + track->codecPrivate = NULL; + track->codecPrivateSize = 0; + track->codecID = MK_ACODEC_AC3; + break; + case HB_ACODEC_LAME: + track->codecPrivate = NULL; + track->codecPrivateSize = 0; + track->codecID = MK_ACODEC_MP3; + break; + case HB_ACODEC_VORBIS: + { + int i, j; + int64_t offset = 0; + int64_t cp_size = 0; + char *cp; + track->codecID = MK_ACODEC_VORBIS; + cp_size = sizeof( char ); + for (i = 0; i < 3; ++i) + { + ogg_headers[i] = (ogg_packet *)audio->config.vorbis.headers[i]; + ogg_headers[i]->packet = (unsigned char *)&audio->config.vorbis.headers[i] + sizeof( ogg_packet ); + cp_size += (sizeof( char ) * ((ogg_headers[i]->bytes / 255) + 1)) + ogg_headers[i]->bytes; + /* This will be too big, but it doesn't matter, as we only need it to be big enough. */ + } + cp = track->codecPrivate = calloc(1, cp_size); + cp[offset++] = 0x02; + for (i = 0; i < 2; ++i) + { + for (j = ogg_headers[i]->bytes; j >= 255; j -= 255) + { + cp[offset++] = 255; + } + cp[offset++] = j; + } + for(i = 0; i < 3; ++i) + { + memcpy(cp + offset, ogg_headers[i]->packet, ogg_headers[i]->bytes); + offset += ogg_headers[i]->bytes; + } + track->codecPrivateSize = offset; + } + break; + case HB_ACODEC_FAAC: + track->codecPrivate = audio->config.aac.bytes; + track->codecPrivateSize = audio->config.aac.length; + track->codecID = MK_ACODEC_AAC; + break; + default: + *job->die = 1; + hb_log("muxmkv: Unknown audio codec: %x", job->acodec); + return 0; + } + + if (default_track_flag) + { + track->flagDefault = 1; + default_track_flag = 0; + } + + track->trackType = MK_TRACK_AUDIO; + track->language = audio->iso639_2; + track->audio.samplingFreq = (float)job->arate; + track->audio.channels = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown); +// track->defaultDuration = job->arate * 1000; + mux_data->track = mk_createTrack(m->file, track); + if (track->codecPrivate != NULL) + free(track->codecPrivate); + } + + mk_writeHeader( m->file, "HandBrake " HB_VERSION); + if (track != NULL) + free(track); + if (avcC != NULL) + free(avcC); + + return 0; +} + +static int MKVMux( hb_mux_object_t * m, hb_mux_data_t * mux_data, + hb_buffer_t * buf ) +{ + hb_job_t * job = m->job; + hb_title_t * title = job->title; + uint64_t timecode = 0; + hb_chapter_t *chapter_data; + char tmp_buffer[1024]; + char *string = tmp_buffer; + if (mux_data == job->mux_data) + { + /* Video */ + /* Where does the 11130 come from? I had to calculate it from the actual + * and the observed duration of the file. Otherwise the timecodes come + * out way too small, and you get a 2hr movie that plays in .64 sec. */ + if ((job->vcodec == HB_VCODEC_X264) && (buf->frametype & HB_FRAME_REF)) + { + timecode = (buf->start + (buf->renderOffset - 1000000)) * 11130; + } + else + { + timecode = buf->start * 11130; + } + + if (job->chapter_markers && (buf->new_chap || timecode == 0)) + { + /* Make sure we're not writing a chapter that has 0 length */ + if (mux_data->prev_chapter_tc != timecode) + { + chapter_data = hb_list_item( title->list_chapter, mux_data->current_chapter ); + tmp_buffer[0] = '\0'; + + if( chapter_data != NULL ) + { + string = chapter_data->title; + } + + if( strlen(string) == 0 || strlen(string) >= 1024 ) + { + snprintf( tmp_buffer, 1023, "Chapter %02i", mux_data->current_chapter++ ); + string = tmp_buffer; + } + mk_createChapterSimple(m->file, mux_data->prev_chapter_tc, timecode, string); + } + mux_data->prev_chapter_tc = timecode; + } + + if (buf->stop * 11130 > mux_data->max_tc) + mux_data->max_tc = buf->stop * 11130; + } + else + { + /* Audio */ + timecode = buf->start * 11130; + if (job->acodec == HB_ACODEC_VORBIS) + { + /* ughhh, vorbis is a pain :( */ + ogg_packet *op; + + op = (ogg_packet *)buf->data; + op->packet = buf->data + sizeof( ogg_packet ); + mk_startFrame(m->file, mux_data->track); + mk_addFrameData(m->file, mux_data->track, op->packet, op->bytes); + mk_setFrameFlags(m->file, mux_data->track, timecode, 1); + return 0; + } + } + + mk_startFrame(m->file, mux_data->track); + mk_addFrameData(m->file, mux_data->track, buf->data, buf->size); + mk_setFrameFlags(m->file, mux_data->track, timecode, + ((job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data) ? (buf->frametype == HB_FRAME_IDR) : ((buf->frametype & HB_FRAME_KEY) != 0)) ); + return 0; +} + +static int MKVEnd( hb_mux_object_t * m ) +{ + hb_job_t *job = m->job; + hb_mux_data_t *mux_data = job->mux_data; + hb_title_t *title = job->title; + hb_chapter_t *chapter_data = hb_list_item( title->list_chapter, mux_data->current_chapter ); + char tmp_buffer[1024]; + char *string = tmp_buffer; + + if(job->chapter_markers) + { + tmp_buffer[0] = '\0'; + + if( chapter_data != NULL ) + { + string = chapter_data->title; + } + + if( strlen(string) == 0 || strlen(string) >= 1024 ) + { + snprintf( tmp_buffer, 1023, "Chapter %02i", mux_data->current_chapter ); + string = tmp_buffer; + } + mk_createChapterSimple(m->file, mux_data->prev_chapter_tc, mux_data->max_tc, string); + } + + mk_close(m->file); + + // TODO: Free what we alloc'd + + return 0; +} + +hb_mux_object_t * hb_mux_mkv_init( hb_job_t * job ) +{ + hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 ); + m->init = MKVInit; + m->mux = MKVMux; + m->end = MKVEnd; + m->job = job; + return m; +} diff --git a/test/test.c b/test/test.c index 105050ecc..d3ae5a794 100644 --- a/test/test.c +++ b/test/test.c @@ -741,7 +741,7 @@ static void ShowHelp() "### Destination Options------------------------------------------------------\n\n" " -o, --output <string> Set output file name\n" - " -f, --format <string> Set output format (avi/mp4/ogm, default:\n" + " -f, --format <string> Set output format (avi/mp4/ogm/mkv, default:\n" " autodetected from file name)\n" " -4, --large-file Use 64-bit mp4 files that can hold more than\n" " 4 GB. Note: Breaks iPod, @TV, PS3 compatibility.\n""" @@ -966,7 +966,7 @@ static int ParseOptions( int argc, char ** argv ) else if( !strcasecmp( optarg, "dpl2" ) ) { audio_mixdown = HB_AMIXDOWN_DOLBYPLII; - } + } else if( !strcasecmp( optarg, "6ch" ) ) { audio_mixdown = HB_AMIXDOWN_6CH; @@ -1011,11 +1011,11 @@ static int ParseOptions( int argc, char ** argv ) vcodec = HB_VCODEC_X264; h264_13 = 1; } - else if( !strcasecmp( optarg, "x264b30" ) ) - { - vcodec = HB_VCODEC_X264; - h264_30 = 1; - } + else if( !strcasecmp( optarg, "x264b30" ) ) + { + vcodec = HB_VCODEC_X264; + h264_30 = 1; + } else { fprintf( stderr, "invalid codec (%s)\n", optarg ); @@ -1031,6 +1031,14 @@ static int ParseOptions( int argc, char ** argv ) { acodec = HB_ACODEC_LAME; } + else if( !strcasecmp( optarg, "faac" ) ) + { + acodec = HB_ACODEC_FAAC; + } + else if( !strcasecmp( optarg, "vorbis") ) + { + acodec = HB_ACODEC_VORBIS; + } break; case 'w': width = atoi( optarg ); @@ -1159,18 +1167,22 @@ static int CheckOptions( int argc, char ** argv ) mux = HB_MUX_AVI; } else if( p && ( !strcasecmp( p, ".mp4" ) || - !strcasecmp( p, ".m4v" ) ) ) + !strcasecmp( p, ".m4v" ) ) ) { - if ( h264_30 == 1 ) + if ( h264_30 == 1 ) mux = HB_MUX_IPOD; - else - mux = HB_MUX_MP4; + else + mux = HB_MUX_MP4; } else if( p && ( !strcasecmp( p, ".ogm" ) || !strcasecmp( p, ".ogg" ) ) ) { mux = HB_MUX_OGM; } + else if( p && !strcasecmp(p, ".mkv" ) ) + { + mux = HB_MUX_MKV; + } else { fprintf( stderr, "Output format couldn't be guessed " @@ -1184,20 +1196,24 @@ static int CheckOptions( int argc, char ** argv ) } else if( !strcasecmp( format, "mp4" ) ) { - if ( h264_30 == 1) - mux = HB_MUX_IPOD; + if ( h264_30 == 1) + mux = HB_MUX_IPOD; else - mux = HB_MUX_MP4; + mux = HB_MUX_MP4; } else if( !strcasecmp( format, "ogm" ) || !strcasecmp( format, "ogg" ) ) { mux = HB_MUX_OGM; } + else if( !strcasecmp( format, "mkv" ) ) + { + mux = HB_MUX_MKV; + } else { fprintf( stderr, "Invalid output format (%s). Possible " - "choices are avi, mp4 and ogm\n.", format ); + "choices are avi, mp4, m4v, ogm, ogg and mkv\n.", format ); return 1; } @@ -1215,6 +1231,10 @@ static int CheckOptions( int argc, char ** argv ) { acodec = HB_ACODEC_VORBIS; } + else if( mux == HB_MUX_MKV ) + { + acodec = HB_ACODEC_AC3; + } } } |