summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsaintdev <[email protected]>2007-07-14 02:24:41 +0000
committersaintdev <[email protected]>2007-07-14 02:24:41 +0000
commitd779db015e45de223ff533649d2a16319da713e0 (patch)
treee0cf952a01516a3437635f79717f3289f546fd75
parent94f9f929935a52ddf7899ff67be5f4cc56c68759 (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--Jamfile3
-rw-r--r--contrib/Jamfile15
-rw-r--r--contrib/version_libmkv.txt1
-rw-r--r--libhb/Jamfile2
-rw-r--r--libhb/common.h1
-rw-r--r--libhb/internal.h1
-rw-r--r--libhb/muxcommon.c2
-rw-r--r--libhb/muxmkv.c324
-rw-r--r--test/test.c50
9 files changed, 382 insertions, 17 deletions
diff --git a/Jamfile b/Jamfile
index a3fa8f447..a84abd2e4 100644
--- a/Jamfile
+++ b/Jamfile
@@ -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;
+ }
}
}