/* $Id: muxmp4.c,v 1.24 2005/11/04 13:09:41 titer Exp $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ /* libmp4v2 header */ #include "mp4.h" #include "hb.h" void AddIPodUUID(MP4FileHandle, MP4TrackId); struct hb_mux_object_s { HB_MUX_COMMON; hb_job_t * job; /* libmp4v2 handle */ MP4FileHandle file; /* Cumulated durations so far, in timescale units (see MP4Mux) */ uint64_t sum_dur; }; struct hb_mux_data_s { MP4TrackId track; }; /********************************************************************** * 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; /* Create an empty mp4 file */ m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 ); /* Video track */ mux_data = malloc( 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 */ MP4SetTimeScale( m->file, job->arate ); if( job->vcodec == HB_VCODEC_X264 ) { /* Stolen from mp4creator */ MP4SetVideoProfileLevel( m->file, 0x7F ); if (job->areBframes == 1) { hb_log("muxmp4: Adjusting duration for B-frames"); mux_data->track = MP4AddH264VideoTrack( m->file, job->arate, MP4_INVALID_DURATION+1, 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 */ } else { hb_log("muxmp4: Using default duration as there are no B-frames"); mux_data->track = MP4AddH264VideoTrack( m->file, job->arate, 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 */ } 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->h264_level == 30) { hb_log("About to add iPod atom"); AddIPodUUID(m->file, mux_data->track); } } else /* FFmpeg or XviD */ { MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 ); mux_data->track = MP4AddVideoTrack( m->file, job->arate, MP4_INVALID_DURATION, job->width, job->height, MP4_MPEG4_VIDEO_TYPE ); /* VOL from FFmpeg or XviD */ MP4SetTrackESConfiguration( m->file, mux_data->track, job->config.mpeg4.bytes, job->config.mpeg4.length ); } /* apply the anamorphic transformation matrix if needed */ if( job->pixel_ratio ) { uint8_t* val; uint8_t nval[38]; uint32_t *ptr32 = (uint32_t*) (nval + 2); uint32_t size; MP4GetBytesProperty(m->file, "moov.trak.tkhd.reserved3", &val, &size); if (size == 38) { 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; #ifdef WORDS_BIGENDIAN ptr32[0] = widthRatioInt; #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); #endif if(!MP4SetBytesProperty(m->file, "moov.trak.tkhd.reserved3", nval, size)) { hb_log("Problem setting transform matrix"); } } } /* end of transformation matrix */ 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 ) ); audio->mux_data = mux_data; mux_data->track = MP4AddAudioTrack( m->file, job->arate, 1024, MP4_MPEG4_AUDIO_TYPE ); MP4SetAudioProfileLevel( m->file, 0x0F ); MP4SetTrackESConfiguration( m->file, mux_data->track, audio->config.aac.bytes, audio->config.aac.length ); } 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; uint64_t duration; if( mux_data == job->mux_data ) { /* 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; m->sum_dur += duration; } else { /* Audio */ duration = MP4_INVALID_DURATION; } MP4WriteSample( m->file, mux_data->track, buf->data, buf->size, duration, 0, buf->key ); return 0; } static int MP4End( hb_mux_object_t * m ) { #if 0 hb_job_t * job = m->job; #endif char filename[1024]; memset( filename, 0, 1024 ); 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 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; }