OSDN Git Service

Soft Subtitle Support in the MP4 Muxer.
[handbrake-jp/handbrake-jp-git.git] / libhb / muxmp4.c
index afc1f33..f49ba74 100644 (file)
@@ -9,8 +9,6 @@
 
 #include "hb.h"
 
-void AddIPodUUID(MP4FileHandle, MP4TrackId);
-
 struct hb_mux_object_s
 {
     HB_MUX_COMMON;
@@ -20,120 +18,52 @@ struct hb_mux_object_s
     /* libmp4v2 handle */
     MP4FileHandle file;
 
-    /* Cumulated durations so far, in output & input timescale units (see MP4Mux) */
-    int64_t sum_dur;        // duration in output timescale units
-    int64_t sum_dur_in;     // duration in input 90KHz timescale units
+    int64_t sum_dur;    // sum of video frame durations so far
 
     // bias to keep render offsets in ctts atom positive (set up by encx264)
     int64_t init_delay;
 
+    uint64_t sum_sub_duration; // sum of subtitle frame durations so far
+
     /* Chapter state information for muxing */
     MP4TrackId chapter_track;
     int current_chapter;
     uint64_t chapter_duration;
-
-    /* Sample rate of the first audio track.
-     * Used for the timescale
-     */
-    int samplerate;
 };
 
 struct hb_mux_data_s
 {
     MP4TrackId track;
+    uint8_t    subtitle;
+    int        sub_format;
 };
 
-struct hb_text_sample_s
-{
-    uint8_t     sample[1280];
-    uint32_t    length;
-    MP4Duration duration;
-};
-
-/**********************************************************************
- * MP4CreateTextSample
- **********************************************************************
- * Creates a buffer for a text track sample
- *********************************************************************/
-static struct hb_text_sample_s *MP4CreateTextSample( char *textString, uint64_t duration )
+/* Tune video track chunk duration.
+ * libmp4v2 default duration == dusamplerate == 1 second.
+ * Per van's suggestion we desire duration == 4 frames.
+ * Should be invoked immediately after track creation.
+ *
+ * return true on fail, false on success.
+ */
+static int MP4TuneTrackDurationPerChunk( hb_mux_object_t* m, MP4TrackId trackId )
 {
-    struct hb_text_sample_s *sample = NULL;
-    int stringLength = strlen(textString);
-    int x;
-
-    if( stringLength < 1024 )
-    {
-        sample = malloc( sizeof( struct hb_text_sample_s ) );
-
-        //textLength = (stringLength; // Account for BOM
-        sample->length = stringLength + 2 + 12; // Account for text length code and other marker
-        sample->duration = (MP4Duration)duration;
-
-        // 2-byte length marker
-        sample->sample[0] = (stringLength >> 8) & 0xff;
-        sample->sample[1] = stringLength & 0xff;
+    uint32_t tscale;
+    MP4Duration dur;
 
-        strncpy( (char *)&(sample->sample[2]), textString, stringLength );
-
-        x = 2 + stringLength;
-
-        // Modifier Length Marker
-        sample->sample[x] = 0x00;
-        sample->sample[x+1] = 0x00;
-        sample->sample[x+2] = 0x00;
-        sample->sample[x+3] = 0x0C;
-
-        // Modifier Type Code
-        sample->sample[x+4] = 'e';
-        sample->sample[x+5] = 'n';
-        sample->sample[x+6] = 'c';
-        sample->sample[x+7] = 'd';
-
-        // Modifier Value
-        sample->sample[x+8] = 0x00;
-        sample->sample[x+9] = 0x00;
-        sample->sample[x+10] = (256 >> 8) & 0xff;
-        sample->sample[x+11] = 256 & 0xff;
-    }
-
-    return sample;
-}
-
-/**********************************************************************
- * MP4GenerateChapterSample
- **********************************************************************
- * Creates a buffer for a text track sample
- *********************************************************************/
-static struct hb_text_sample_s *MP4GenerateChapterSample( hb_mux_object_t * m,
-                                                          uint64_t duration,
-                                                          int chapter )
-{
-    // We substract 1 from the chapter number because the chapters start at
-    // 1 but our name array starts at 0. We substract another 1 because we're
-    // writing the text of the previous chapter mark (when we get the start
-    // of chapter 2 we know the duration of chapter 1 & can write its mark).
-    hb_chapter_t *chapter_data = hb_list_item( m->job->title->list_chapter,
-                                               chapter - 2 );
-    char tmp_buffer[1024];
-    char *string = tmp_buffer;
-
-    tmp_buffer[0] = '\0';
-
-    if( chapter_data != NULL )
-    {
-        string = chapter_data->title;
-    }
+    tscale = MP4GetTrackTimeScale( m->file, trackId );
+    dur = (MP4Duration)ceil( (double)tscale * (double)m->job->vrate_base / (double)m->job->vrate * 4.0 );
 
-    if( strlen(string) == 0 || strlen(string) >= 1024 )
+    if( !MP4SetTrackDurationPerChunk( m->file, trackId, dur ))
     {
-        snprintf( tmp_buffer, 1023, "Chapter %03i", chapter - 2 );
-        string = tmp_buffer;
+        hb_error( "muxmp4.c: MP4SetTrackDurationPerChunk failed!" );
+        *m->job->die = 1;
+        return 0;
     }
 
-    return MP4CreateTextSample( string, duration );
+    hb_deep_log( 2, "muxmp4: track %u, chunk duration %llu", MP4FindTrackIndex( m->file, trackId ), dur );
+    return 1;
 }
 
-
 /**********************************************************************
  * MP4Init
  **********************************************************************
@@ -147,22 +77,10 @@ static int MP4Init( hb_mux_object_t * m )
     hb_audio_t    * audio;
     hb_mux_data_t * mux_data;
     int i;
-    uint16_t language_code;
 
     /* Flags for enabling/disabling tracks in an MP4. */
     typedef enum { TRACK_DISABLED = 0x0, TRACK_ENABLED = 0x1, TRACK_IN_MOVIE = 0x2, TRACK_IN_PREVIEW = 0x4, TRACK_IN_POSTER = 0x8}  track_header_flags;
 
-    if( (audio = hb_list_item(title->list_audio, 0)) != NULL )
-    {
-        /* Need the sample rate of the first audio track to use as the timescale. */
-        m->samplerate = audio->config.out.samplerate;
-        audio = NULL;
-    }
-    else
-    {
-        m->samplerate = 90000;
-    }
-
     /* Create an empty mp4 file */
     if (job->largeFileSize)
     /* Use 64-bit MP4 file */
@@ -184,14 +102,10 @@ static int MP4Init( hb_mux_object_t * m )
     }
 
     /* Video track */
-    mux_data      = malloc( sizeof( hb_mux_data_t ) );
+    mux_data      = calloc(1, sizeof( hb_mux_data_t ) );
     job->mux_data = mux_data;
 
-    /* When using the standard 90000 timescale, QuickTime tends to have
-       synchronization issues (audio not playing at the correct speed).
-       To workaround this, we use the audio samplerate as the
-       timescale */
-    if (!(MP4SetTimeScale( m->file, m->samplerate )))
+    if (!(MP4SetTimeScale( m->file, 90000 )))
     {
         hb_error("muxmp4.c: MP4SetTimeScale failed!");
         *job->die = 1;
@@ -202,13 +116,24 @@ static int MP4Init( hb_mux_object_t * m )
     {
         /* Stolen from mp4creator */
         MP4SetVideoProfileLevel( m->file, 0x7F );
-               mux_data->track = MP4AddH264VideoTrack( m->file, m->samplerate,
+               mux_data->track = MP4AddH264VideoTrack( m->file, 90000,
                        MP4_INVALID_DURATION, job->width, job->height,
                        job->config.h264.sps[1], /* AVCProfileIndication */
                        job->config.h264.sps[2], /* profile_compat */
                        job->config.h264.sps[3], /* AVCLevelIndication */
                        3 );      /* 4 bytes length before each NAL unit */
+        if ( mux_data->track == MP4_INVALID_TRACK_ID )
+        {
+            hb_error( "muxmp4.c: MP4AddH264VideoTrack failed!" );
+            *job->die = 1;
+            return 0;
+        }
 
+        /* Tune track chunk duration */
+        if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
+        {
+            return 0;
+        }
 
         MP4AddH264SequenceParameterSet( m->file, mux_data->track,
                 job->config.h264.sps, job->config.h264.sps_length );
@@ -226,7 +151,7 @@ static int MP4Init( hb_mux_object_t * m )
     else /* FFmpeg or XviD */
     {
         MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
-        mux_data->track = MP4AddVideoTrack( m->file, m->samplerate,
+        mux_data->track = MP4AddVideoTrack( m->file, 90000,
                 MP4_INVALID_DURATION, job->width, job->height,
                 MP4_MPEG4_VIDEO_TYPE );
         if (mux_data->track == MP4_INVALID_TRACK_ID)
@@ -236,6 +161,11 @@ static int MP4Init( hb_mux_object_t * m )
             return 0;
         }
 
+        /* Tune track chunk duration */
+        if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
+        {
+            return 0;
+        }
 
         /* VOL from FFmpeg or XviD */
         if (!(MP4SetTrackESConfiguration( m->file, mux_data->track,
@@ -276,14 +206,14 @@ static int MP4Init( hb_mux_object_t * m )
         MP4AddColr(m->file, mux_data->track, 6, 1, 6);
     }
 
-    if( job->pixel_ratio )
+    if( job->anamorphic.mode )
     {
         /* PASP atom for anamorphic video */
         float width, height;
 
-        width = job->pixel_aspect_width;
+        width  = job->anamorphic.par_width;
 
-        height = job->pixel_aspect_height;
+        height = job->anamorphic.par_height;
 
         MP4AddPixelAspectRatio(m->file, mux_data->track, (uint32_t)width, (uint32_t)height);
 
@@ -293,15 +223,8 @@ static int MP4Init( hb_mux_object_t * m )
        /* add the audio tracks */
     for( i = 0; i < hb_list_count( title->list_audio ); i++ )
     {
-       static uint8_t reserved2[16] = {
-               0x00, 0x00, 0x00, 0x00,
-               0x00, 0x00, 0x00, 0x00,
-               0x00, 0x02, 0x00, 0x10,
-               0x00, 0x00, 0x00, 0x00,
-           };
-
         audio = hb_list_item( title->list_audio, i );
-        mux_data = malloc( sizeof( hb_mux_data_t ) );
+        mux_data = calloc(1, sizeof( hb_mux_data_t ) );
         audio->priv.mux_data = mux_data;
 
         if( audio->config.out.codec == HB_ACODEC_AC3 )
@@ -402,7 +325,7 @@ static int MP4Init( hb_mux_object_t * m )
 
             mux_data->track = MP4AddAC3AudioTrack(
                 m->file,
-                m->samplerate, 
+                audio->config.out.samplerate, 
                 fscod,
                 bsid,
                 bsmod,
@@ -410,6 +333,9 @@ static int MP4Init( hb_mux_object_t * m )
                 lfeon,
                 bit_rate_code);
 
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
+
             if (audio->config.out.name == NULL) {
                 MP4SetTrackBytesProperty(
                     m->file, mux_data->track,
@@ -426,7 +352,11 @@ static int MP4Init( hb_mux_object_t * m )
         } else {
             mux_data->track = MP4AddAudioTrack(
                 m->file,
-                m->samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
+                audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
+
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
+
             if (audio->config.out.name == NULL) {
                 MP4SetTrackBytesProperty(
                     m->file, mux_data->track,
@@ -451,11 +381,7 @@ static int MP4Init( hb_mux_object_t * m )
         }
 
         /* Set the language for this track */
-        /* The language is stored as 5-bit text - 0x60 */
-        language_code = audio->config.lang.iso639_2[0] - 0x60;   language_code <<= 5;
-        language_code |= audio->config.lang.iso639_2[1] - 0x60;  language_code <<= 5;
-        language_code |= audio->config.lang.iso639_2[2] - 0x60;
-        MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.mdhd.language", language_code);
+        MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2);
 
         if( hb_list_count( title->list_audio ) > 1 )
         {
@@ -472,19 +398,83 @@ static int MP4Init( hb_mux_object_t * m )
                them all at once. */
         {
             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
-            hb_deep_log( 2, "muxp4: disabled extra audio track %i", mux_data->track-1);
+            hb_deep_log( 2, "muxmp4: disabled extra audio track %u", MP4FindTrackIndex( m->file, mux_data->track ));
         }
 
     }
 
-       if (job->chapter_markers)
+    for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
+    {
+        hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
+
+        if( subtitle && subtitle->format == TEXTSUB && 
+            subtitle->dest == PASSTHRUSUB )
+        {
+            mux_data = calloc(1, sizeof( hb_mux_data_t ) );
+            subtitle->mux_data = mux_data;
+            mux_data->subtitle = 1;
+            mux_data->sub_format = subtitle->format;
+            mux_data->track = MP4AddSubtitleTrack( m->file, 1 );
+
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
+
+            const uint8_t textColor[4] = { 255,255,255,255 };
+            uint64_t subHeight = 60;
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 2);
+
+            MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.width", job->width);
+            MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.height", subHeight);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.dataReferenceIndex", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.horizontalJustification", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.verticalJustification", 0);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.bgColorAlpha", 255);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxBottom", subHeight);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", job->width);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontID", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontSize", 24);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorRed", textColor[0]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorGreen", textColor[1]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorBlue", textColor[2]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorAlpha", textColor[3]);
+            
+            /* translate the track */
+            uint8_t* val;
+            uint8_t nval[36];
+            uint32_t *ptr32 = (uint32_t*) nval;
+            uint32_t size;
+
+            MP4GetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", &val, &size);
+            memcpy(nval, val, size);
+
+            const uint32_t ytranslation = (job->height - subHeight) * 0x10000;
+                
+#ifdef WORDS_BIGENDIAN
+            ptr32[7] = ytranslation;
+#else
+            /* we need to switch the endianness, as the file format expects big endian */
+            ptr32[7] = ((ytranslation & 0x000000FF) << 24) + ((ytranslation & 0x0000FF00) << 8) + 
+                            ((ytranslation & 0x00FF0000) >> 8) + ((ytranslation & 0xFF000000) >> 24);
+#endif
+
+            MP4SetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", nval, size);  
+        }
+    }
+
+    if (job->chapter_markers)
     {
         /* add a text track for the chapters. We add the 'chap' atom to track
            one which is usually the video track & should never be disabled.
            The Quicktime spec says it doesn't matter which media track the
            chap atom is on but it has to be an enabled track. */
         MP4TrackId textTrack;
-        textTrack = MP4AddChapterTextTrack(m->file, 1);
+        textTrack = MP4AddChapterTextTrack(m->file, 1, 0);
 
         m->chapter_track = textTrack;
         m->chapter_duration = 0;
@@ -494,8 +484,16 @@ static int MP4Init( hb_mux_object_t * m )
     /* Add encoded-by metadata listing version and build date */
     char *tool_string;
     tool_string = (char *)malloc(80);
-    snprintf( tool_string, 80, "HandBrake %s %i", HB_VERSION, HB_BUILD);
-    MP4SetMetadataTool(m->file, tool_string);
+    snprintf( tool_string, 80, "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD);
+
+    /* allocate,fetch,populate,store,free tags structure */
+    const MP4Tags* tags;
+    tags = MP4TagsAlloc();
+    MP4TagsFetch( tags, m->file );
+    MP4TagsSetEncodingTool( tags, tool_string );
+    MP4TagsStore( tags, m->file );
+    MP4TagsFree( tags );
+
     free(tool_string);
 
     return 0;
@@ -516,76 +514,67 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
         // (we'll need it for both the video frame & the chapter track)
         if ( m->init_delay )
         {
-            offset = ( buf->start + m->init_delay ) * m->samplerate / 90000 -
-                     m->sum_dur;
+            offset = buf->start + m->init_delay - m->sum_dur;
+            if ( offset < 0 )
+            {
+                hb_log("MP4Mux: illegal render offset %lld, start %lld,"
+                       "stop %lld, sum_dur %lld",
+                       offset, buf->start, buf->stop, m->sum_dur );
+                offset = 0;
+            }
         }
+
         /* Add the sample before the new frame.
            It is important that this be calculated prior to the duration
            of the new video sample, as we want to sync to right after it.
            (This is because of how durations for text tracks work in QT) */
         if( job->chapter_markers && buf->new_chap )
-        {
-            struct hb_text_sample_s *sample;
+        {    
+            hb_chapter_t *chapter = NULL;
 
             // this chapter is postioned by writing out the previous chapter.
             // the duration of the previous chapter is the duration up to but
             // not including the current frame minus the duration of all
             // chapters up to the previous.
+            // The initial and final chapters can be very short (a second or
+            // less) since they're not really chapters but just a placeholder to
+            // insert a cell command. We don't write chapters shorter than 1.5 sec.
             duration = m->sum_dur - m->chapter_duration + offset;
-            if ( duration <= 0 )
+            if ( duration >= (90000*3)/2 )
             {
-                /* The initial & final chapters can have very short durations
-                 * (less than the error in our total duration estimate) so
-                 * the duration calc above can result in a negative number.
-                 * when this happens give the chapter a short duration (1/3
-                 * of an ntsc frame time). */
-                duration = 1000 * m->samplerate / 90000;
-            }
+                chapter = hb_list_item( m->job->title->list_chapter,
+                                        buf->new_chap - 2 );
 
-            sample = MP4GenerateChapterSample( m, duration, buf->new_chap );
+                MP4AddChapter( m->file,
+                               m->chapter_track,
+                               duration,
+                               (chapter != NULL) ? chapter->title : NULL);
 
-            if( !MP4WriteSample(m->file,
-                                m->chapter_track,
-                                sample->sample,
-                                sample->length,
-                                sample->duration,
-                                0, true) )
-            {
-                hb_error("Failed to write to output file, disk full?");
-                *job->die = 1;
+                m->current_chapter = buf->new_chap;
+                m->chapter_duration += duration;
             }
-            free(sample);
-            m->current_chapter = buf->new_chap;
-            m->chapter_duration += duration;
         }
 
-        // since we're changing the sample rate we need to keep track of
-        // the truncation bias so that the audio and video don't go out
-        // of sync. m->sum_dur_in is the sum of the input durations so far.
-        // m->sum_dur is the sum of the output durations. Their difference
-        // (in output sample rate units) is the accumulated truncation bias.
-        int64_t bias = ( m->sum_dur_in * m->samplerate / 90000 ) - m->sum_dur;
-        int64_t dur_in = buf->stop - buf->start;
-        duration = dur_in * m->samplerate / 90000 + bias;
+        // We're getting the frames in decode order but the timestamps are
+        // for presentation so we have to use durations and effectively
+        // compute a DTS.
+        duration = buf->stop - buf->start;
         if ( duration <= 0 )
         {
             /* We got an illegal mp4/h264 duration. This shouldn't
                be possible and usually indicates a bug in the upstream code.
                Complain in the hope that someone will go find the bug but
                try to fix the error so that the file will still be playable. */
-            hb_log("MP4Mux: illegal duration %lld, bias %lld, start %lld (%lld),"
-                   "stop %lld (%lld), sum_dur %lld",
-                   duration, bias, buf->start * m->samplerate / 90000, buf->start,
-                   buf->stop * m->samplerate / 90000, buf->stop, m->sum_dur );
+            hb_log("MP4Mux: illegal duration %lld, start %lld,"
+                   "stop %lld, sum_dur %lld",
+                   duration, buf->start, buf->stop, m->sum_dur );
             /* we don't know when the next frame starts so we can't pick a
-               valid duration for this one so we pick something "short"
-               (roughly 1/3 of an NTSC frame time) and rely on the bias calc
-               for the next frame to correct things (a duration underestimate
-               just results in a large bias on the next frame). */
-            duration = 1000 * m->samplerate / 90000;
+               valid duration for this one. we pick something "short"
+               (roughly 1/3 of an NTSC frame time) to take time from
+               the next frame. */
+            duration = 1000;
         }
         m->sum_dur += duration;
-        m->sum_dur_in += dur_in;
     }
     else
     {
@@ -593,18 +582,120 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
         duration = MP4_INVALID_DURATION;
     }
 
-    // Here's where the sample actually gets muxed.
-    if( !MP4WriteSample( m->file,
-                         mux_data->track,
-                         buf->data,
-                         buf->size,
-                         duration,
-                         offset,
-                         ((buf->frametype & HB_FRAME_KEY) != 0) ) )
+    /* Here's where the sample actually gets muxed. */
+    if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
     {
-        hb_error("Failed to write to output file, disk full?");
-        *job->die = 1;
+        /* Compute dependency flags.
+         *
+         * This mechanism is (optionally) used by media players such as QuickTime
+         * to offer better scrubbing performance. The most influential bits are
+         * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
+         *
+         * Other bits are possible but no example media using such bits have been
+         * found.
+         *
+         * It is acceptable to supply 0-bits for any samples which characteristics
+         * cannot be positively guaranteed.
+         */
+        int sync = 0;
+        uint32_t dflags = 0;
+
+        /* encoding layer signals if frame is referenced by other frames */
+        if( buf->flags & HB_FRAME_REF )
+            dflags |= MP4_SDT_HAS_DEPENDENTS;
+        else
+            dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
+
+        switch( buf->frametype )
+        {
+            case HB_FRAME_IDR:
+                sync = 1;
+                break;
+            case HB_FRAME_I:
+                dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
+                break;
+            case HB_FRAME_P:
+                dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
+                break;
+            case HB_FRAME_BREF:
+            case HB_FRAME_B:
+            default:
+                break; /* nothing to mark */
+        }
+
+        if( !MP4WriteSampleDependency( m->file,
+                                       mux_data->track,
+                                       buf->data,
+                                       buf->size,
+                                       duration,
+                                       offset,
+                                       sync,
+                                       dflags ))
+        {
+            hb_error("Failed to write to output file, disk full?");
+            *job->die = 1;
+        }
     }
+    else if (mux_data->subtitle)
+    {
+        if( mux_data->sub_format == TEXTSUB )
+        {
+            /* Write an empty sample */
+            if ( m->sum_sub_duration < buf->start )
+            {
+                uint8_t empty[2] = {0,0};
+                if( !MP4WriteSample( m->file,
+                                    mux_data->track,
+                                    empty,
+                                    2,
+                                    buf->start - m->sum_sub_duration,
+                                    0,
+                                    1 ))
+                {
+                    hb_error("Failed to write to output file, disk full?");
+                    *job->die = 1;
+                }
+                m->sum_sub_duration += buf->start - m->sum_sub_duration;
+            }
+
+            /* Write the subtitle sample */
+            uint8_t buffer[2048];
+            memcpy( buffer + 2, buf->data, buf->size );
+            buffer[0] = ( buf->size >> 8 ) & 0xff;
+            buffer[1] = buf->size & 0xff;
+            
+            if( !MP4WriteSample( m->file,
+                                 mux_data->track,
+                                 buffer,
+                                 buf->size,
+                                 buf->stop - buf->start,
+                                 0,
+                                 1 ))
+            {
+                hb_error("Failed to write to output file, disk full?");
+                *job->die = 1;
+            }
+
+            m->sum_sub_duration += buf->stop - buf->start;
+            hb_log("MuxMP4:Sub:%lld:%lld: %s", buf->start, buf->stop, buf->data);
+            hb_log("MuxMP4:Total time elapsed:%lld", m->sum_sub_duration);
+        }
+    }
+    else
+    {
+        if( !MP4WriteSample( m->file,
+                             mux_data->track,
+                             buf->data,
+                             buf->size,
+                             duration,
+                             offset,
+                             ( buf->frametype & HB_FRAME_KEY ) != 0 ))
+        {
+            hb_error("Failed to write to output file, disk full?");
+            *job->die = 1;
+        }
+    }
+
 
     return 0;
 }
@@ -612,25 +703,25 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
 static int MP4End( hb_mux_object_t * m )
 {
     hb_job_t   * job   = m->job;
+    hb_title_t * title = job->title;
 
     /* Write our final chapter marker */
     if( m->job->chapter_markers )
     {
+        hb_chapter_t *chapter = NULL;
         int64_t duration = m->sum_dur - m->chapter_duration;
         /* The final chapter can have a very short duration - if it's less
-         * than a second just skip it. */
-        if ( duration >= m->samplerate )
+         * than 1.5 seconds just skip it. */
+        if ( duration >= (90000*3)/2 )
         {
 
-            struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, duration,
-                                                    m->current_chapter + 1 );
-            if( ! MP4WriteSample(m->file, m->chapter_track, sample->sample,
-                                 sample->length, sample->duration, 0, true) )
-            {
-                hb_error("Failed to write to output file, disk full?");
-                *job->die = 1;
-            }
-            free(sample);
+            chapter = hb_list_item( m->job->title->list_chapter,
+                                    m->current_chapter - 1 );
+
+            MP4AddChapter( m->file,
+                           m->chapter_track,
+                           duration,
+                           (chapter != NULL) ? chapter->title : NULL);
         }
     }
 
@@ -638,7 +729,7 @@ static int MP4End( hb_mux_object_t * m )
     {
            // Insert track edit to get A/V back in sync.  The edit amount is
            // the init_delay.
-           int64_t edit_amt = m->init_delay * m->samplerate / 90000;
+           int64_t edit_amt = m->init_delay;
            MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
                            MP4GetTrackDuration(m->file, 1), 0);
             if ( m->job->chapter_markers )
@@ -650,6 +741,45 @@ static int MP4End( hb_mux_object_t * m )
             }
      }
 
+    /*
+     * Write the MP4 iTunes metadata if we have any metadata
+     */
+    if( title->metadata )
+    {
+        hb_metadata_t *md = title->metadata;
+        const MP4Tags* tags;
+
+        hb_deep_log( 2, "Writing Metadata to output file...");
+
+        /* allocate tags structure */
+        tags = MP4TagsAlloc();
+        /* fetch data from MP4 file (in case it already has some data) */
+        MP4TagsFetch( tags, m->file );
+
+        /* populate */
+        MP4TagsSetName( tags, md->name );
+        MP4TagsSetArtist( tags, md->artist );
+        MP4TagsSetComposer( tags, md->composer );
+        MP4TagsSetComments( tags, md->comment );
+        MP4TagsSetReleaseDate( tags, md->release_date );
+        MP4TagsSetAlbum( tags, md->album );
+        MP4TagsSetGenre( tags, md->genre );
+
+        if( md->coverart )
+        {
+            MP4TagArtwork art;
+            art.data = md->coverart;
+            art.size = md->coverart_size;
+            art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
+            MP4TagsAddArtwork( tags, &art );
+        }
+
+        /* push data to MP4 file */
+        MP4TagsStore( tags, m->file );
+        /* free memory associated with structure */
+        MP4TagsFree( tags );
+    }
+
     MP4Close( m->file );
 
     if ( job->mp4_optimize )