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\n",
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;
603 stylerecord *stylestack = 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 stylestack = stylestack->next;
649 hb_error("Mismatched Subtitle markup '%s'", input);
653 *writer++ = *reader++;
657 if (*(reader+3) == '>') {
658 if (stylestack && stylestack->style == BOLD) {
659 uint8_t style_record[12];
660 stylestack->stop = writer - output;
661 hb_makestyleatom(stylestack, style_record);
663 memcpy(style + 10 + (12 * stylecount), style_record, 12);
666 stylestack = stylestack->next;
668 hb_error("Mismatched Subtitle markup '%s'", input);
673 *writer++ = *reader++;
677 if (*(reader+3) == '>') {
678 if (stylestack && stylestack->style == UNDERLINE) {
679 uint8_t style_record[12];
680 stylestack->stop = writer - output;
681 hb_makestyleatom(stylestack, style_record);
683 memcpy(style + 10 + (12 * stylecount), style_record, 12);
686 stylestack = stylestack->next;
688 hb_error("Mismatched Subtitle markup '%s'", input);
692 *writer++ = *reader++;
696 *writer++ = *reader++;
701 *writer++ = *reader++;
705 *writer++ = *reader++;
712 *stylesize = 10 + ( stylecount * 12 );
714 memcpy( style + 4, "styl", 4);
718 style[2] = (*stylesize >> 8) & 0xff;
719 style[3] = *stylesize & 0xff;
720 style[8] = (stylecount >> 8) & 0xff;
721 style[9] = stylecount & 0xff;
727 static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
730 hb_job_t * job = m->job;
734 if( mux_data == job->mux_data )
738 // if there are b-frames compute the render offset
739 // (we'll need it for both the video frame & the chapter track)
742 offset = buf->start + m->init_delay - m->sum_dur;
745 hb_log("MP4Mux: illegal render offset %"PRId64", start %"PRId64","
746 "stop %"PRId64", sum_dur %"PRId64,
747 offset, buf->start, buf->stop, m->sum_dur );
752 /* Add the sample before the new frame.
753 It is important that this be calculated prior to the duration
754 of the new video sample, as we want to sync to right after it.
755 (This is because of how durations for text tracks work in QT) */
756 if( job->chapter_markers && buf->new_chap )
758 hb_chapter_t *chapter = NULL;
760 // this chapter is postioned by writing out the previous chapter.
761 // the duration of the previous chapter is the duration up to but
762 // not including the current frame minus the duration of all
763 // chapters up to the previous.
764 // The initial and final chapters can be very short (a second or
765 // less) since they're not really chapters but just a placeholder to
766 // insert a cell command. We don't write chapters shorter than 1.5 sec.
767 duration = m->sum_dur - m->chapter_duration + offset;
768 if ( duration >= (90000*3)/2 )
770 chapter = hb_list_item( m->job->title->list_chapter,
773 MP4AddChapter( m->file,
776 (chapter != NULL) ? chapter->title : NULL);
778 m->current_chapter = buf->new_chap;
779 m->chapter_duration += duration;
783 // We're getting the frames in decode order but the timestamps are
784 // for presentation so we have to use durations and effectively
786 duration = buf->stop - buf->start;
789 /* We got an illegal mp4/h264 duration. This shouldn't
790 be possible and usually indicates a bug in the upstream code.
791 Complain in the hope that someone will go find the bug but
792 try to fix the error so that the file will still be playable. */
793 hb_log("MP4Mux: illegal duration %"PRId64", start %"PRId64","
794 "stop %"PRId64", sum_dur %"PRId64,
795 duration, buf->start, buf->stop, m->sum_dur );
796 /* we don't know when the next frame starts so we can't pick a
797 valid duration for this one. we pick something "short"
798 (roughly 1/3 of an NTSC frame time) to take time from
802 m->sum_dur += duration;
807 duration = MP4_INVALID_DURATION;
810 /* Here's where the sample actually gets muxed. */
811 if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
813 /* Compute dependency flags.
815 * This mechanism is (optionally) used by media players such as QuickTime
816 * to offer better scrubbing performance. The most influential bits are
817 * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
819 * Other bits are possible but no example media using such bits have been
822 * It is acceptable to supply 0-bits for any samples which characteristics
823 * cannot be positively guaranteed.
828 /* encoding layer signals if frame is referenced by other frames */
829 if( buf->flags & HB_FRAME_REF )
830 dflags |= MP4_SDT_HAS_DEPENDENTS;
832 dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
834 switch( buf->frametype )
840 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
843 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
848 break; /* nothing to mark */
851 if( !MP4WriteSampleDependency( m->file,
860 hb_error("Failed to write to output file, disk full?");
864 else if (mux_data->subtitle)
866 if( mux_data->sub_format == TEXTSUB )
868 /* Write an empty sample */
869 if ( mux_data->sum_dur < buf->start )
871 uint8_t empty[2] = {0,0};
872 if( !MP4WriteSample( m->file,
876 buf->start - mux_data->sum_dur,
880 hb_error("Failed to write to output file, disk full?");
883 mux_data->sum_dur += buf->start - mux_data->sum_dur;
885 uint8_t styleatom[2048];;
886 uint16_t stylesize = 0;
887 uint8_t buffer[2048];
888 uint16_t buffersize = 0;
889 uint8_t output[2048];
894 * Copy the subtitle into buffer stripping markup and creating
895 * style atoms for them.
897 hb_muxmp4_process_subtitle_style( buf->data,
899 styleatom, &stylesize );
901 buffersize = strlen((char*)buffer);
903 hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%"PRId64": %s",
904 (float)buf->start / 90000, buf->start, buf->stop,
905 (buf->stop - buf->start), buffer);
907 hb_log("MuxMP4: sub len: %d, style_len %d", buffersize, stylesize);
909 /* Write the subtitle sample */
910 memcpy( output + 2, buffer, buffersize );
911 memcpy( output + 2 + buffersize, styleatom, stylesize);
912 output[0] = ( buffersize >> 8 ) & 0xff;
913 output[1] = buffersize & 0xff;
915 if( !MP4WriteSample( m->file,
918 buffersize + stylesize + 2,
919 buf->stop - buf->start,
923 hb_error("Failed to write to output file, disk full?");
927 mux_data->sum_dur += (buf->stop - buf->start);
928 hb_deep_log(3, "MuxMP4:Total time elapsed:%"PRId64, mux_data->sum_dur);
936 if( !MP4WriteSample( m->file,
942 ( buf->frametype & HB_FRAME_KEY ) != 0 ))
944 hb_error("Failed to write to output file, disk full?");
953 static int MP4End( hb_mux_object_t * m )
955 hb_job_t * job = m->job;
956 hb_title_t * title = job->title;
958 /* Write our final chapter marker */
959 if( m->job->chapter_markers )
961 hb_chapter_t *chapter = NULL;
962 int64_t duration = m->sum_dur - m->chapter_duration;
963 /* The final chapter can have a very short duration - if it's less
964 * than 1.5 seconds just skip it. */
965 if ( duration >= (90000*3)/2 )
968 chapter = hb_list_item( m->job->title->list_chapter,
969 m->current_chapter - 1 );
971 MP4AddChapter( m->file,
974 (chapter != NULL) ? chapter->title : NULL);
980 // Insert track edit to get A/V back in sync. The edit amount is
982 int64_t edit_amt = m->init_delay;
983 MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
984 MP4GetTrackDuration(m->file, 1), 0);
985 if ( m->job->chapter_markers )
987 // apply same edit to chapter track to keep it in sync with video
988 MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
990 MP4GetTrackDuration(m->file, m->chapter_track), 0);
995 * Write the MP4 iTunes metadata if we have any metadata
997 if( title->metadata )
999 hb_metadata_t *md = title->metadata;
1000 const MP4Tags* tags;
1002 hb_deep_log( 2, "Writing Metadata to output file...");
1004 /* allocate tags structure */
1005 tags = MP4TagsAlloc();
1006 /* fetch data from MP4 file (in case it already has some data) */
1007 MP4TagsFetch( tags, m->file );
1010 if( strlen( md->name ))
1011 MP4TagsSetName( tags, md->name );
1012 if( strlen( md->artist ))
1013 MP4TagsSetArtist( tags, md->artist );
1014 if( strlen( md->composer ))
1015 MP4TagsSetComposer( tags, md->composer );
1016 if( strlen( md->comment ))
1017 MP4TagsSetComments( tags, md->comment );
1018 if( strlen( md->release_date ))
1019 MP4TagsSetReleaseDate( tags, md->release_date );
1020 if( strlen( md->album ))
1021 MP4TagsSetAlbum( tags, md->album );
1022 if( strlen( md->genre ))
1023 MP4TagsSetGenre( tags, md->genre );
1028 art.data = md->coverart;
1029 art.size = md->coverart_size;
1030 art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
1031 MP4TagsAddArtwork( tags, &art );
1034 /* push data to MP4 file */
1035 MP4TagsStore( tags, m->file );
1036 /* free memory associated with structure */
1037 MP4TagsFree( tags );
1040 MP4Close( m->file );
1042 if ( job->mp4_optimize )
1044 hb_log( "muxmp4: optimizing file" );
1045 char filename[1024]; memset( filename, 0, 1024 );
1046 snprintf( filename, 1024, "%s.tmp", job->file );
1047 MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
1048 remove( job->file );
1049 rename( filename, job->file );
1055 hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
1057 hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );