OSDN Git Service

CLI: Missed file from SubRip - a symptom of too many views and patches
[handbrake-jp/handbrake-jp-git.git] / libhb / muxmp4.c
index 9698faf..aaaa7d9 100644 (file)
@@ -18,9 +18,7 @@ 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;
@@ -29,16 +27,15 @@ struct hb_mux_object_s
     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;
+    MP4TrackId  track;
+    uint8_t     subtitle;
+    int         sub_format;
+
+    uint64_t    sum_dur; // sum of the frame durations so far
 };
 
 /* Tune video track chunk duration.
@@ -80,21 +77,11 @@ static int MP4Init( hb_mux_object_t * m )
     hb_audio_t    * audio;
     hb_mux_data_t * mux_data;
     int i;
+    int subtitle_default;
 
     /* 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 */
@@ -116,14 +103,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;
@@ -134,7 +117,7 @@ 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 */
@@ -169,7 +152,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)
@@ -242,7 +225,7 @@ static int MP4Init( hb_mux_object_t * m )
     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 ) );
+        mux_data = calloc(1, sizeof( hb_mux_data_t ) );
         audio->priv.mux_data = mux_data;
 
         if( audio->config.out.codec == HB_ACODEC_AC3 )
@@ -421,6 +404,100 @@ static int MP4Init( hb_mux_object_t * m )
 
     }
 
+    // Quicktime requires that at least one subtitle is enabled,
+    // else it doesn't show any of the subtitles.
+    // So check to see if any of the subtitles are flagged to be
+    // the defualt.  The default will the the enabled track, else
+    // enable the first track.
+    subtitle_default = 0;
+    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->config.dest == PASSTHRUSUB )
+        {
+            if ( subtitle->config.default_track )
+                subtitle_default = 1;
+        }
+    }
+    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->config.dest == PASSTHRUSUB )
+        {
+            uint64_t width, height = 60;
+            if( job->anamorphic.mode )
+                width = job->width * ( (float) job->anamorphic.par_width / job->anamorphic.par_height );
+            else
+                width = job->width;
+
+            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, 90000, width, height );
+
+            MP4SetTrackLanguage(m->file, mux_data->track, subtitle->iso639_2);
+
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
+
+            const uint8_t textColor[4] = { 255,255,255,255 };
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 2);
+
+            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", height);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", 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 - height) * 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 ( !subtitle_default || subtitle->config.default_track ) {
+                /* Enable the default subtitle track */
+                MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
+                subtitle_default = 1;
+            }
+            else
+            {
+                MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
+            }
+        }
+    }
+
     if (job->chapter_markers)
     {
         /* add a text track for the chapters. We add the 'chap' atom to track
@@ -468,8 +545,14 @@ 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.
@@ -484,56 +567,45 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
             // 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 );
 
-            chapter = hb_list_item( m->job->title->list_chapter,
-                                    buf->new_chap - 2 );
+                MP4AddChapter( m->file,
+                               m->chapter_track,
+                               duration,
+                               (chapter != NULL) ? chapter->title : NULL);
 
-            MP4AddChapter( m->file,
-                           m->chapter_track,
-                           duration,
-                           (chapter != NULL) ? chapter->title : NULL);
-
-            m->current_chapter = buf->new_chap;
-            m->chapter_duration += duration;
+                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
     {
@@ -541,19 +613,124 @@ 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,
-                         ( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data ) ?
-                            ( buf->frametype == HB_FRAME_IDR ) : ( ( 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 ( mux_data->sum_dur < buf->start )
+            {
+                uint8_t empty[2] = {0,0};
+                if( !MP4WriteSample( m->file,
+                                    mux_data->track,
+                                    empty,
+                                    2,
+                                    buf->start - mux_data->sum_dur,
+                                    0,
+                                    1 ))
+                {
+                    hb_error("Failed to write to output file, disk full?");
+                    *job->die = 1;
+                } 
+                mux_data->sum_dur += buf->start - mux_data->sum_dur;
+            }
+
+            /* 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 + 2,
+                                 buf->stop - buf->start,
+                                 0,
+                                 1 ))
+            {
+                hb_error("Failed to write to output file, disk full?");
+                *job->die = 1;
+            }
+
+            mux_data->sum_dur += (buf->stop - buf->start);
+            hb_deep_log(3, "MuxMP4:Sub:%fs:%lld:%lld:%lld: %s", (float)buf->start / 90000, buf->start, buf->stop, 
+                   (buf->stop - buf->start), buf->data);
+            hb_deep_log(3, "MuxMP4:Total time elapsed:%lld", mux_data->sum_dur);
+        }
+    }
+    else
+    {
+        /*
+         * Audio
+         */
+        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;
 }
@@ -569,8 +746,8 @@ static int MP4End( hb_mux_object_t * m )
         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 )
         {
 
             chapter = hb_list_item( m->job->title->list_chapter,
@@ -587,7 +764,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 )
@@ -615,13 +792,20 @@ static int MP4End( hb_mux_object_t * m )
         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( strlen( md->name ))
+            MP4TagsSetName( tags, md->name );
+        if( strlen( md->artist ))
+            MP4TagsSetArtist( tags, md->artist );
+        if( strlen( md->composer ))
+            MP4TagsSetComposer( tags, md->composer );
+        if( strlen( md->comment ))
+            MP4TagsSetComments( tags, md->comment );
+        if( strlen( md->release_date ))
+            MP4TagsSetReleaseDate( tags, md->release_date );
+        if( strlen( md->album ))
+            MP4TagsSetAlbum( tags, md->album );
+        if( strlen( md->genre ))
+            MP4TagsSetGenre( tags, md->genre );
 
         if( md->coverart )
         {