void AddIPodUUID(MP4FileHandle, MP4TrackId);
-/* B-frame muxing variables */
-MP4SampleId thisSample = 0;
-uint64_t initDelay;
-
struct hb_mux_object_s
{
HB_MUX_COMMON;
/* Create an empty mp4 file */
- m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
+ if (job->largeFileSize)
+ /* Use 64-bit MP4 file */
+ {
+ m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA );
+ hb_log("Using 64-bit MP4 formatting.");
+ }
+ else
+ /* Limit MP4s to less than 4 GB */
+ {
+ m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
+ }
+
if (m->file == MP4_INVALID_FILE_HANDLE)
{
- hb_log("muxmp4.c: MP4Create failed!");
+ hb_error("muxmp4.c: MP4Create failed!");
*job->die = 1;
return 0;
}
timescale */
if (!(MP4SetTimeScale( m->file, job->arate )))
{
- hb_log("muxmp4.c: MP4SetTimeScale failed!");
+ hb_error("muxmp4.c: MP4SetTimeScale failed!");
*job->die = 1;
return 0;
}
/* Stolen from mp4creator */
if(!(MP4SetVideoProfileLevel( m->file, 0x7F )))
{
- hb_log("muxmp4.c: MP4SetVideoProfileLevel failed!");
+ hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
*job->die = 1;
return 0;
}
MP4AddH264PictureParameterSet( m->file, mux_data->track,
job->config.h264.pps, job->config.h264.pps_length );
- if( job->h264_level == 30)
+ if( job->h264_level == 30 || job->ipod_atom)
{
hb_log("About to add iPod atom");
AddIPodUUID(m->file, mux_data->track);
{
if(!(MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 )))
{
- hb_log("muxmp4.c: MP4SetVideoProfileLevel failed!");
+ hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
*job->die = 1;
return 0;
}
MP4_MPEG4_VIDEO_TYPE );
if (mux_data->track == MP4_INVALID_TRACK_ID)
{
- hb_log("muxmp4.c: MP4AddVideoTrack failed!");
+ hb_error("muxmp4.c: MP4AddVideoTrack failed!");
*job->die = 1;
return 0;
}
if (!(MP4SetTrackESConfiguration( m->file, mux_data->track,
job->config.mpeg4.bytes, job->config.mpeg4.length )))
{
- hb_log("muxmp4.c: MP4SetTrackESConfiguration failed!");
+ hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!");
*job->die = 1;
return 0;
}
memcpy(nval, val, size);
float width, height;
- float widthRatio;
width = job->pixel_aspect_width;
height = job->pixel_aspect_height;
- widthRatio = (width / height) * 0x10000;
- uint32_t widthRatioInt;
- widthRatioInt = (uint32_t)widthRatio;
+ uint32_t pixelRatioInt;
+ if (width >= height)
+ {
+ pixelRatioInt = (uint32_t)((width / height) * 0x10000);
+
+#ifdef WORDS_BIGENDIAN
+ ptr32[0] = pixelRatioInt;
+#else
+ /* we need to switch the endianness, as the file format expects big endian */
+ ptr32[0] = ((pixelRatioInt & 0x000000FF) << 24) + ((pixelRatioInt & 0x0000FF00) << 8) + ((pixelRatioInt & 0x00FF0000) >> 8) + ((pixelRatioInt & 0xFF000000) >> 24);
+#endif
+ }
+ else
+ {
+ pixelRatioInt = (uint32_t)((height / width) * 0x10000);
#ifdef WORDS_BIGENDIAN
- ptr32[0] = widthRatioInt;
+ ptr32[4] = pixelRatioInt;
#else
- /* we need to switch the endianness, as the file format expects big endian */
- ptr32[0] = ((widthRatioInt & 0x000000FF) << 24) + ((widthRatioInt & 0x0000FF00) << 8) + ((widthRatioInt & 0x00FF0000) >> 8) + ((widthRatioInt & 0xFF000000) >> 24);
+ /* we need to switch the endianness, as the file format expects big endian */
+ ptr32[4] = ((pixelRatioInt & 0x000000FF) << 24) + ((pixelRatioInt & 0x0000FF00) << 8) + ((pixelRatioInt & 0x00FF0000) >> 8) + ((pixelRatioInt & 0xFF000000) >> 24);
#endif
+ }
+
if(!MP4SetBytesProperty(m->file, "moov.trak.tkhd.reserved3", nval, size)) {
hb_log("Problem setting transform matrix");
mux_data = malloc( sizeof( hb_mux_data_t ) );
audio->mux_data = mux_data;
- mux_data->track = MP4AddAudioTrack( m->file,
+ if( job->acodec & HB_ACODEC_AC3 ||
+ job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 )
+ {
+ mux_data->track = MP4AddAC3AudioTrack(
+ m->file,
+ job->arate, 1536, MP4_MPEG4_AUDIO_TYPE );
+ MP4SetTrackBytesProperty(
+ m->file, mux_data->track,
+ "udta.name.value",
+ (const u_int8_t*)"Surround", strlen("Surround"));
+ } else {
+ mux_data->track = MP4AddAudioTrack(
+ m->file,
job->arate, 1024, MP4_MPEG4_AUDIO_TYPE );
- MP4SetAudioProfileLevel( m->file, 0x0F );
- MP4SetTrackESConfiguration( m->file, mux_data->track,
+ MP4SetTrackBytesProperty(
+ m->file, mux_data->track,
+ "udta.name.value",
+ (const u_int8_t*)"Stereo", strlen("Stereo"));
+
+ MP4SetAudioProfileLevel( m->file, 0x0F );
+ MP4SetTrackESConfiguration(
+ m->file, mux_data->track,
audio->config.aac.bytes, audio->config.aac.length );
-
+
+ /* Set the correct number of channels for this track */
+ reserved2[9] = (u_int8_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown);
+ MP4SetTrackBytesProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.reserved2", reserved2, sizeof(reserved2));
+
+ }
/* Set the language for this track */
/* The language is stored as 5-bit text - 0x60 */
language_code = audio->iso639_2[0] - 0x60; language_code <<= 5;
language_code |= audio->iso639_2[2] - 0x60;
MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.mdhd.language", language_code);
- /* Set the correct number of channels for this track */
- reserved2[9] = (u_int8_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown);
- MP4SetTrackBytesProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.reserved2", reserved2, sizeof(reserved2));
+
+ /* Set the audio track alternate group */
+ MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1);
+
+ /* If we ever upgrade mpeg4ip, the line above should be replaced with the line below.*/
+// MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (u_int16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown));
/* store a reference to the first audio track,
so we can use it to feed the chapter text track's sample rate */
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_VERSION, HB_BUILD);
+ MP4SetMetadataTool(m->file, tool_string);
+ free(tool_string);
+
return 0;
}
(This is because of how durations for text tracks work in QT) */
if( job->chapter_markers && buf->new_chap )
{
- struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) );
-
- MP4WriteSample(m->file, m->chapter_track, sample->sample, sample->length, sample->duration, 0, true);
+ struct hb_text_sample_s *sample;
+
+ /* If this is an x264 encode with bframes the IDR frame we're
+ trying to mark will be displayed offset by its renderOffset
+ so we need to offset the chapter by the same amount.
+ MP4 render offsets don't seem to work for text tracks so
+ we have to fudge the duration instead. */
+ duration = m->sum_dur - m->chapter_duration;
+
+ if ( job->areBframes )
+ {
+ duration += buf->renderOffset * job->arate / 90000;
+ }
+
+ sample = MP4GenerateChapterSample( m, duration );
+
+ if( !MP4WriteSample(m->file,
+ m->chapter_track,
+ sample->sample,
+ sample->length,
+ sample->duration,
+ 0, true) )
+ {
+ hb_error("Failed to write to output file, disk full?");
+ *job->die = 1;
+ }
free(sample);
m->current_chapter++;
- m->chapter_duration = m->sum_dur;
+ m->chapter_duration += duration;
}
/* Video */
/* Because we use the audio samplerate as the timescale,
we have to use potentially variable durations so the video
doesn't go out of sync */
- duration = ( buf->stop * job->arate / 90000 ) - m->sum_dur;
+ int64_t bias = ( buf->start * job->arate / 90000 ) - m->sum_dur;
+ duration = ( buf->stop - buf->start ) * job->arate / 90000 + bias;
m->sum_dur += duration;
}
else
duration = MP4_INVALID_DURATION;
}
- /* When we do get the first keyframe, use its duration as the
- initial delay added to the frame order offset for b-frames.
- Because of b-pyramid, double this duration when there are
- b-pyramids, as denoted by job->areBframes equalling 2. */
- if ((mux_data->track == 1) && (thisSample == 0) && (buf->key == 1) && (job->vcodec == HB_VCODEC_X264))
- {
- initDelay = buf->renderOffset;
- thisSample++;
- }
-
/* Here's where the sample actually gets muxed.
If it's an audio sample, don't offset the sample's playback.
If it's a video sample and there are no b-frames, ditto.
If there are b-frames, offset by the initDelay plus the
difference between the presentation time stamp x264 gives
and the decoding time stamp from the buffer data. */
- MP4WriteSample( m->file, mux_data->track, buf->data, buf->size,
- duration, ((mux_data->track != 1) || (job->areBframes==0) || (job->vcodec != HB_VCODEC_X264)) ? 0 : ( buf->renderOffset * job->arate / 90000),
- (buf->key == 1) );
+ if( !MP4WriteSample( m->file,
+ mux_data->track,
+ buf->data,
+ buf->size,
+ duration,
+ ((mux_data->track != 1) ||
+ (job->areBframes==0) ||
+ (job->vcodec != HB_VCODEC_X264)) ? 0 : ( buf->renderOffset * job->arate / 90000),
+ ((buf->frametype & HB_FRAME_KEY) != 0) ) )
+ {
+ hb_error("Failed to write to output file, disk full?");
+ *job->die = 1;
+ }
return 0;
}
static int MP4End( hb_mux_object_t * m )
-{
+{
+ hb_job_t * job = m->job;
+
/* Write our final chapter marker */
if( m->job->chapter_markers )
{
struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) );
- MP4WriteSample(m->file, m->chapter_track, sample->sample, sample->length, sample->duration, 0, true);
+ if( !MP4WriteSample(m->file,
+ m->chapter_track,
+ sample->sample,
+ sample->length,
+ sample->duration,
+ 0, true) )
+ {
+ hb_error("Failed to write to output file, disk full?");
+ *job->die = 1;
+ }
free(sample);
}
-#if 0
- hb_job_t * job = m->job;
- char filename[1024]; memset( filename, 0, 1024 );
-#endif
-
- hb_job_t * job = m->job;
-
if (job->areBframes)
- /* Walk the entire video sample table and find the minumum ctts value. */
{
- MP4SampleId count = MP4GetTrackNumberOfSamples( m->file, 1);
- MP4SampleId i;
- MP4Duration renderingOffset = 2000000000, tmp;
-
- // Find the smallest rendering offset
- for(i = 1; i <= count; i++)
- {
- tmp = MP4GetSampleRenderingOffset(m->file, 1, i);
- if(tmp < renderingOffset)
- renderingOffset = tmp;
- }
-
- // Adjust all ctts values down by renderingOffset
- for(i = 1; i <= count; i++)
- {
- MP4SetSampleRenderingOffset(m->file,1,i,
- MP4GetSampleRenderingOffset(m->file,1,i) - renderingOffset);
- }
-
// Insert track edit to get A/V back in sync. The edit amount is
// the rendering offset of the first sample.
MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, MP4GetSampleRenderingOffset(m->file,1,1),
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,
+ MP4GetSampleRenderingOffset(m->file,1,1),
+ MP4GetTrackDuration(m->file, m->chapter_track), 0);
+ }
}
MP4Close( m->file );
-#if 0
- hb_log( "muxmp4: optimizing file" );
- snprintf( filename, 1024, "%s.tmp", job->file );
- MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
- remove( job->file );
- rename( filename, job->file );
-#endif
+ if ( job->mp4_optimize )
+ {
+ hb_log( "muxmp4: optimizing file" );
+ char filename[1024]; memset( filename, 0, 1024 );
+ snprintf( filename, 1024, "%s.tmp", job->file );
+ MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
+ remove( job->file );
+ rename( filename, job->file );
+ }
return 0;
}