X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fmuxmp4.c;h=1264f9b56b8824e58ba005916fd7004fa58a04a9;hb=033e32de9c380f54c7d1362a3979da205ebc3a29;hp=bd54b11ce8c009ad580850e7d1d47c6882a31fe4;hpb=fe53480e5a2b90db404f340d1bbc8aeaed802622;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c index bd54b11c..1264f9b5 100644 --- a/libhb/muxmp4.c +++ b/libhb/muxmp4.c @@ -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,18 +27,42 @@ 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. + * 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 ) +{ + uint32_t tscale; + MP4Duration dur; + + tscale = MP4GetTrackTimeScale( m->file, trackId ); + dur = (MP4Duration)ceil( (double)tscale * (double)m->job->vrate_base / (double)m->job->vrate * 4.0 ); + + if( !MP4SetTrackDurationPerChunk( m->file, trackId, dur )) + { + hb_error( "muxmp4.c: MP4SetTrackDurationPerChunk failed!" ); + *m->job->die = 1; + return 0; + } + + hb_deep_log( 2, "muxmp4: track %u, chunk duration %"PRIu64, MP4FindTrackIndex( m->file, trackId ), dur ); + return 1; +} /********************************************************************** * MP4Init @@ -55,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 */ @@ -91,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; @@ -109,13 +117,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 ); @@ -133,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) @@ -143,6 +162,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, @@ -183,14 +207,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); @@ -201,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 ) @@ -302,7 +326,7 @@ static int MP4Init( hb_mux_object_t * m ) mux_data->track = MP4AddAC3AudioTrack( m->file, - m->samplerate, + audio->config.out.samplerate, fscod, bsid, bsmod, @@ -310,6 +334,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, @@ -326,7 +353,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, @@ -368,11 +399,105 @@ 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 )); } } + // 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", 255); + + 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 @@ -390,13 +515,259 @@ 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; } +typedef struct stylerecord_s { + enum style_s {ITALIC, BOLD, UNDERLINE} style; + uint16_t start; + uint16_t stop; + struct stylerecord_s *next; +} stylerecord; + +static void hb_makestylerecord( stylerecord **stack, + enum style_s style, int start ) +{ + stylerecord *record = calloc( sizeof( stylerecord ), 1 ); + + if( record ) + { + record->style = style; + record->start = start; + record->next = *stack; + *stack = record; + } +} + +static void hb_makestyleatom( stylerecord *record, uint8_t *style) +{ + uint8_t face = 1; + hb_deep_log(3, "Made style '%s' from %d to %d", + record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop); + + switch( record->style ) + { + case ITALIC: + face = 2; + break; + case BOLD: + face = 1; + break; + case UNDERLINE: + face = 4; + break; + default: + face = 2; + break; + } + + style[0] = (record->start >> 8) & 0xff; // startChar + style[1] = record->start & 0xff; + style[2] = (record->stop >> 8) & 0xff; // endChar + style[3] = record->stop & 0xff; + style[4] = (1 >> 8) & 0xff; // font-ID + style[5] = 1 & 0xff; + style[6] = face; // face-style-flags: 1 bold; 2 italic; 4 underline + style[7] = 24; // font-size + style[8] = 255; // r + style[9] = 255; // g + style[10] = 255; // b + style[11] = 255; // a + +} + +/* + * Copy the input to output removing markup and adding markup to the style + * atom where appropriate. + */ +static void hb_muxmp4_process_subtitle_style( uint8_t *input, + uint8_t *output, + uint8_t *style, uint16_t *stylesize ) +{ + uint8_t *reader = input; + uint8_t *writer = output; + uint8_t stylecount = 0; + uint16_t utf8_count = 0; // utf8 count from start of subtitle + stylerecord *stylestack = NULL; + stylerecord *oldrecord = NULL; + + while(*reader != '\0') { + if( ( *reader & 0xc0 ) == 0x80 ) + { + /* + * Track the utf8_count when doing markup so that we get the tx3g stops + * based on UTF8 chr counts rather than bytes. + */ + utf8_count++; + hb_deep_log( 3, "MuxMP4: Counted %d UTF-8 chrs within subtitle so far", + utf8_count); + } + if (*reader == '<') { + /* + * possible markup, peek at the next chr + */ + switch(*(reader+1)) { + case 'i': + if (*(reader+2) == '>') { + reader += 3; + hb_makestylerecord(&stylestack, ITALIC, (writer - output - utf8_count)); + } else { + *writer++ = *reader++; + } + break; + case 'b': + if (*(reader+2) == '>') { + reader += 3; + hb_makestylerecord(&stylestack, BOLD, (writer - output - utf8_count)); + } else { + *writer++ = *reader++; + } + break; + case 'u': + if (*(reader+2) == '>') { + reader += 3; + hb_makestylerecord(&stylestack, UNDERLINE, (writer - output - utf8_count)); + } else { + *writer++ = *reader++; + } + break; + case '/': + switch(*(reader+2)) { + case 'i': + if (*(reader+3) == '>') { + /* + * Check whether we then immediately start more markup of the same type, if so then + * lets not close it now and instead continue this markup. + */ + if ((*(reader+4) && *(reader+4) == '<') && + (*(reader+5) && *(reader+5) == 'i') && + (*(reader+6) && *(reader+6) == '>')) { + /* + * Opening italics right after, so don't close off these italics. + */ + hb_deep_log(3, "Joining two sets of italics"); + reader += (4 + 3); + continue; + } + + + if ((*(reader+4) && *(reader+4) == ' ') && + (*(reader+5) && *(reader+5) == '<') && + (*(reader+6) && *(reader+6) == 'i') && + (*(reader+7) && *(reader+7) == '>')) { + /* + * Opening italics right after, so don't close off these italics. + */ + hb_deep_log(3, "Joining two sets of italics (plus space)"); + reader += (4 + 4); + *writer++ = ' '; + continue; + } + if (stylestack && stylestack->style == ITALIC) { + uint8_t style_record[12]; + stylestack->stop = writer - output - utf8_count; + hb_makestyleatom(stylestack, style_record); + + memcpy(style + 10 + (12 * stylecount), style_record, 12); + stylecount++; + + oldrecord = stylestack; + stylestack = stylestack->next; + free(oldrecord); + } else { + hb_error("Mismatched Subtitle markup '%s'", input); + } + reader += 4; + } else { + *writer++ = *reader++; + } + break; + case 'b': + if (*(reader+3) == '>') { + if (stylestack && stylestack->style == BOLD) { + uint8_t style_record[12]; + stylestack->stop = writer - output - utf8_count; + hb_makestyleatom(stylestack, style_record); + + memcpy(style + 10 + (12 * stylecount), style_record, 12); + stylecount++; + oldrecord = stylestack; + stylestack = stylestack->next; + free(oldrecord); + } else { + hb_error("Mismatched Subtitle markup '%s'", input); + } + + reader += 4; + } else { + *writer++ = *reader++; + } + break; + case 'u': + if (*(reader+3) == '>') { + if (stylestack && stylestack->style == UNDERLINE) { + uint8_t style_record[12]; + stylestack->stop = writer - output - utf8_count; + hb_makestyleatom(stylestack, style_record); + + memcpy(style + 10 + (12 * stylecount), style_record, 12); + stylecount++; + + oldrecord = stylestack; + stylestack = stylestack->next; + free(oldrecord); + } else { + hb_error("Mismatched Subtitle markup '%s'", input); + } + reader += 4; + } else { + *writer++ = *reader++; + } + break; + default: + *writer++ = *reader++; + break; + } + break; + default: + *writer++ = *reader++; + break; + } + } else { + *writer++ = *reader++; + } + } + *writer = '\0'; + + if( stylecount ) + { + *stylesize = 10 + ( stylecount * 12 ); + + memcpy( style + 4, "styl", 4); + + style[0] = 0; + style[1] = 0; + style[2] = (*stylesize >> 8) & 0xff; + style[3] = *stylesize & 0xff; + style[8] = (stylecount >> 8) & 0xff; + style[9] = stylecount & 0xff; + + } + +} + static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, hb_buffer_t * buf ) { @@ -412,8 +783,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 %"PRId64", start %"PRId64"," + "stop %"PRId64", sum_dur %"PRId64, + offset, buf->start, buf->stop, m->sum_dur ); + offset = 0; + } } /* Add the sample before the new frame. @@ -428,56 +805,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 %"PRId64", start %"PRId64"," + "stop %"PRId64", sum_dur %"PRId64, + 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 { @@ -485,19 +851,143 @@ 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 ( 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; + } + uint8_t styleatom[2048];; + uint16_t stylesize = 0; + uint8_t buffer[2048]; + uint16_t buffersize = 0; + uint8_t output[2048]; + + *buffer = '\0'; + + /* + * Copy the subtitle into buffer stripping markup and creating + * style atoms for them. + */ + hb_muxmp4_process_subtitle_style( buf->data, + buffer, + styleatom, &stylesize ); + + buffersize = strlen((char*)buffer); + + hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%"PRId64": %s", + (float)buf->start / 90000, buf->start, buf->stop, + (buf->stop - buf->start), buffer); + + /* Write the subtitle sample */ + memcpy( output + 2, buffer, buffersize ); + memcpy( output + 2 + buffersize, styleatom, stylesize); + output[0] = ( buffersize >> 8 ) & 0xff; + output[1] = buffersize & 0xff; + + if( !MP4WriteSample( m->file, + mux_data->track, + output, + buffersize + stylesize + 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); + } + } + 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; } @@ -512,8 +1002,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, @@ -530,7 +1020,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 ) @@ -548,20 +1038,44 @@ static int MP4End( hb_mux_object_t * m ) if( title->metadata ) { hb_metadata_t *md = title->metadata; + const MP4Tags* tags; hb_deep_log( 2, "Writing Metadata to output file..."); - MP4SetMetadataName( m->file, md->name ); - MP4SetMetadataArtist( m->file, md->artist ); - MP4SetMetadataComposer( m->file, md->composer ); - MP4SetMetadataComment( m->file, md->comment ); - MP4SetMetadataReleaseDate( m->file, md->release_date ); - MP4SetMetadataAlbum( m->file, md->album ); - MP4SetMetadataGenre( m->file, md->genre ); + /* allocate tags structure */ + tags = MP4TagsAlloc(); + /* fetch data from MP4 file (in case it already has some data) */ + MP4TagsFetch( tags, m->file ); + + /* populate */ + 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 ) { - MP4SetMetadataCoverArt( m->file, md->coverart, md->coverart_size); + 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 );