OSDN Git Service

Matroska muxer!
authorsaintdev <saintdev@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Sat, 14 Jul 2007 02:24:41 +0000 (02:24 +0000)
committersaintdev <saintdev@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Sat, 14 Jul 2007 02:24:41 +0000 (02:24 +0000)
-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://localhost/HandBrake/trunk@680 b64f7644-9d1e-0410-96f1-a4d463321fa5

Jamfile
contrib/Jamfile
contrib/version_libmkv.txt [new file with mode: 0644]
libhb/Jamfile
libhb/common.h
libhb/internal.h
libhb/muxcommon.c
libhb/muxmkv.c [new file with mode: 0644]
test/test.c

diff --git a/Jamfile b/Jamfile
index a3fa8f4..a84abd2 100644 (file)
--- 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
 {
index d133ee6..7be6aa7 100644 (file)
@@ -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 (file)
index 0000000..d8496b2
--- /dev/null
@@ -0,0 +1 @@
+http://download.m0k.org/handbrake/contrib/libmkv-0.6.0.tar.gz
index f301451..ce04831 100644 (file)
@@ -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) ;
 
index 123e528..81988de 100644 (file)
@@ -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;
index 60fb80c..ced8d57 100644 (file)
@@ -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 );
 
index 3fc13b8..009b81b 100644 (file)
@@ -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 (file)
index 0000000..653fe74
--- /dev/null
@@ -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;
+}
index 105050e..d3ae5a7 100644 (file)
@@ -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;
+            }
         }
 
     }