OSDN Git Service

Push an EOF onto the subtitle fifos from the reader for DVD VOBSUBs and also from...
[handbrake-jp/handbrake-jp-git.git] / libhb / muxmp4.c
1 /* $Id: muxmp4.c,v 1.24 2005/11/04 13:09:41 titer Exp $
2
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. */
6
7 #include "mp4v2/mp4v2.h"
8 #include "a52dec/a52.h"
9
10 #include "hb.h"
11
12 struct hb_mux_object_s
13 {
14     HB_MUX_COMMON;
15
16     hb_job_t * job;
17
18     /* libmp4v2 handle */
19     MP4FileHandle file;
20
21     int64_t sum_dur;    // sum of video frame durations so far
22
23     // bias to keep render offsets in ctts atom positive (set up by encx264)
24     int64_t init_delay;
25
26     /* Chapter state information for muxing */
27     MP4TrackId chapter_track;
28     int current_chapter;
29     uint64_t chapter_duration;
30 };
31
32 struct hb_mux_data_s
33 {
34     MP4TrackId track;
35 };
36
37 /* Tune video track chunk duration.
38  * libmp4v2 default duration == dusamplerate == 1 second.
39  * Per van's suggestion we desire duration == 4 frames.
40  * Should be invoked immediately after track creation.
41  *
42  * return true on fail, false on success.
43  */
44 static int MP4TuneTrackDurationPerChunk( hb_mux_object_t* m, MP4TrackId trackId )
45 {
46     uint32_t tscale;
47     MP4Duration dur;
48
49     tscale = MP4GetTrackTimeScale( m->file, trackId );
50     dur = (MP4Duration)ceil( (double)tscale * (double)m->job->vrate_base / (double)m->job->vrate * 4.0 );
51
52     if( !MP4SetTrackDurationPerChunk( m->file, trackId, dur ))
53     {
54         hb_error( "muxmp4.c: MP4SetTrackDurationPerChunk failed!" );
55         *m->job->die = 1;
56         return 0;
57     }
58
59     hb_deep_log( 2, "muxmp4: track %u, chunk duration %llu", MP4FindTrackIndex( m->file, trackId ), dur );
60     return 1;
61 }
62
63 /**********************************************************************
64  * MP4Init
65  **********************************************************************
66  * Allocates hb_mux_data_t structures, create file and write headers
67  *********************************************************************/
68 static int MP4Init( hb_mux_object_t * m )
69 {
70     hb_job_t   * job   = m->job;
71     hb_title_t * title = job->title;
72
73     hb_audio_t    * audio;
74     hb_mux_data_t * mux_data;
75     int i;
76
77     /* Flags for enabling/disabling tracks in an MP4. */
78     typedef enum { TRACK_DISABLED = 0x0, TRACK_ENABLED = 0x1, TRACK_IN_MOVIE = 0x2, TRACK_IN_PREVIEW = 0x4, TRACK_IN_POSTER = 0x8}  track_header_flags;
79
80     /* Create an empty mp4 file */
81     if (job->largeFileSize)
82     /* Use 64-bit MP4 file */
83     {
84         m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA );
85         hb_deep_log( 2, "muxmp4: using 64-bit MP4 formatting.");
86     }
87     else
88     /* Limit MP4s to less than 4 GB */
89     {
90         m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
91     }
92
93     if (m->file == MP4_INVALID_FILE_HANDLE)
94     {
95         hb_error("muxmp4.c: MP4Create failed!");
96         *job->die = 1;
97         return 0;
98     }
99
100     /* Video track */
101     mux_data      = malloc( sizeof( hb_mux_data_t ) );
102     job->mux_data = mux_data;
103
104     if (!(MP4SetTimeScale( m->file, 90000 )))
105     {
106         hb_error("muxmp4.c: MP4SetTimeScale failed!");
107         *job->die = 1;
108         return 0;
109     }
110
111     if( job->vcodec == HB_VCODEC_X264 )
112     {
113         /* Stolen from mp4creator */
114         MP4SetVideoProfileLevel( m->file, 0x7F );
115                 mux_data->track = MP4AddH264VideoTrack( m->file, 90000,
116                         MP4_INVALID_DURATION, job->width, job->height,
117                         job->config.h264.sps[1], /* AVCProfileIndication */
118                         job->config.h264.sps[2], /* profile_compat */
119                         job->config.h264.sps[3], /* AVCLevelIndication */
120                         3 );      /* 4 bytes length before each NAL unit */
121         if ( mux_data->track == MP4_INVALID_TRACK_ID )
122         {
123             hb_error( "muxmp4.c: MP4AddH264VideoTrack failed!" );
124             *job->die = 1;
125             return 0;
126         }
127
128         /* Tune track chunk duration */
129         if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
130         {
131             return 0;
132         }
133
134         MP4AddH264SequenceParameterSet( m->file, mux_data->track,
135                 job->config.h264.sps, job->config.h264.sps_length );
136         MP4AddH264PictureParameterSet( m->file, mux_data->track,
137                 job->config.h264.pps, job->config.h264.pps_length );
138
139                 if( job->h264_level == 30 || job->ipod_atom)
140                 {
141                         hb_deep_log( 2, "muxmp4: adding iPod atom");
142                         MP4AddIPodUUID(m->file, mux_data->track);
143                 }
144
145         m->init_delay = job->config.h264.init_delay;
146     }
147     else /* FFmpeg or XviD */
148     {
149         MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
150         mux_data->track = MP4AddVideoTrack( m->file, 90000,
151                 MP4_INVALID_DURATION, job->width, job->height,
152                 MP4_MPEG4_VIDEO_TYPE );
153         if (mux_data->track == MP4_INVALID_TRACK_ID)
154         {
155             hb_error("muxmp4.c: MP4AddVideoTrack failed!");
156             *job->die = 1;
157             return 0;
158         }
159
160         /* Tune track chunk duration */
161         if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
162         {
163             return 0;
164         }
165
166         /* VOL from FFmpeg or XviD */
167         if (!(MP4SetTrackESConfiguration( m->file, mux_data->track,
168                 job->config.mpeg4.bytes, job->config.mpeg4.length )))
169         {
170             hb_error("muxmp4.c: MP4SetTrackESConfiguration failed!");
171             *job->die = 1;
172             return 0;
173         }
174     }
175
176     // COLR atom for color and gamma correction.
177     // Per the notes at:
178     //   http://developer.apple.com/quicktime/icefloe/dispatch019.html#colr
179     //   http://forum.doom9.org/showthread.php?t=133982#post1090068
180     // the user can set it from job->color_matrix, otherwise by default
181     // we say anything that's likely to be HD content is ITU BT.709 and
182     // DVD, SD TV & other content is ITU BT.601.  We look at the title height
183     // rather than the job height here to get uncropped input dimensions.
184     if( job->color_matrix == 1 )
185     {
186         // ITU BT.601 DVD or SD TV content
187         MP4AddColr(m->file, mux_data->track, 6, 1, 6);
188     }
189     else if( job->color_matrix == 2 )
190     {
191         // ITU BT.709 HD content
192         MP4AddColr(m->file, mux_data->track, 1, 1, 1);        
193     }
194     else if ( job->title->width >= 1280 || job->title->height >= 720 )
195     {
196         // we guess that 720p or above is ITU BT.709 HD content
197         MP4AddColr(m->file, mux_data->track, 1, 1, 1);
198     }
199     else
200     {
201         // ITU BT.601 DVD or SD TV content
202         MP4AddColr(m->file, mux_data->track, 6, 1, 6);
203     }
204
205     if( job->anamorphic.mode )
206     {
207         /* PASP atom for anamorphic video */
208         float width, height;
209
210         width  = job->anamorphic.par_width;
211
212         height = job->anamorphic.par_height;
213
214         MP4AddPixelAspectRatio(m->file, mux_data->track, (uint32_t)width, (uint32_t)height);
215
216         MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.width", job->width * (width / height));
217     }
218
219         /* add the audio tracks */
220     for( i = 0; i < hb_list_count( title->list_audio ); i++ )
221     {
222         audio = hb_list_item( title->list_audio, i );
223         mux_data = malloc( sizeof( hb_mux_data_t ) );
224         audio->priv.mux_data = mux_data;
225
226         if( audio->config.out.codec == HB_ACODEC_AC3 )
227         {
228             uint8_t fscod = 0;
229             uint8_t bsid = audio->config.in.version;
230             uint8_t bsmod = audio->config.in.mode;
231             uint8_t acmod = audio->config.flags.ac3 & 0x7;
232             uint8_t lfeon = (audio->config.flags.ac3 & A52_LFE) ? 1 : 0;
233             uint8_t bit_rate_code = 0;
234
235             /*
236              * Rewrite AC3 information into correct format for dac3 atom
237              */
238             switch( audio->config.in.samplerate )
239             {
240             case 48000:
241                 fscod = 0;
242                 break;
243             case 44100:
244                 fscod = 1;
245                 break;
246             case 32000:
247                 fscod = 2;
248                 break;
249             default:
250                 /*
251                  * Error value, tells decoder to not decode this audio.
252                  */
253                 fscod = 3;
254                 break;
255             }
256
257             switch( audio->config.in.bitrate )
258             {
259             case 32000:
260                 bit_rate_code = 0;
261                 break;
262             case 40000:
263                 bit_rate_code = 1;
264                 break;
265             case 48000:
266                 bit_rate_code = 2;
267                 break;
268             case 56000:
269                 bit_rate_code = 3;
270                 break;
271             case 64000:
272                 bit_rate_code = 4;
273                 break;
274             case 80000:
275                 bit_rate_code = 5;
276                 break;
277             case 96000:
278                 bit_rate_code = 6;
279                 break;
280             case 112000:
281                 bit_rate_code = 7;
282                 break;
283             case 128000:
284                 bit_rate_code = 8;
285                 break;
286             case 160000:
287                 bit_rate_code = 9;
288                 break;
289             case 192000:
290                 bit_rate_code = 10;
291                 break;
292             case 224000:
293                 bit_rate_code = 11;
294                 break;
295             case 256000:
296                 bit_rate_code = 12;
297                 break;
298             case 320000:
299                 bit_rate_code = 13;
300                 break;
301             case 384000:
302                 bit_rate_code = 14;
303                 break;
304             case 448000:
305                 bit_rate_code = 15;
306                 break;
307             case 512000:
308                 bit_rate_code = 16;
309                 break;
310             case 576000:
311                 bit_rate_code = 17;
312                 break;
313             case 640000:
314                 bit_rate_code = 18;
315                 break;
316             default:
317                 hb_error("Unknown AC3 bitrate");
318                 bit_rate_code = 0;
319                 break;
320             }
321
322             mux_data->track = MP4AddAC3AudioTrack(
323                 m->file,
324                 audio->config.out.samplerate, 
325                 fscod,
326                 bsid,
327                 bsmod,
328                 acmod,
329                 lfeon,
330                 bit_rate_code);
331
332             /* Tune track chunk duration */
333             MP4TuneTrackDurationPerChunk( m, mux_data->track );
334
335             if (audio->config.out.name == NULL) {
336                 MP4SetTrackBytesProperty(
337                     m->file, mux_data->track,
338                     "udta.name.value",
339                     (const uint8_t*)"Surround", strlen("Surround"));
340             }
341             else {
342                 MP4SetTrackBytesProperty(
343                     m->file, mux_data->track,
344                     "udta.name.value",
345                     (const uint8_t*)(audio->config.out.name),
346                     strlen(audio->config.out.name));
347             }
348         } else {
349             mux_data->track = MP4AddAudioTrack(
350                 m->file,
351                 audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
352
353             /* Tune track chunk duration */
354             MP4TuneTrackDurationPerChunk( m, mux_data->track );
355
356             if (audio->config.out.name == NULL) {
357                 MP4SetTrackBytesProperty(
358                     m->file, mux_data->track,
359                     "udta.name.value",
360                     (const uint8_t*)"Stereo", strlen("Stereo"));
361             }
362             else {
363                 MP4SetTrackBytesProperty(
364                     m->file, mux_data->track,
365                     "udta.name.value",
366                     (const uint8_t*)(audio->config.out.name),
367                     strlen(audio->config.out.name));
368             }
369
370             MP4SetAudioProfileLevel( m->file, 0x0F );
371             MP4SetTrackESConfiguration(
372                 m->file, mux_data->track,
373                 audio->priv.config.aac.bytes, audio->priv.config.aac.length );
374
375             /* Set the correct number of channels for this track */
376              MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (uint16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown));
377         }
378
379         /* Set the language for this track */
380         MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2);
381
382         if( hb_list_count( title->list_audio ) > 1 )
383         {
384             /* Set the audio track alternate group */
385             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1);
386         }
387
388         if (i == 0) {
389             /* Enable the first audio track */
390             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
391         }
392         else
393             /* Disable the other audio tracks so QuickTime doesn't play
394                them all at once. */
395         {
396             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
397             hb_deep_log( 2, "muxmp4: disabled extra audio track %u", MP4FindTrackIndex( m->file, mux_data->track ));
398         }
399
400     }
401
402     if (job->chapter_markers)
403     {
404         /* add a text track for the chapters. We add the 'chap' atom to track
405            one which is usually the video track & should never be disabled.
406            The Quicktime spec says it doesn't matter which media track the
407            chap atom is on but it has to be an enabled track. */
408         MP4TrackId textTrack;
409         textTrack = MP4AddChapterTextTrack(m->file, 1, 0);
410
411         m->chapter_track = textTrack;
412         m->chapter_duration = 0;
413         m->current_chapter = job->chapter_start;
414     }
415
416     /* Add encoded-by metadata listing version and build date */
417     char *tool_string;
418     tool_string = (char *)malloc(80);
419     snprintf( tool_string, 80, "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD);
420
421     /* allocate,fetch,populate,store,free tags structure */
422     const MP4Tags* tags;
423     tags = MP4TagsAlloc();
424     MP4TagsFetch( tags, m->file );
425     MP4TagsSetEncodingTool( tags, tool_string );
426     MP4TagsStore( tags, m->file );
427     MP4TagsFree( tags );
428
429     free(tool_string);
430
431     return 0;
432 }
433
434 static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
435                    hb_buffer_t * buf )
436 {
437     hb_job_t * job = m->job;
438     int64_t duration;
439     int64_t offset = 0;
440     int i;
441
442     if( mux_data == job->mux_data )
443     {
444         /* Video */
445
446         // if there are b-frames compute the render offset
447         // (we'll need it for both the video frame & the chapter track)
448         if ( m->init_delay )
449         {
450             offset = buf->start + m->init_delay - m->sum_dur;
451             if ( offset < 0 )
452             {
453                 hb_log("MP4Mux: illegal render offset %lld, start %lld,"
454                        "stop %lld, sum_dur %lld",
455                        offset, buf->start, buf->stop, m->sum_dur );
456                 offset = 0;
457             }
458         }
459
460         /* Add the sample before the new frame.
461            It is important that this be calculated prior to the duration
462            of the new video sample, as we want to sync to right after it.
463            (This is because of how durations for text tracks work in QT) */
464         if( job->chapter_markers && buf->new_chap )
465         {    
466             hb_chapter_t *chapter = NULL;
467
468             // this chapter is postioned by writing out the previous chapter.
469             // the duration of the previous chapter is the duration up to but
470             // not including the current frame minus the duration of all
471             // chapters up to the previous.
472             // The initial and final chapters can be very short (a second or
473             // less) since they're not really chapters but just a placeholder to
474             // insert a cell command. We don't write chapters shorter than 1.5 sec.
475             duration = m->sum_dur - m->chapter_duration + offset;
476             if ( duration >= (90000*3)/2 )
477             {
478                 chapter = hb_list_item( m->job->title->list_chapter,
479                                         buf->new_chap - 2 );
480
481                 MP4AddChapter( m->file,
482                                m->chapter_track,
483                                duration,
484                                (chapter != NULL) ? chapter->title : NULL);
485
486                 m->current_chapter = buf->new_chap;
487                 m->chapter_duration += duration;
488             }
489         }
490
491         // We're getting the frames in decode order but the timestamps are
492         // for presentation so we have to use durations and effectively
493         // compute a DTS.
494         duration = buf->stop - buf->start;
495         if ( duration <= 0 )
496         {
497             /* We got an illegal mp4/h264 duration. This shouldn't
498                be possible and usually indicates a bug in the upstream code.
499                Complain in the hope that someone will go find the bug but
500                try to fix the error so that the file will still be playable. */
501             hb_log("MP4Mux: illegal duration %lld, start %lld,"
502                    "stop %lld, sum_dur %lld",
503                    duration, buf->start, buf->stop, m->sum_dur );
504             /* we don't know when the next frame starts so we can't pick a
505                valid duration for this one. we pick something "short"
506                (roughly 1/3 of an NTSC frame time) to take time from
507                the next frame. */
508             duration = 1000;
509         }
510         m->sum_dur += duration;
511     }
512     else
513     {
514         /* Audio */
515         duration = MP4_INVALID_DURATION;
516     }
517
518     /* Here's where the sample actually gets muxed. */
519     if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
520     {
521         /* Compute dependency flags.
522          *
523          * This mechanism is (optionally) used by media players such as QuickTime
524          * to offer better scrubbing performance. The most influential bits are
525          * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
526          *
527          * Other bits are possible but no example media using such bits have been
528          * found.
529          *
530          * It is acceptable to supply 0-bits for any samples which characteristics
531          * cannot be positively guaranteed.
532          */
533         int sync = 0;
534         uint32_t dflags = 0;
535
536         /* encoding layer signals if frame is referenced by other frames */
537         if( buf->flags & HB_FRAME_REF )
538             dflags |= MP4_SDT_HAS_DEPENDENTS;
539         else
540             dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
541
542         switch( buf->frametype )
543         {
544             case HB_FRAME_IDR:
545                 sync = 1;
546                 break;
547             case HB_FRAME_I:
548                 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
549                 break;
550             case HB_FRAME_P:
551                 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
552                 break;
553             case HB_FRAME_BREF:
554             case HB_FRAME_B:
555             default:
556                 break; /* nothing to mark */
557         }
558
559         if( !MP4WriteSampleDependency( m->file,
560                                        mux_data->track,
561                                        buf->data,
562                                        buf->size,
563                                        duration,
564                                        offset,
565                                        sync,
566                                        dflags ))
567         {
568             hb_error("Failed to write to output file, disk full?");
569             *job->die = 1;
570         }
571     }
572     else
573     {
574         if( !MP4WriteSample( m->file,
575                              mux_data->track,
576                              buf->data,
577                              buf->size,
578                              duration,
579                              offset,
580                              ( buf->frametype & HB_FRAME_KEY ) != 0 ))
581         {
582             hb_error("Failed to write to output file, disk full?");
583             *job->die = 1;
584         }
585     }
586
587     for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
588     {
589         hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
590         
591         if( subtitle && subtitle->format == TEXTSUB && 
592             subtitle->dest == PASSTHRUSUB )
593         {
594             /*
595              * Should be adding this one if the timestamp is right.
596              */
597             hb_buffer_t *sub;
598
599             while( ( sub = hb_fifo_see( subtitle->fifo_out )) != NULL )
600             {
601                 if( sub->size == 0 )
602                 {
603                     /*
604                      * EOF 
605                      */ 
606                     hb_log("MuxMP4: Text Sub: EOF");
607                     sub = hb_fifo_get( subtitle->fifo_out );
608                     hb_buffer_close( &sub );
609                 } else {
610                     if( sub->start < buf->start ) {
611                         sub = hb_fifo_get( subtitle->fifo_out );
612                         hb_log("MuxMP4: Text Sub:%lld: %s", sub->start, sub->data);
613                         hb_buffer_close( &sub );
614                     } else {
615                         /*
616                          * Not time yet
617                          */
618                         break;
619                     }
620                 }
621             }
622         }
623     }
624
625     return 0;
626 }
627
628 static int MP4End( hb_mux_object_t * m )
629 {
630     hb_job_t   * job   = m->job;
631     hb_title_t * title = job->title;
632
633     /* Write our final chapter marker */
634     if( m->job->chapter_markers )
635     {
636         hb_chapter_t *chapter = NULL;
637         int64_t duration = m->sum_dur - m->chapter_duration;
638         /* The final chapter can have a very short duration - if it's less
639          * than 1.5 seconds just skip it. */
640         if ( duration >= (90000*3)/2 )
641         {
642
643             chapter = hb_list_item( m->job->title->list_chapter,
644                                     m->current_chapter - 1 );
645
646             MP4AddChapter( m->file,
647                            m->chapter_track,
648                            duration,
649                            (chapter != NULL) ? chapter->title : NULL);
650         }
651     }
652
653     if (job->areBframes)
654     {
655            // Insert track edit to get A/V back in sync.  The edit amount is
656            // the init_delay.
657            int64_t edit_amt = m->init_delay;
658            MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
659                            MP4GetTrackDuration(m->file, 1), 0);
660             if ( m->job->chapter_markers )
661             {
662                 // apply same edit to chapter track to keep it in sync with video
663                 MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
664                                 edit_amt,
665                                 MP4GetTrackDuration(m->file, m->chapter_track), 0);
666             }
667      }
668
669     /*
670      * Write the MP4 iTunes metadata if we have any metadata
671      */
672     if( title->metadata )
673     {
674         hb_metadata_t *md = title->metadata;
675         const MP4Tags* tags;
676
677         hb_deep_log( 2, "Writing Metadata to output file...");
678
679         /* allocate tags structure */
680         tags = MP4TagsAlloc();
681         /* fetch data from MP4 file (in case it already has some data) */
682         MP4TagsFetch( tags, m->file );
683
684         /* populate */
685         MP4TagsSetName( tags, md->name );
686         MP4TagsSetArtist( tags, md->artist );
687         MP4TagsSetComposer( tags, md->composer );
688         MP4TagsSetComments( tags, md->comment );
689         MP4TagsSetReleaseDate( tags, md->release_date );
690         MP4TagsSetAlbum( tags, md->album );
691         MP4TagsSetGenre( tags, md->genre );
692
693         if( md->coverart )
694         {
695             MP4TagArtwork art;
696             art.data = md->coverart;
697             art.size = md->coverart_size;
698             art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
699             MP4TagsAddArtwork( tags, &art );
700         }
701
702         /* push data to MP4 file */
703         MP4TagsStore( tags, m->file );
704         /* free memory associated with structure */
705         MP4TagsFree( tags );
706     }
707
708     MP4Close( m->file );
709
710     if ( job->mp4_optimize )
711     {
712         hb_log( "muxmp4: optimizing file" );
713         char filename[1024]; memset( filename, 0, 1024 );
714         snprintf( filename, 1024, "%s.tmp", job->file );
715         MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
716         remove( job->file );
717         rename( filename, job->file );
718     }
719
720     return 0;
721 }
722
723 hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
724 {
725     hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );
726     m->init      = MP4Init;
727     m->mux       = MP4Mux;
728     m->end       = MP4End;
729     m->job       = job;
730     return m;
731 }
732