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;
+ hb_buffer_t *delay_buf;
/* Chapter state information for muxing */
MP4TrackId chapter_track;
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
**********************************************************************
MP4AddH264PictureParameterSet( m->file, mux_data->track,
job->config.h264.pps, job->config.h264.pps_length );
- if( job->h264_level == 30 || job->ipod_atom)
+ if( job->ipod_atom )
{
hb_deep_log( 2, "muxmp4: adding iPod atom");
MP4AddIPodUUID(m->file, mux_data->track);
}
-
- m->init_delay = job->config.h264.init_delay;
}
else /* FFmpeg or XviD */
{
mux_data = calloc(1, sizeof( hb_mux_data_t ) );
audio->priv.mux_data = mux_data;
- if( audio->config.out.codec == HB_ACODEC_AC3 )
+ if( audio->config.out.codec == HB_ACODEC_AC3_PASS )
{
- uint8_t fscod = 0;
uint8_t bsid = audio->config.in.version;
uint8_t bsmod = audio->config.in.mode;
uint8_t acmod = audio->config.flags.ac3 & 0x7;
uint8_t lfeon = (audio->config.flags.ac3 & A52_LFE) ? 1 : 0;
uint8_t bit_rate_code = 0;
+ int ii, jj;
+ int freq = audio->config.in.samplerate;
+ int bitrate = audio->config.in.bitrate;
+ int sr_shift, sr_code;
- /*
- * Rewrite AC3 information into correct format for dac3 atom
- */
- switch( audio->config.in.samplerate )
+ for (ii = 0; ii < 3; ii++)
{
- case 48000:
- fscod = 0;
- break;
- case 44100:
- fscod = 1;
- break;
- case 32000:
- fscod = 2;
- break;
- default:
- /*
- * Error value, tells decoder to not decode this audio.
- */
- fscod = 3;
- break;
+ for (jj = 0; jj < 3; jj++)
+ {
+ if ((ac3_sample_rate_tab[jj] >> ii) == freq)
+ {
+ goto rate_found1;
+ }
+ }
+ }
+ hb_error("Unknown AC3 samplerate");
+ ii = jj = 0;
+rate_found1:
+ sr_shift = ii;
+ sr_code = jj;
+ 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;
+ }
+ bit_rate_code = ii;
+
+ mux_data->track = MP4AddAC3AudioTrack(
+ m->file,
+ audio->config.in.samplerate,
+ sr_code,
+ bsid,
+ bsmod,
+ acmod,
+ 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,
+ "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));
}
+ }
+ else if( audio->config.out.codec == HB_ACODEC_AC3 )
+ {
+ uint8_t bsid = 8;
+ uint8_t bsmod = 0;
+ uint8_t acmod = 2;
+ uint8_t lfeon = 0;
+ uint8_t bit_rate_code = 0;
+ int ii, jj;
+ int freq = audio->config.out.samplerate;
+ int bitrate = audio->config.out.bitrate;
+ int sr_shift, sr_code;
- switch( audio->config.in.bitrate )
+ for (ii = 0; ii < 3; ii++)
+ {
+ for (jj = 0; jj < 3; jj++)
+ {
+ if ((ac3_sample_rate_tab[jj] >> ii) == freq)
+ {
+ goto rate_found2;
+ }
+ }
+ }
+ hb_error("Unknown AC3 samplerate");
+ ii = jj = 0;
+rate_found2:
+ sr_shift = ii;
+ sr_code = jj;
+ bsid = 8 + ii;
+ for (ii = 0; ii < 19; ii++)
+ {
+ if ((ac3_bitrate_tab[ii] >> sr_shift) == bitrate)
+ break;
+ }
+ if ( ii >= 19 )
{
- case 32000:
- bit_rate_code = 0;
- break;
- case 40000:
- bit_rate_code = 1;
- break;
- case 48000:
- bit_rate_code = 2;
- break;
- case 56000:
- bit_rate_code = 3;
- break;
- case 64000:
- bit_rate_code = 4;
- break;
- case 80000:
- bit_rate_code = 5;
- break;
- case 96000:
- bit_rate_code = 6;
- break;
- case 112000:
- bit_rate_code = 7;
- break;
- case 128000:
- bit_rate_code = 8;
- break;
- case 160000:
- bit_rate_code = 9;
- break;
- case 192000:
- bit_rate_code = 10;
- break;
- case 224000:
- bit_rate_code = 11;
- break;
- case 256000:
- bit_rate_code = 12;
- break;
- case 320000:
- bit_rate_code = 13;
- break;
- case 384000:
- bit_rate_code = 14;
- break;
- case 448000:
- bit_rate_code = 15;
- break;
- case 512000:
- bit_rate_code = 16;
- break;
- case 576000:
- bit_rate_code = 17;
- break;
- case 640000:
- bit_rate_code = 18;
- break;
- default:
hb_error("Unknown AC3 bitrate");
- bit_rate_code = 0;
- break;
+ ii = 0;
+ }
+ bit_rate_code = ii;
+
+ switch( audio->config.out.mixdown )
+ {
+ case HB_AMIXDOWN_MONO:
+ acmod = 1;
+ break;
+
+ case HB_AMIXDOWN_STEREO:
+ case HB_AMIXDOWN_DOLBY:
+ case HB_AMIXDOWN_DOLBYPLII:
+ acmod = 2;
+ break;
+
+ case HB_AMIXDOWN_6CH:
+ acmod = 7;
+ lfeon = 1;
+ break;
+
+ default:
+ hb_log(" MP4Init: bad mixdown" );
+ break;
}
mux_data->track = MP4AddAC3AudioTrack(
m->file,
audio->config.out.samplerate,
- fscod,
+ sr_code,
bsid,
bsmod,
acmod,
(const uint8_t*)(audio->config.out.name),
strlen(audio->config.out.name));
}
- } else {
+ }
+ else if( audio->config.out.codec == HB_ACODEC_FAAC ||
+ audio->config.out.codec == HB_ACODEC_CA_AAC )
+ {
mux_data->track = MP4AddAudioTrack(
m->file,
audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
/* Set the correct number of channels for this track */
MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (uint16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown));
+ } else if( audio->config.out.codec == HB_ACODEC_LAME ) {
+ mux_data->track = MP4AddAudioTrack(
+ m->file,
+ audio->config.out.samplerate, 1152, MP4_MPEG2_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 );
+
+ /* Set the correct number of channels for this track */
+ MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (uint16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown));
}
/* Set the language for this track */
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->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)
hb_job_t * job = m->job;
int64_t duration;
int64_t offset = 0;
+ hb_buffer_t *tmp;
if( mux_data == job->mux_data )
{
/* Video */
- // if there are b-frames compute the render offset
- // (we'll need it for both the video frame & the chapter track)
- if ( m->init_delay )
+ if( job->vcodec == HB_VCODEC_X264 )
{
- offset = buf->start + m->init_delay - m->sum_dur;
- if ( offset < 0 )
+ if ( buf && buf->start < buf->renderOffset )
{
- hb_log("MP4Mux: illegal render offset %"PRId64", start %"PRId64","
- "stop %"PRId64", sum_dur %"PRId64,
- offset, buf->start, buf->stop, m->sum_dur );
- offset = 0;
+ hb_log("MP4Mux: PTS %"PRId64" < DTS %"PRId64,
+ buf->start, buf->renderOffset );
+ buf->renderOffset = buf->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;
+
+ if( job->vcodec == HB_VCODEC_X264 )
+ {
+ // x264 supplies us with DTS, so offset is PTS - DTS
+ offset = buf->start - buf->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.
}
}
- // 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( job->vcodec == HB_VCODEC_X264 )
+ {
+ // x264 supplies us with DTS
+ if ( m->delay_buf )
+ {
+ duration = m->delay_buf->renderOffset - buf->renderOffset;
+ }
+ else
+ {
+ duration = buf->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->stop - buf->start;
+ }
+
if ( duration <= 0 )
{
/* We got an illegal mp4/h264 duration. This shouldn't
mux_data->sum_dur += (buf->stop - buf->start);
}
+ else if( mux_data->sub_format == PICTURESUB )
+ {
+ /* 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;
+ }
+ if( !MP4WriteSample( m->file,
+ mux_data->track,
+ buf->data,
+ buf->size,
+ 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
{
*job->die = 1;
}
}
-
+ hb_buffer_close( &buf );
return 0;
}
hb_job_t * job = m->job;
hb_title_t * title = job->title;
+ // Flush the delayed frame
+ if ( m->delay_buf )
+ MP4Mux( m, job->mux_data, NULL );
+
/* Write our final chapter marker */
if( m->job->chapter_markers )
{
}
}
- if (job->areBframes)
+ 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 = m->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 )