diff options
Diffstat (limited to 'libhb/muxmp4.c')
-rw-r--r-- | libhb/muxmp4.c | 1249 |
1 files changed, 0 insertions, 1249 deletions
diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c deleted file mode 100644 index 946277574..000000000 --- a/libhb/muxmp4.c +++ /dev/null @@ -1,1249 +0,0 @@ -/* muxmp4.c - - Copyright (c) 2003-2014 HandBrake Team - This file is part of the HandBrake source code - Homepage: <http://handbrake.fr/>. - It may be used under the terms of the GNU General Public License v2. - For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html - */ - -#include "hb.h" - -#if defined(USE_MP4V2) - -#include "mp4v2/mp4v2.h" - -struct hb_mux_object_s -{ - HB_MUX_COMMON; - - hb_job_t * job; - - /* output file name in current code page */ - char * path; - /* libmp4v2 handle */ - MP4FileHandle file; - - int64_t sum_dur; // sum of video frame durations so far - - hb_buffer_t *delay_buf; - - /* Chapter state information for muxing */ - MP4TrackId chapter_track; - int current_chapter; - uint64_t chapter_duration; -}; - -struct hb_mux_data_s -{ - MP4TrackId track; - uint8_t subtitle; - int sub_format; - - uint64_t sum_dur; // sum of the frame durations so far - - // audio frame duration info - int sample_rate; - int samples_per_frame; -}; - -/* 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->done_error = HB_ERROR_UNKNOWN; - *m->job->die = 1; - return 0; - } - - hb_deep_log( 2, "muxmp4: track %u, chunk duration %"PRIu64, MP4FindTrackIndex( m->file, trackId ), dur ); - return 1; -} - -static const uint16_t ac3_sample_rate_tab[3] = { 48000, 44100, 32000 }; -/* possible bitrates */ -static const uint16_t ac3_bitrate_tab[19] = { - 32, 40, 48, 56, 64, 80, 96, 112, 128, - 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 -}; - - -/********************************************************************** - * MP4Init - ********************************************************************** - * Allocates hb_mux_data_t structures, create file and write headers - *********************************************************************/ -static int MP4Init( 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; - int i; - int subtitle_default; - - /* Flags for enabling/disabling tracks in an MP4. */ - enum - { - TRACK_DISABLED = 0x0, - TRACK_ENABLED = 0x1, - TRACK_IN_MOVIE = 0x2, - TRACK_IN_PREVIEW = 0x4, - TRACK_IN_POSTER = 0x8 - }; - - m->path = hb_utf8_to_cp(job->file); - if (m->path == NULL) - { - hb_error("Could not convert string, out of memory?"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - - /* Create an empty mp4 file */ - if (job->largeFileSize) - /* Use 64-bit MP4 file */ - { - m->file = MP4Create(m->path, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA); - hb_deep_log( 2, "muxmp4: using 64-bit MP4 formatting."); - } - else - /* Limit MP4s to less than 4 GB */ - { - m->file = MP4Create(m->path, MP4_DETAILS_ERROR, 0); - } - - if (m->file == MP4_INVALID_FILE_HANDLE) - { - hb_error("muxmp4.c: MP4Create failed!"); - *job->done_error = HB_ERROR_WRONG_INPUT; - *job->die = 1; - return 0; - } - - /* Video track */ - mux_data = calloc(1, sizeof( hb_mux_data_t ) ); - job->mux_data = mux_data; - - if (!(MP4SetTimeScale( m->file, 90000 ))) - { - hb_error("muxmp4.c: MP4SetTimeScale failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - - if (job->vcodec & HB_VCODEC_H264_MASK) - { - /* Stolen from mp4creator */ - MP4SetVideoProfileLevel( m->file, 0x7F ); - 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->done_error = HB_ERROR_INIT; - *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 ); - MP4AddH264PictureParameterSet( m->file, mux_data->track, - job->config.h264.pps, job->config.h264.pps_length ); - - if( job->ipod_atom ) - { - hb_deep_log( 2, "muxmp4: adding iPod atom"); - MP4AddIPodUUID(m->file, mux_data->track); - } - } - else if ( job->vcodec == HB_VCODEC_FFMPEG_MPEG4 ) /* FFmpeg MPEG-4 */ - { - MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 ); - 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) - { - hb_error("muxmp4.c: MP4AddVideoTrack failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - 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, - job->config.mpeg4.bytes, job->config.mpeg4.length ))) - { - hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - } - else if ( job->vcodec == HB_VCODEC_FFMPEG_MPEG2 ) /* FFmpeg MPEG-2 */ - { - mux_data->track = MP4AddVideoTrack( m->file, 90000, - MP4_INVALID_DURATION, job->width, job->height, - MP4_MPEG2_VIDEO_TYPE ); - if (mux_data->track == MP4_INVALID_TRACK_ID) - { - hb_error("muxmp4.c: MP4AddVideoTrack failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - - /* Tune track chunk duration */ - if( !MP4TuneTrackDurationPerChunk( m, mux_data->track )) - { - return 0; - } - - /* VOL from FFmpeg */ - if (!(MP4SetTrackESConfiguration( m->file, mux_data->track, - job->config.mpeg4.bytes, job->config.mpeg4.length ))) - { - hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - } - else - { - hb_error("muxmp4.c: Unsupported video encoder!"); - } - - /* COLR atom for color and gamma correction. Per the notes at: - * http://developer.apple.com/quicktime/icefloe/dispatch019.html#colr - * http://forum.doom9.org/showthread.php?t=133982#post1090068 - * The user can set it from job->color_matrix_code. */ - if( job->color_matrix_code == 4 ) - { - // Custom - MP4AddColr(m->file, mux_data->track, job->color_prim, job->color_transfer, job->color_matrix); - } - else if( job->color_matrix_code == 3 ) - { - // ITU BT.709 HD content - MP4AddColr(m->file, mux_data->track, HB_COLR_PRI_BT709, HB_COLR_TRA_BT709, HB_COLR_MAT_BT709); - } - else if( job->color_matrix_code == 2 ) - { - // ITU BT.601 DVD or SD TV content (PAL) - MP4AddColr(m->file, mux_data->track, HB_COLR_PRI_EBUTECH, HB_COLR_TRA_BT709, HB_COLR_MAT_SMPTE170M); - } - else if( job->color_matrix_code == 1 ) - { - // ITU BT.601 DVD or SD TV content (NTSC) - MP4AddColr(m->file, mux_data->track, HB_COLR_PRI_SMPTEC, HB_COLR_TRA_BT709, HB_COLR_MAT_SMPTE170M); - } - else - { - // detected during scan - MP4AddColr(m->file, mux_data->track, title->color_prim, title->color_transfer, title->color_matrix); - } - - if( job->anamorphic.mode ) - { - /* PASP atom for anamorphic video */ - float width, height; - - width = job->anamorphic.par_width; - - height = job->anamorphic.par_height; - - MP4AddPixelAspectRatio(m->file, mux_data->track, (uint32_t)width, (uint32_t)height); - - MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.width", job->width * (width / height)); - } - - /* add the audio tracks */ - for( i = 0; i < hb_list_count( job->list_audio ); i++ ) - { - audio = hb_list_item( job->list_audio, i ); - mux_data = calloc(1, sizeof( hb_mux_data_t ) ); - audio->priv.mux_data = mux_data; - - switch ( audio->config.out.codec & HB_ACODEC_MASK ) - { - case HB_ACODEC_AC3: - { - int ii, jj; - int bitrate; - int sr_shift, sr_code; - uint8_t bsid; - uint8_t bsmod; - uint8_t acmod; - uint8_t lfeon; - uint8_t br_code = 0; - - if ( audio->config.out.codec & HB_ACODEC_PASS_FLAG ) - { - bsmod = audio->config.in.mode; - switch (audio->config.in.channel_layout & ~AV_CH_LOW_FREQUENCY) - { - case AV_CH_LAYOUT_MONO: - acmod = 1; - break; - - case AV_CH_LAYOUT_STEREO: - acmod = 2; - break; - - case AV_CH_LAYOUT_SURROUND: - acmod = 3; - break; - - case AV_CH_LAYOUT_2_1: - acmod = 4; - break; - - case AV_CH_LAYOUT_4POINT0: - acmod = 5; - break; - - case AV_CH_LAYOUT_2_2: - acmod = 6; - break; - - case AV_CH_LAYOUT_5POINT0: - acmod = 7; - break; - - default: - hb_error("MP4Init: bad mixdown"); - acmod = 2; - break; - } - lfeon = !!(audio->config.in.channel_layout & AV_CH_LOW_FREQUENCY); - bitrate = audio->config.in.bitrate; - mux_data->sample_rate = audio->config.in.samplerate; - mux_data->samples_per_frame = audio->config.in.samples_per_frame; - } - else - { - bsmod = 0; - bitrate = audio->config.out.bitrate * 1000; - mux_data->sample_rate = audio->config.out.samplerate; - mux_data->samples_per_frame = audio->config.out.samples_per_frame; - switch (audio->config.out.mixdown) - { - case HB_AMIXDOWN_MONO: - acmod = 1; - lfeon = 0; - break; - - case HB_AMIXDOWN_STEREO: - case HB_AMIXDOWN_DOLBY: - case HB_AMIXDOWN_DOLBYPLII: - acmod = 2; - lfeon = 0; - break; - - case HB_AMIXDOWN_5POINT1: - acmod = 7; - lfeon = 1; - break; - - default: - hb_log("MP4Init: bad mixdown"); - acmod = 2; - lfeon = 0; - break; - } - } - - for (ii = 0; ii < 3; ii++) - { - for (jj = 0; jj < 3; jj++) - { - if ((ac3_sample_rate_tab[jj] >> ii) == mux_data->sample_rate) - { - goto rate_found1; - } - } - } - hb_error("Unknown AC3 samplerate"); - ii = jj = 0; - rate_found1: - sr_shift = ii; - sr_code = jj; - bsid = 8 + ii; - for (ii = 0; ii < 19; ii++) - { - if ((ac3_bitrate_tab[ii] >> sr_shift)*1000 == bitrate) - break; - } - if ( ii >= 19 ) - { - hb_error("Unknown AC3 bitrate"); - ii = 0; - } - br_code = ii; - - mux_data->track = MP4AddAC3AudioTrack(m->file, - mux_data->sample_rate, - sr_code, bsid, bsmod, - acmod, lfeon, br_code); - - /* Tune track chunk duration */ - MP4TuneTrackDurationPerChunk( m, mux_data->track ); - - if (audio->config.out.name == NULL) { - MP4SetTrackBytesProperty( - m->file, mux_data->track, - "udta.name.value", - (const uint8_t*)"Surround", strlen("Surround")); - } - else { - MP4SetTrackBytesProperty( - m->file, mux_data->track, - "udta.name.value", - (const uint8_t*)(audio->config.out.name), - strlen(audio->config.out.name)); - } - } break; - - case HB_ACODEC_FFAAC: - case HB_ACODEC_CA_AAC: - case HB_ACODEC_CA_HAAC: - case HB_ACODEC_FDK_AAC: - case HB_ACODEC_FDK_HAAC: - case HB_ACODEC_LAME: - case HB_ACODEC_MP3: - case HB_ACODEC_DCA_HD: - case HB_ACODEC_DCA: - { - uint8_t audio_type = MP4_MPEG4_AUDIO_TYPE; - int channels, config_len = 0; - uint8_t *config_bytes = NULL; - - switch ( audio->config.out.codec & HB_ACODEC_MASK ) - { - case HB_ACODEC_FFAAC: - case HB_ACODEC_CA_AAC: - case HB_ACODEC_CA_HAAC: - case HB_ACODEC_FDK_AAC: - case HB_ACODEC_FDK_HAAC: - { - audio_type = MP4_MPEG4_AUDIO_TYPE; - config_bytes = audio->priv.config.extradata.bytes; - config_len = audio->priv.config.extradata.length; - } break; - case HB_ACODEC_LAME: - case HB_ACODEC_MP3: - { - audio_type = MP4_MPEG2_AUDIO_TYPE; - } break; - case HB_ACODEC_DCA: - case HB_ACODEC_DCA_HD: - { - audio_type = 0xA9; - } break; - } - if (audio->config.out.codec & HB_ACODEC_PASS_FLAG) - { - mux_data->sample_rate = audio->config.in.samplerate; - mux_data->samples_per_frame = audio->config.in.samples_per_frame; - channels = av_get_channel_layout_nb_channels(audio->config.in.channel_layout); - } - else - { - mux_data->sample_rate = audio->config.out.samplerate; - mux_data->samples_per_frame = audio->config.out.samples_per_frame; - channels = hb_mixdown_get_discrete_channel_count(audio->config.out.mixdown); - } - mux_data->track = MP4AddAudioTrack(m->file, - mux_data->sample_rate, - // fixed frame duration, if applicable - mux_data->samples_per_frame > 0 ? - mux_data->samples_per_frame : MP4_INVALID_DURATION, - audio_type); - - /* Tune track chunk duration */ - MP4TuneTrackDurationPerChunk( m, mux_data->track ); - - if (audio->config.out.name == NULL) { - MP4SetTrackBytesProperty( - m->file, mux_data->track, - "udta.name.value", - (const uint8_t*)"Stereo", strlen("Stereo")); - } - else { - MP4SetTrackBytesProperty( - m->file, mux_data->track, - "udta.name.value", - (const uint8_t*)(audio->config.out.name), - strlen(audio->config.out.name)); - } - - MP4SetAudioProfileLevel( m->file, 0x0F ); - if ( config_bytes ) - { - MP4SetTrackESConfiguration( m->file, mux_data->track, - config_bytes, config_len ); - } - /* Set the correct number of channels for this track */ - MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.*.channels", channels); - } break; - - default: - { - hb_log("MP4Mux: Unsupported audio codec %x", audio->config.out.codec); - } break; - } - - /* Set the language for this track */ - MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2); - - if( hb_list_count( job->list_audio ) > 1 ) - { - /* Set the audio track alternate group */ - MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1); - } - - if (i == 0) { - /* Enable the first audio track */ - MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE)); - } - else - /* Disable the other audio tracks so QuickTime doesn't play - them all at once. */ - { - MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE)); - 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)); - } - } - else if( subtitle && subtitle->format == PICTURESUB && - subtitle->config.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 = MP4AddSubpicTrack( m->file, 90000, subtitle->width, subtitle->height ); - - MP4SetTrackLanguage(m->file, mux_data->track, subtitle->iso639_2); - - /* Tune track chunk duration */ - MP4TuneTrackDurationPerChunk( m, mux_data->track ); - uint8_t palette[16][4]; - int ii; - for ( ii = 0; ii < 16; ii++ ) - { - palette[ii][0] = 0; - palette[ii][1] = (subtitle->palette[ii] >> 16) & 0xff; - palette[ii][2] = (subtitle->palette[ii] >> 8) & 0xff; - palette[ii][3] = (subtitle->palette[ii]) & 0xff; - } - if (!(MP4SetTrackESConfiguration( m->file, mux_data->track, - (uint8_t*)palette, 16 * 4 ))) - { - hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!"); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - return 0; - } - 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 - 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, 0); - - m->chapter_track = textTrack; - m->chapter_duration = 0; - m->current_chapter = job->chapter_start; - } - - /* 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_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; -} - -static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, - hb_buffer_t * buf ) -{ - hb_job_t * job = m->job; - int64_t duration, stop = AV_NOPTS_VALUE; - int64_t offset = 0; - hb_buffer_t *tmp; - - if (buf != NULL) - { - if (buf->s.duration >= 0) - { - stop = buf->s.start + buf->s.duration; - } - else if (mux_data->subtitle) - { - buf->s.duration = 10 * 90000; - stop = buf->s.start + buf->s.duration; - } - } - - if( mux_data == job->mux_data ) - { - /* Video */ - if ((job->vcodec & HB_VCODEC_H264_MASK) || - (job->vcodec & HB_VCODEC_FFMPEG_MASK)) - { - if ( buf && buf->s.start < buf->s.renderOffset ) - { - hb_log("MP4Mux: PTS %"PRId64" < DTS %"PRId64, - buf->s.start, buf->s.renderOffset ); - buf->s.renderOffset = buf->s.start; - } - } - - // We delay muxing video by one frame so that we can calculate - // the dts to dts duration of the frames. - tmp = buf; - buf = m->delay_buf; - m->delay_buf = tmp; - - if ( !buf ) - return 0; - - stop = buf->s.start + buf->s.duration; - - if ((job->vcodec & HB_VCODEC_H264_MASK) || - (job->vcodec & HB_VCODEC_FFMPEG_MASK)) - { - // x264 supplies us with DTS, so offset is PTS - DTS - offset = buf->s.start - buf->s.renderOffset; - } - - /* 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->s.new_chap ) - { - 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 >= (90000*3)/2 ) - { - chapter = hb_list_item( m->job->list_chapter, - buf->s.new_chap - 2 ); - - MP4AddChapter( m->file, - m->chapter_track, - duration, - (chapter != NULL) ? chapter->title : NULL); - - m->current_chapter = buf->s.new_chap; - m->chapter_duration += duration; - } - } - - if ((job->vcodec & HB_VCODEC_H264_MASK) || - (job->vcodec & HB_VCODEC_FFMPEG_MASK)) - { - // x264 supplies us with DTS - if ( m->delay_buf ) - { - duration = m->delay_buf->s.renderOffset - buf->s.renderOffset; - } - else - { - duration = stop - m->sum_dur; - // Due to how libx264 generates DTS, it's possible for the - // above calculation to be negative. - // - // x264 generates DTS by rearranging PTS in this sequence: - // pts0 - delay, pts1 - delay, pts2 - delay, pts1, pts2, pts3... - // - // where delay == pts2. This guarantees that DTS <= PTS for - // any frame, but also generates this sequence of durations: - // d0 + d1 + d0 + d1 + d2 + d3 ... + d(N-2) - // - // so the sum up to the last frame is: - // sum_dur = d0 + d1 + d0 + d1 + d2 + d3 ... + d(N-3) - // - // while the original total duration of the video was: - // duration = d0 + d1 + d2 + d3 ... + d(N) - // - // Note that if d0 + d1 != d(N-1) + d(N), the total - // length of the video changes since d(N-1) and d(N) are - // replaced by d0 and d1 in the final duration sum. - // - // To keep the total length of the video the same as the source - // we try to make - // d(N-2) = duration - sum_dur - // - // But if d0 + d1 >= d(N-1) + d(N), the above calculation - // results in a nagative value and we need to fix it. - if ( duration <= 0 ) - duration = 90000. / ((double)job->vrate / (double)job->vrate_base); - } - } - else - { - // 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->s.duration; - } - - 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 %"PRId64", start %"PRId64"," - "stop %"PRId64", sum_dur %"PRId64, - duration, buf->s.start, stop, m->sum_dur ); - /* we don't know when the next frame starts so we can't pick a - 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; - } - else - { - /* Audio */ - if (mux_data->samples_per_frame > 0) - // frame size is fixed and known - duration = MP4_INVALID_DURATION; - else - // frame size has to be computed - duration = buf->s.duration * mux_data->sample_rate / 90000; - } - - /* Here's where the sample actually gets muxed. */ - if (mux_data == job->mux_data && ((job->vcodec & HB_VCODEC_H264_MASK) || - (job->vcodec & HB_VCODEC_FFMPEG_MASK))) - { - /* 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->s.flags & HB_FRAME_REF ) - dflags |= MP4_SDT_HAS_DEPENDENTS; - else - dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */ - - switch( buf->s.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->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - } - else if (mux_data->subtitle) - { - if( mux_data->sub_format == TEXTSUB ) - { - /* MPEG4 timed text does not allow overlapping samples; upstream - code should coalesce overlapping subtitle lines. */ - if( buf->s.start < mux_data->sum_dur ) - { - if ( stop - mux_data->sum_dur > 90*500 ) - { - hb_log("MP4Mux: shortening overlapping subtitle, " - "start %"PRId64", stop %"PRId64", sum_dur %"PRId64, - buf->s.start, stop, m->sum_dur); - buf->s.start = mux_data->sum_dur; - } - } - if( buf->s.start < mux_data->sum_dur ) - { - hb_log("MP4Mux: skipping overlapping subtitle, " - "start %"PRId64", stop %"PRId64", sum_dur %"PRId64, - buf->s.start, stop, m->sum_dur); - } - else - { - /* Write an empty sample */ - if ( mux_data->sum_dur < buf->s.start ) - { - uint8_t empty[2] = {0,0}; - if( !MP4WriteSample( m->file, - mux_data->track, - empty, - 2, - buf->s.start - mux_data->sum_dur, - 0, - 1 )) - { - hb_error("Failed to write to output file, disk full?"); - *job->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - mux_data->sum_dur += buf->s.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":%f: %s", - (float)buf->s.start / 90000, buf->s.start, stop, - buf->s.duration, 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->s.duration, - 0, - 1 )) - { - hb_error("Failed to write to output file, disk full?"); - *job->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - - mux_data->sum_dur += buf->s.duration; - } - } - else if( mux_data->sub_format == PICTURESUB ) - { - /* Write an empty sample */ - if ( mux_data->sum_dur < buf->s.start ) - { - uint8_t empty[2] = {0,0}; - if( !MP4WriteSample( m->file, - mux_data->track, - empty, - 2, - buf->s.start - mux_data->sum_dur, - 0, - 1 )) - { - hb_error("Failed to write to output file, disk full?"); - *job->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - mux_data->sum_dur += buf->s.start - mux_data->sum_dur; - } - if( !MP4WriteSample( m->file, - mux_data->track, - buf->data, - buf->size, - buf->s.duration, - 0, - 1 )) - { - hb_error("Failed to write to output file, disk full?"); - *job->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - - mux_data->sum_dur += buf->s.duration; - } - } - else - { - /* - * Audio - */ - if( !MP4WriteSample( m->file, - mux_data->track, - buf->data, - buf->size, - duration, - offset, - ( buf->s.frametype & HB_FRAME_KEY ) != 0 )) - { - hb_error("Failed to write to output file, disk full?"); - *job->done_error = HB_ERROR_UNKNOWN; - *job->die = 1; - } - } - hb_buffer_close( &buf ); - - return 0; -} - -static int MP4End( hb_mux_object_t * m ) -{ - hb_job_t * job = m->job; - int i; - - if (m->file != MP4_INVALID_FILE_HANDLE) - { - // Flush the delayed frame - if ( m->delay_buf ) - MP4Mux( m, job->mux_data, NULL ); - - /* 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 1.5 seconds just skip it. */ - if ( duration >= (90000*3)/2 ) - { - - chapter = hb_list_item( m->job->list_chapter, - m->current_chapter - 1 ); - - MP4AddChapter( m->file, - m->chapter_track, - duration, - (chapter != NULL) ? chapter->title : NULL); - } - } - - if ( job->config.h264.init_delay ) - { - // Insert track edit to get A/V back in sync. The edit amount is - // the init_delay. - int64_t edit_amt = job->config.h264.init_delay; - MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt, - MP4GetTrackDuration(m->file, 1), 0); - if ( m->job->chapter_markers ) - { - // apply same edit to chapter track to keep it in sync with video - MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID, - edit_amt, - MP4GetTrackDuration(m->file, m->chapter_track), 0); - } - } - - // Check for audio preroll and add edit entries for audio - for( i = 0; i < hb_list_count( job->list_audio ); i++ ) - { - hb_audio_t *audio = hb_list_item( job->list_audio, i ); - hb_mux_data_t *mux_data = audio->priv.mux_data; - if (audio->config.out.delay > 0) - { - int64_t edit_amt = audio->config.out.delay; - MP4AddTrackEdit(m->file, mux_data->track, MP4_INVALID_EDIT_ID, - edit_amt, MP4GetTrackDuration(m->file, 1), 0); - } - } - - /* - * Write the MP4 iTunes metadata if we have any metadata - */ - if( job->metadata ) - { - hb_metadata_t *md = job->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 */ - if( md->name ) - MP4TagsSetName( tags, md->name ); - if( md->artist ) - MP4TagsSetArtist( tags, md->artist ); - if( md->composer ) - MP4TagsSetComposer( tags, md->composer ); - if( md->comment ) - MP4TagsSetComments( tags, md->comment ); - if( md->release_date ) - MP4TagsSetReleaseDate( tags, md->release_date ); - if( md->album ) - MP4TagsSetAlbum( tags, md->album ); - if( md->album_artist ) - MP4TagsSetAlbumArtist( tags, md->album_artist ); - if( md->genre ) - MP4TagsSetGenre( tags, md->genre ); - if( md->description ) - MP4TagsSetDescription( tags, md->description ); - if( md->long_description ) - MP4TagsSetLongDescription( tags, md->long_description ); - - if( md->list_coverart ) - { - hb_coverart_t * coverart; - int ii; - - for ( ii = 0; ii < hb_list_count( md->list_coverart ); ii++ ) - { - coverart = hb_list_item( md->list_coverart, ii ); - MP4TagArtwork art; - int type; - switch ( coverart->type ) - { - case HB_ART_BMP: - type = MP4_ART_BMP; - break; - case HB_ART_GIF: - type = MP4_ART_GIF; - break; - case HB_ART_JPEG: - type = MP4_ART_JPEG; - break; - case HB_ART_PNG: - type = MP4_ART_PNG; - break; - default: - type = MP4_ART_UNDEFINED; - break; - } - art.data = coverart->data; - art.size = coverart->size; - art.type = type; - 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 ) - { - hb_log( "muxmp4: optimizing file" ); - char filename[1024]; memset( filename, 0, 1024 ); - snprintf(filename, 1024, "%s.tmp", m->path); - MP4Optimize(m->path, filename, MP4_DETAILS_ERROR); - remove(m->path); - rename(filename, m->path); - } - - } - free(m->path); - - return 0; -} - -hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job ) -{ - hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 ); - m->init = MP4Init; - m->mux = MP4Mux; - m->end = MP4End; - m->job = job; - return m; -} - -#endif // USE_MP4V2 |