1 /* $Id: muxmp4.c,v 1.24 2005/11/04 13:09:41 titer Exp $
3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.fr/>.
5 It may be used under the terms of the GNU General Public License. */
7 #include "mp4v2/mp4v2.h"
8 #include "a52dec/a52.h"
12 struct hb_mux_object_s
21 int64_t sum_dur; // sum of video frame durations so far
23 // bias to keep render offsets in ctts atom positive (set up by encx264)
26 /* Chapter state information for muxing */
27 MP4TrackId chapter_track;
29 uint64_t chapter_duration;
38 uint64_t sum_dur; // sum of the frame durations so far
41 /* Tune video track chunk duration.
42 * libmp4v2 default duration == dusamplerate == 1 second.
43 * Per van's suggestion we desire duration == 4 frames.
44 * Should be invoked immediately after track creation.
46 * return true on fail, false on success.
48 static int MP4TuneTrackDurationPerChunk( hb_mux_object_t* m, MP4TrackId trackId )
53 tscale = MP4GetTrackTimeScale( m->file, trackId );
54 dur = (MP4Duration)ceil( (double)tscale * (double)m->job->vrate_base / (double)m->job->vrate * 4.0 );
56 if( !MP4SetTrackDurationPerChunk( m->file, trackId, dur ))
58 hb_error( "muxmp4.c: MP4SetTrackDurationPerChunk failed!" );
63 hb_deep_log( 2, "muxmp4: track %u, chunk duration %"PRIu64, MP4FindTrackIndex( m->file, trackId ), dur );
67 /**********************************************************************
69 **********************************************************************
70 * Allocates hb_mux_data_t structures, create file and write headers
71 *********************************************************************/
72 static int MP4Init( hb_mux_object_t * m )
74 hb_job_t * job = m->job;
75 hb_title_t * title = job->title;
78 hb_mux_data_t * mux_data;
82 /* Flags for enabling/disabling tracks in an MP4. */
83 typedef enum { TRACK_DISABLED = 0x0, TRACK_ENABLED = 0x1, TRACK_IN_MOVIE = 0x2, TRACK_IN_PREVIEW = 0x4, TRACK_IN_POSTER = 0x8} track_header_flags;
85 /* Create an empty mp4 file */
86 if (job->largeFileSize)
87 /* Use 64-bit MP4 file */
89 m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA );
90 hb_deep_log( 2, "muxmp4: using 64-bit MP4 formatting.");
93 /* Limit MP4s to less than 4 GB */
95 m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
98 if (m->file == MP4_INVALID_FILE_HANDLE)
100 hb_error("muxmp4.c: MP4Create failed!");
106 mux_data = calloc(1, sizeof( hb_mux_data_t ) );
107 job->mux_data = mux_data;
109 if (!(MP4SetTimeScale( m->file, 90000 )))
111 hb_error("muxmp4.c: MP4SetTimeScale failed!");
116 if( job->vcodec == HB_VCODEC_X264 )
118 /* Stolen from mp4creator */
119 MP4SetVideoProfileLevel( m->file, 0x7F );
120 mux_data->track = MP4AddH264VideoTrack( m->file, 90000,
121 MP4_INVALID_DURATION, job->width, job->height,
122 job->config.h264.sps[1], /* AVCProfileIndication */
123 job->config.h264.sps[2], /* profile_compat */
124 job->config.h264.sps[3], /* AVCLevelIndication */
125 3 ); /* 4 bytes length before each NAL unit */
126 if ( mux_data->track == MP4_INVALID_TRACK_ID )
128 hb_error( "muxmp4.c: MP4AddH264VideoTrack failed!" );
133 /* Tune track chunk duration */
134 if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
139 MP4AddH264SequenceParameterSet( m->file, mux_data->track,
140 job->config.h264.sps, job->config.h264.sps_length );
141 MP4AddH264PictureParameterSet( m->file, mux_data->track,
142 job->config.h264.pps, job->config.h264.pps_length );
144 if( job->h264_level == 30 || job->ipod_atom)
146 hb_deep_log( 2, "muxmp4: adding iPod atom");
147 MP4AddIPodUUID(m->file, mux_data->track);
150 m->init_delay = job->config.h264.init_delay;
152 else /* FFmpeg or XviD */
154 MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
155 mux_data->track = MP4AddVideoTrack( m->file, 90000,
156 MP4_INVALID_DURATION, job->width, job->height,
157 MP4_MPEG4_VIDEO_TYPE );
158 if (mux_data->track == MP4_INVALID_TRACK_ID)
160 hb_error("muxmp4.c: MP4AddVideoTrack failed!");
165 /* Tune track chunk duration */
166 if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
171 /* VOL from FFmpeg or XviD */
172 if (!(MP4SetTrackESConfiguration( m->file, mux_data->track,
173 job->config.mpeg4.bytes, job->config.mpeg4.length )))
175 hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!");
181 // COLR atom for color and gamma correction.
183 // http://developer.apple.com/quicktime/icefloe/dispatch019.html#colr
184 // http://forum.doom9.org/showthread.php?t=133982#post1090068
185 // the user can set it from job->color_matrix, otherwise by default
186 // we say anything that's likely to be HD content is ITU BT.709 and
187 // DVD, SD TV & other content is ITU BT.601. We look at the title height
188 // rather than the job height here to get uncropped input dimensions.
189 if( job->color_matrix == 1 )
191 // ITU BT.601 DVD or SD TV content
192 MP4AddColr(m->file, mux_data->track, 6, 1, 6);
194 else if( job->color_matrix == 2 )
196 // ITU BT.709 HD content
197 MP4AddColr(m->file, mux_data->track, 1, 1, 1);
199 else if ( job->title->width >= 1280 || job->title->height >= 720 )
201 // we guess that 720p or above is ITU BT.709 HD content
202 MP4AddColr(m->file, mux_data->track, 1, 1, 1);
206 // ITU BT.601 DVD or SD TV content
207 MP4AddColr(m->file, mux_data->track, 6, 1, 6);
210 if( job->anamorphic.mode )
212 /* PASP atom for anamorphic video */
215 width = job->anamorphic.par_width;
217 height = job->anamorphic.par_height;
219 MP4AddPixelAspectRatio(m->file, mux_data->track, (uint32_t)width, (uint32_t)height);
221 MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.width", job->width * (width / height));
224 /* add the audio tracks */
225 for( i = 0; i < hb_list_count( title->list_audio ); i++ )
227 audio = hb_list_item( title->list_audio, i );
228 mux_data = calloc(1, sizeof( hb_mux_data_t ) );
229 audio->priv.mux_data = mux_data;
231 if( audio->config.out.codec == HB_ACODEC_AC3 )
234 uint8_t bsid = audio->config.in.version;
235 uint8_t bsmod = audio->config.in.mode;
236 uint8_t acmod = audio->config.flags.ac3 & 0x7;
237 uint8_t lfeon = (audio->config.flags.ac3 & A52_LFE) ? 1 : 0;
238 uint8_t bit_rate_code = 0;
241 * Rewrite AC3 information into correct format for dac3 atom
243 switch( audio->config.in.samplerate )
256 * Error value, tells decoder to not decode this audio.
262 switch( audio->config.in.bitrate )
322 hb_error("Unknown AC3 bitrate");
327 mux_data->track = MP4AddAC3AudioTrack(
329 audio->config.out.samplerate,
337 /* Tune track chunk duration */
338 MP4TuneTrackDurationPerChunk( m, mux_data->track );
340 if (audio->config.out.name == NULL) {
341 MP4SetTrackBytesProperty(
342 m->file, mux_data->track,
344 (const uint8_t*)"Surround", strlen("Surround"));
347 MP4SetTrackBytesProperty(
348 m->file, mux_data->track,
350 (const uint8_t*)(audio->config.out.name),
351 strlen(audio->config.out.name));
354 mux_data->track = MP4AddAudioTrack(
356 audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
358 /* Tune track chunk duration */
359 MP4TuneTrackDurationPerChunk( m, mux_data->track );
361 if (audio->config.out.name == NULL) {
362 MP4SetTrackBytesProperty(
363 m->file, mux_data->track,
365 (const uint8_t*)"Stereo", strlen("Stereo"));
368 MP4SetTrackBytesProperty(
369 m->file, mux_data->track,
371 (const uint8_t*)(audio->config.out.name),
372 strlen(audio->config.out.name));
375 MP4SetAudioProfileLevel( m->file, 0x0F );
376 MP4SetTrackESConfiguration(
377 m->file, mux_data->track,
378 audio->priv.config.aac.bytes, audio->priv.config.aac.length );
380 /* Set the correct number of channels for this track */
381 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (uint16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown));
384 /* Set the language for this track */
385 MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2);
387 if( hb_list_count( title->list_audio ) > 1 )
389 /* Set the audio track alternate group */
390 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1);
394 /* Enable the first audio track */
395 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
398 /* Disable the other audio tracks so QuickTime doesn't play
401 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
402 hb_deep_log( 2, "muxmp4: disabled extra audio track %u", MP4FindTrackIndex( m->file, mux_data->track ));
407 // Quicktime requires that at least one subtitle is enabled,
408 // else it doesn't show any of the subtitles.
409 // So check to see if any of the subtitles are flagged to be
410 // the defualt. The default will the the enabled track, else
411 // enable the first track.
412 subtitle_default = 0;
413 for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
415 hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
417 if( subtitle && subtitle->format == TEXTSUB &&
418 subtitle->config.dest == PASSTHRUSUB )
420 if ( subtitle->config.default_track )
421 subtitle_default = 1;
424 for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
426 hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
428 if( subtitle && subtitle->format == TEXTSUB &&
429 subtitle->config.dest == PASSTHRUSUB )
431 uint64_t width, height = 60;
432 if( job->anamorphic.mode )
433 width = job->width * ( (float) job->anamorphic.par_width / job->anamorphic.par_height );
437 mux_data = calloc(1, sizeof( hb_mux_data_t ) );
438 subtitle->mux_data = mux_data;
439 mux_data->subtitle = 1;
440 mux_data->sub_format = subtitle->format;
441 mux_data->track = MP4AddSubtitleTrack( m->file, 90000, width, height );
443 MP4SetTrackLanguage(m->file, mux_data->track, subtitle->iso639_2);
445 /* Tune track chunk duration */
446 MP4TuneTrackDurationPerChunk( m, mux_data->track );
448 const uint8_t textColor[4] = { 255,255,255,255 };
450 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 2);
452 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.dataReferenceIndex", 1);
453 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.horizontalJustification", 1);
454 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.verticalJustification", 0);
456 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.bgColorAlpha", 255);
458 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxBottom", height);
459 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", width);
461 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontID", 1);
462 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontSize", 24);
464 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorRed", textColor[0]);
465 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorGreen", textColor[1]);
466 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorBlue", textColor[2]);
467 MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorAlpha", textColor[3]);
469 /* translate the track */
472 uint32_t *ptr32 = (uint32_t*) nval;
475 MP4GetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", &val, &size);
476 memcpy(nval, val, size);
478 const uint32_t ytranslation = (job->height - height) * 0x10000;
480 #ifdef WORDS_BIGENDIAN
481 ptr32[7] = ytranslation;
483 /* we need to switch the endianness, as the file format expects big endian */
484 ptr32[7] = ((ytranslation & 0x000000FF) << 24) + ((ytranslation & 0x0000FF00) << 8) +
485 ((ytranslation & 0x00FF0000) >> 8) + ((ytranslation & 0xFF000000) >> 24);
488 MP4SetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", nval, size);
489 if ( !subtitle_default || subtitle->config.default_track ) {
490 /* Enable the default subtitle track */
491 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
492 subtitle_default = 1;
496 MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
501 if (job->chapter_markers)
503 /* add a text track for the chapters. We add the 'chap' atom to track
504 one which is usually the video track & should never be disabled.
505 The Quicktime spec says it doesn't matter which media track the
506 chap atom is on but it has to be an enabled track. */
507 MP4TrackId textTrack;
508 textTrack = MP4AddChapterTextTrack(m->file, 1, 0);
510 m->chapter_track = textTrack;
511 m->chapter_duration = 0;
512 m->current_chapter = job->chapter_start;
515 /* Add encoded-by metadata listing version and build date */
517 tool_string = (char *)malloc(80);
518 snprintf( tool_string, 80, "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD);
520 /* allocate,fetch,populate,store,free tags structure */
522 tags = MP4TagsAlloc();
523 MP4TagsFetch( tags, m->file );
524 MP4TagsSetEncodingTool( tags, tool_string );
525 MP4TagsStore( tags, m->file );
533 typedef struct stylerecord_s {
534 enum style_s {ITALIC, BOLD, UNDERLINE} style;
537 struct stylerecord_s *next;
540 static void hb_makestylerecord( stylerecord **stack,
541 enum style_s style, int start )
543 stylerecord *record = calloc( sizeof( stylerecord ), 1 );
547 record->style = style;
548 record->start = start;
549 record->next = *stack;
554 static void hb_makestyleatom( stylerecord *record, uint8_t *style)
557 hb_deep_log(3, "Made style '%s' from %d to %d",
558 record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop);
560 switch( record->style )
576 style[0] = (record->start >> 8) & 0xff; // startChar
577 style[1] = record->start & 0xff;
578 style[2] = (record->stop >> 8) & 0xff; // endChar
579 style[3] = record->stop & 0xff;
580 style[4] = (1 >> 8) & 0xff; // font-ID
582 style[6] = face; // face-style-flags: 1 bold; 2 italic; 4 underline
583 style[7] = 24; // font-size
586 style[10] = 255; // b
587 style[11] = 255; // a
592 * Copy the input to output removing markup and adding markup to the style
593 * atom where appropriate.
595 static void hb_muxmp4_process_subtitle_style( uint8_t *input,
597 uint8_t *style, uint16_t *stylesize )
599 uint8_t *reader = input;
600 uint8_t *writer = output;
601 uint8_t stylecount = 0;
602 stylerecord *stylestack = NULL;
603 stylerecord *oldrecord = NULL;
605 while(*reader != '\0') {
606 if (*reader == '<') {
608 * possible markup, peek at the next chr
610 switch(*(reader+1)) {
612 if (*(reader+2) == '>') {
614 hb_makestylerecord(&stylestack, ITALIC, writer-output);
616 *writer++ = *reader++;
620 if (*(reader+2) == '>') {
622 hb_makestylerecord(&stylestack, BOLD, writer-output);
624 *writer++ = *reader++;
628 if (*(reader+2) == '>') {
630 hb_makestylerecord(&stylestack, UNDERLINE, writer-output);
632 *writer++ = *reader++;
636 switch(*(reader+2)) {
638 if (*(reader+3) == '>') {
639 if (stylestack && stylestack->style == ITALIC) {
640 uint8_t style_record[12];
641 stylestack->stop = writer - output;
642 hb_makestyleatom(stylestack, style_record);
644 memcpy(style + 10 + (12 * stylecount), style_record, 12);
647 oldrecord = stylestack;
648 stylestack = stylestack->next;
651 hb_error("Mismatched Subtitle markup '%s'", input);
655 *writer++ = *reader++;
659 if (*(reader+3) == '>') {
660 if (stylestack && stylestack->style == BOLD) {
661 uint8_t style_record[12];
662 stylestack->stop = writer - output;
663 hb_makestyleatom(stylestack, style_record);
665 memcpy(style + 10 + (12 * stylecount), style_record, 12);
667 oldrecord = stylestack;
668 stylestack = stylestack->next;
671 hb_error("Mismatched Subtitle markup '%s'", input);
676 *writer++ = *reader++;
680 if (*(reader+3) == '>') {
681 if (stylestack && stylestack->style == UNDERLINE) {
682 uint8_t style_record[12];
683 stylestack->stop = writer - output;
684 hb_makestyleatom(stylestack, style_record);
686 memcpy(style + 10 + (12 * stylecount), style_record, 12);
689 oldrecord = stylestack;
690 stylestack = stylestack->next;
693 hb_error("Mismatched Subtitle markup '%s'", input);
697 *writer++ = *reader++;
701 *writer++ = *reader++;
706 *writer++ = *reader++;
710 *writer++ = *reader++;
717 *stylesize = 10 + ( stylecount * 12 );
719 memcpy( style + 4, "styl", 4);
723 style[2] = (*stylesize >> 8) & 0xff;
724 style[3] = *stylesize & 0xff;
725 style[8] = (stylecount >> 8) & 0xff;
726 style[9] = stylecount & 0xff;
732 static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
735 hb_job_t * job = m->job;
739 if( mux_data == job->mux_data )
743 // if there are b-frames compute the render offset
744 // (we'll need it for both the video frame & the chapter track)
747 offset = buf->start + m->init_delay - m->sum_dur;
750 hb_log("MP4Mux: illegal render offset %"PRId64", start %"PRId64","
751 "stop %"PRId64", sum_dur %"PRId64,
752 offset, buf->start, buf->stop, m->sum_dur );
757 /* Add the sample before the new frame.
758 It is important that this be calculated prior to the duration
759 of the new video sample, as we want to sync to right after it.
760 (This is because of how durations for text tracks work in QT) */
761 if( job->chapter_markers && buf->new_chap )
763 hb_chapter_t *chapter = NULL;
765 // this chapter is postioned by writing out the previous chapter.
766 // the duration of the previous chapter is the duration up to but
767 // not including the current frame minus the duration of all
768 // chapters up to the previous.
769 // The initial and final chapters can be very short (a second or
770 // less) since they're not really chapters but just a placeholder to
771 // insert a cell command. We don't write chapters shorter than 1.5 sec.
772 duration = m->sum_dur - m->chapter_duration + offset;
773 if ( duration >= (90000*3)/2 )
775 chapter = hb_list_item( m->job->title->list_chapter,
778 MP4AddChapter( m->file,
781 (chapter != NULL) ? chapter->title : NULL);
783 m->current_chapter = buf->new_chap;
784 m->chapter_duration += duration;
788 // We're getting the frames in decode order but the timestamps are
789 // for presentation so we have to use durations and effectively
791 duration = buf->stop - buf->start;
794 /* We got an illegal mp4/h264 duration. This shouldn't
795 be possible and usually indicates a bug in the upstream code.
796 Complain in the hope that someone will go find the bug but
797 try to fix the error so that the file will still be playable. */
798 hb_log("MP4Mux: illegal duration %"PRId64", start %"PRId64","
799 "stop %"PRId64", sum_dur %"PRId64,
800 duration, buf->start, buf->stop, m->sum_dur );
801 /* we don't know when the next frame starts so we can't pick a
802 valid duration for this one. we pick something "short"
803 (roughly 1/3 of an NTSC frame time) to take time from
807 m->sum_dur += duration;
812 duration = MP4_INVALID_DURATION;
815 /* Here's where the sample actually gets muxed. */
816 if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
818 /* Compute dependency flags.
820 * This mechanism is (optionally) used by media players such as QuickTime
821 * to offer better scrubbing performance. The most influential bits are
822 * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
824 * Other bits are possible but no example media using such bits have been
827 * It is acceptable to supply 0-bits for any samples which characteristics
828 * cannot be positively guaranteed.
833 /* encoding layer signals if frame is referenced by other frames */
834 if( buf->flags & HB_FRAME_REF )
835 dflags |= MP4_SDT_HAS_DEPENDENTS;
837 dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
839 switch( buf->frametype )
845 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
848 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
853 break; /* nothing to mark */
856 if( !MP4WriteSampleDependency( m->file,
865 hb_error("Failed to write to output file, disk full?");
869 else if (mux_data->subtitle)
871 if( mux_data->sub_format == TEXTSUB )
873 /* Write an empty sample */
874 if ( mux_data->sum_dur < buf->start )
876 uint8_t empty[2] = {0,0};
877 if( !MP4WriteSample( m->file,
881 buf->start - mux_data->sum_dur,
885 hb_error("Failed to write to output file, disk full?");
888 mux_data->sum_dur += buf->start - mux_data->sum_dur;
890 uint8_t styleatom[2048];;
891 uint16_t stylesize = 0;
892 uint8_t buffer[2048];
893 uint16_t buffersize = 0;
894 uint8_t output[2048];
899 * Copy the subtitle into buffer stripping markup and creating
900 * style atoms for them.
902 hb_muxmp4_process_subtitle_style( buf->data,
904 styleatom, &stylesize );
906 buffersize = strlen((char*)buffer);
908 hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%"PRId64": %s",
909 (float)buf->start / 90000, buf->start, buf->stop,
910 (buf->stop - buf->start), buffer);
912 /* Write the subtitle sample */
913 memcpy( output + 2, buffer, buffersize );
914 memcpy( output + 2 + buffersize, styleatom, stylesize);
915 output[0] = ( buffersize >> 8 ) & 0xff;
916 output[1] = buffersize & 0xff;
918 if( !MP4WriteSample( m->file,
921 buffersize + stylesize + 2,
922 buf->stop - buf->start,
926 hb_error("Failed to write to output file, disk full?");
930 mux_data->sum_dur += (buf->stop - buf->start);
931 hb_deep_log(3, "MuxMP4:Total time elapsed:%"PRId64, mux_data->sum_dur);
939 if( !MP4WriteSample( m->file,
945 ( buf->frametype & HB_FRAME_KEY ) != 0 ))
947 hb_error("Failed to write to output file, disk full?");
956 static int MP4End( hb_mux_object_t * m )
958 hb_job_t * job = m->job;
959 hb_title_t * title = job->title;
961 /* Write our final chapter marker */
962 if( m->job->chapter_markers )
964 hb_chapter_t *chapter = NULL;
965 int64_t duration = m->sum_dur - m->chapter_duration;
966 /* The final chapter can have a very short duration - if it's less
967 * than 1.5 seconds just skip it. */
968 if ( duration >= (90000*3)/2 )
971 chapter = hb_list_item( m->job->title->list_chapter,
972 m->current_chapter - 1 );
974 MP4AddChapter( m->file,
977 (chapter != NULL) ? chapter->title : NULL);
983 // Insert track edit to get A/V back in sync. The edit amount is
985 int64_t edit_amt = m->init_delay;
986 MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
987 MP4GetTrackDuration(m->file, 1), 0);
988 if ( m->job->chapter_markers )
990 // apply same edit to chapter track to keep it in sync with video
991 MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
993 MP4GetTrackDuration(m->file, m->chapter_track), 0);
998 * Write the MP4 iTunes metadata if we have any metadata
1000 if( title->metadata )
1002 hb_metadata_t *md = title->metadata;
1003 const MP4Tags* tags;
1005 hb_deep_log( 2, "Writing Metadata to output file...");
1007 /* allocate tags structure */
1008 tags = MP4TagsAlloc();
1009 /* fetch data from MP4 file (in case it already has some data) */
1010 MP4TagsFetch( tags, m->file );
1013 if( strlen( md->name ))
1014 MP4TagsSetName( tags, md->name );
1015 if( strlen( md->artist ))
1016 MP4TagsSetArtist( tags, md->artist );
1017 if( strlen( md->composer ))
1018 MP4TagsSetComposer( tags, md->composer );
1019 if( strlen( md->comment ))
1020 MP4TagsSetComments( tags, md->comment );
1021 if( strlen( md->release_date ))
1022 MP4TagsSetReleaseDate( tags, md->release_date );
1023 if( strlen( md->album ))
1024 MP4TagsSetAlbum( tags, md->album );
1025 if( strlen( md->genre ))
1026 MP4TagsSetGenre( tags, md->genre );
1031 art.data = md->coverart;
1032 art.size = md->coverart_size;
1033 art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
1034 MP4TagsAddArtwork( tags, &art );
1037 /* push data to MP4 file */
1038 MP4TagsStore( tags, m->file );
1039 /* free memory associated with structure */
1040 MP4TagsFree( tags );
1043 MP4Close( m->file );
1045 if ( job->mp4_optimize )
1047 hb_log( "muxmp4: optimizing file" );
1048 char filename[1024]; memset( filename, 0, 1024 );
1049 snprintf( filename, 1024, "%s.tmp", job->file );
1050 MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
1051 remove( job->file );
1052 rename( filename, job->file );
1058 hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
1060 hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );