Homepage: <http://handbrake.fr/>.
It may be used under the terms of the GNU General Public License. */
-/* libmp4v2 header */
-#include "mp4.h"
+#include "mp4v2/mp4v2.h"
+#include "a52dec/a52.h"
#include "hb.h"
-void AddIPodUUID(MP4FileHandle, MP4TrackId);
-
struct hb_mux_object_s
{
HB_MUX_COMMON;
MP4TrackId track;
};
-struct hb_text_sample_s
-{
- uint8_t sample[1280];
- uint32_t length;
- MP4Duration duration;
-};
-
-/**********************************************************************
- * MP4CreateTextSample
- **********************************************************************
- * Creates a buffer for a text track sample
- *********************************************************************/
-static struct hb_text_sample_s *MP4CreateTextSample( char *textString, uint64_t duration )
-{
- struct hb_text_sample_s *sample = NULL;
- int stringLength = strlen(textString);
- int x;
-
- if( stringLength < 1024 )
- {
- sample = malloc( sizeof( struct hb_text_sample_s ) );
-
- //textLength = (stringLength; // Account for BOM
- sample->length = stringLength + 2 + 12; // Account for text length code and other marker
- sample->duration = (MP4Duration)duration;
-
- // 2-byte length marker
- sample->sample[0] = (stringLength >> 8) & 0xff;
- sample->sample[1] = stringLength & 0xff;
-
- strncpy( (char *)&(sample->sample[2]), textString, stringLength );
-
- x = 2 + stringLength;
-
- // Modifier Length Marker
- sample->sample[x] = 0x00;
- sample->sample[x+1] = 0x00;
- sample->sample[x+2] = 0x00;
- sample->sample[x+3] = 0x0C;
-
- // Modifier Type Code
- sample->sample[x+4] = 'e';
- sample->sample[x+5] = 'n';
- sample->sample[x+6] = 'c';
- sample->sample[x+7] = 'd';
-
- // Modifier Value
- sample->sample[x+8] = 0x00;
- sample->sample[x+9] = 0x00;
- sample->sample[x+10] = (256 >> 8) & 0xff;
- sample->sample[x+11] = 256 & 0xff;
- }
-
- return sample;
-}
-
-/**********************************************************************
- * MP4GenerateChapterSample
- **********************************************************************
- * Creates a buffer for a text track sample
- *********************************************************************/
-static struct hb_text_sample_s *MP4GenerateChapterSample( hb_mux_object_t * m,
- uint64_t duration,
- int chapter )
-{
- // We substract 1 from the chapter number because the chapters start at
- // 1 but our name array starts at 0. We substract another 1 because we're
- // writing the text of the previous chapter mark (when we get the start
- // of chapter 2 we know the duration of chapter 1 & can write its mark).
- hb_chapter_t *chapter_data = hb_list_item( m->job->title->list_chapter,
- chapter - 2 );
- char tmp_buffer[1024];
- char *string = tmp_buffer;
-
- tmp_buffer[0] = '\0';
-
- if( chapter_data != NULL )
- {
- string = chapter_data->title;
- }
-
- if( strlen(string) == 0 || strlen(string) >= 1024 )
- {
- snprintf( tmp_buffer, 1023, "Chapter %03i", chapter - 2 );
- string = tmp_buffer;
- }
-
- return MP4CreateTextSample( string, duration );
-}
-
/**********************************************************************
* MP4Init
hb_audio_t * audio;
hb_mux_data_t * mux_data;
int i;
- u_int16_t language_code;
/* 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;
/* Use 64-bit MP4 file */
{
m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA );
- hb_log("Using 64-bit MP4 formatting.");
+ hb_deep_log( 2, "muxmp4: using 64-bit MP4 formatting.");
}
else
/* Limit MP4s to less than 4 GB */
if( job->vcodec == HB_VCODEC_X264 )
{
/* Stolen from mp4creator */
- if(!(MP4SetVideoProfileLevel( m->file, 0x7F )))
- {
- hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
- *job->die = 1;
- return 0;
- }
-
+ MP4SetVideoProfileLevel( m->file, 0x7F );
mux_data->track = MP4AddH264VideoTrack( m->file, m->samplerate,
MP4_INVALID_DURATION, job->width, job->height,
job->config.h264.sps[1], /* AVCProfileIndication */
if( job->h264_level == 30 || job->ipod_atom)
{
- hb_log("About to add iPod atom");
- AddIPodUUID(m->file, mux_data->track);
+ 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 */
{
- if(!(MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 )))
- {
- hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
- *job->die = 1;
- return 0;
- }
+ MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
mux_data->track = MP4AddVideoTrack( m->file, m->samplerate,
MP4_INVALID_DURATION, job->width, job->height,
MP4_MPEG4_VIDEO_TYPE );
// 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, otherwise by default
// we say anything that's likely to be HD content is ITU BT.709 and
// DVD, SD TV & other content is ITU BT.601. We look at the title height
// rather than the job height here to get uncropped input dimensions.
- if ( job->title->height >= 720 )
+ if( job->color_matrix == 1 )
+ {
+ // ITU BT.601 DVD or SD TV content
+ MP4AddColr(m->file, mux_data->track, 6, 1, 6);
+ }
+ else if( job->color_matrix == 2 )
+ {
+ // ITU BT.709 HD content
+ MP4AddColr(m->file, mux_data->track, 1, 1, 1);
+ }
+ else if ( job->title->width >= 1280 || job->title->height >= 720 )
{
// we guess that 720p or above is ITU BT.709 HD content
MP4AddColr(m->file, mux_data->track, 1, 1, 1);
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);
/* add the audio tracks */
for( i = 0; i < hb_list_count( title->list_audio ); i++ )
{
- static u_int8_t reserved2[16] = {
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x00, 0x10,
- 0x00, 0x00, 0x00, 0x00,
- };
-
audio = hb_list_item( title->list_audio, i );
mux_data = malloc( sizeof( hb_mux_data_t ) );
audio->priv.mux_data = mux_data;
if( audio->config.out.codec == HB_ACODEC_AC3 )
{
+ 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;
+
+ /*
+ * Rewrite AC3 information into correct format for dac3 atom
+ */
+ switch( audio->config.in.samplerate )
+ {
+ 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;
+ }
+
+ switch( audio->config.in.bitrate )
+ {
+ 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;
+ }
+
mux_data->track = MP4AddAC3AudioTrack(
m->file,
- m->samplerate, 1536, MP4_MPEG4_AUDIO_TYPE );
- MP4SetTrackBytesProperty(
- m->file, mux_data->track,
- "udta.name.value",
- (const u_int8_t*)"Surround", strlen("Surround"));
+ audio->config.out.samplerate,
+ fscod,
+ bsid,
+ bsmod,
+ acmod,
+ lfeon,
+ bit_rate_code);
+
+ 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 {
mux_data->track = MP4AddAudioTrack(
m->file,
- m->samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
- MP4SetTrackBytesProperty(
- m->file, mux_data->track,
- "udta.name.value",
- (const u_int8_t*)"Stereo", strlen("Stereo"));
+ audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
+ 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 );
MP4SetTrackESConfiguration(
audio->priv.config.aac.bytes, audio->priv.config.aac.length );
/* Set the correct number of channels for this track */
- reserved2[9] = (u_int8_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown);
- MP4SetTrackBytesProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.reserved2", reserved2, sizeof(reserved2));
-
+ 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 */
- /* The language is stored as 5-bit text - 0x60 */
- language_code = audio->config.lang.iso639_2[0] - 0x60; language_code <<= 5;
- language_code |= audio->config.lang.iso639_2[1] - 0x60; language_code <<= 5;
- language_code |= audio->config.lang.iso639_2[2] - 0x60;
- MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.mdhd.language", language_code);
-
- /* Set the audio track alternate group */
- MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1);
+ /* Set the language for this track */
+ MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2);
- /* 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));
+ if( hb_list_count( title->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 */
them all at once. */
{
MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
- hb_log("Disabled extra audio track %i", mux_data->track-1);
+ hb_deep_log( 2, "muxp4: disabled extra audio track %i", mux_data->track-1);
}
}
- if (job->chapter_markers)
+ 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);
+ textTrack = MP4AddChapterTextTrack(m->file, 1, 0);
m->chapter_track = textTrack;
m->chapter_duration = 0;
/* 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);
+ snprintf( tool_string, 80, "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD);
MP4SetMetadataTool(m->file, tool_string);
free(tool_string);
offset = ( buf->start + m->init_delay ) * m->samplerate / 90000 -
m->sum_dur;
}
+
/* 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->new_chap )
- {
- struct hb_text_sample_s *sample;
+ {
+ 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
duration = 1000 * m->samplerate / 90000;
}
- sample = MP4GenerateChapterSample( m, duration, buf->new_chap );
+ 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);
- 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 = buf->new_chap;
m->chapter_duration += duration;
}
buf->size,
duration,
offset,
- ((buf->frametype & HB_FRAME_KEY) != 0) ) )
+ ( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data ) ?
+ ( buf->frametype == HB_FRAME_IDR ) : ( ( buf->frametype & HB_FRAME_KEY ) != 0 ) ) )
{
hb_error("Failed to write to output file, disk full?");
*job->die = 1;
static int MP4End( hb_mux_object_t * m )
{
hb_job_t * job = m->job;
+ hb_title_t * title = job->title;
/* 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 a second just skip it. */
if ( duration >= m->samplerate )
{
- struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, duration,
- m->current_chapter + 1 );
- 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);
+ chapter = hb_list_item( m->job->title->list_chapter,
+ m->current_chapter - 1 );
+
+ MP4AddChapter( m->file,
+ m->chapter_track,
+ duration,
+ (chapter != NULL) ? chapter->title : NULL);
}
}
}
}
+ /*
+ * Write the MP4 iTunes metadata if we have any metadata
+ */
+ if( title->metadata )
+ {
+ hb_metadata_t *md = title->metadata;
+
+ 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 );
+ if( md->coverart )
+ {
+ MP4SetMetadataCoverArt( m->file, md->coverart, md->coverart_size);
+ }
+ }
+
MP4Close( m->file );
if ( job->mp4_optimize )