OSDN Git Service

Soft Subs Part 2: Auto-detect CC during scan, add CC to subtitle list in title, if...
[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
441     if( mux_data == job->mux_data )
442     {
443         /* Video */
444
445         // if there are b-frames compute the render offset
446         // (we'll need it for both the video frame & the chapter track)
447         if ( m->init_delay )
448         {
449             offset = buf->start + m->init_delay - m->sum_dur;
450             if ( offset < 0 )
451             {
452                 hb_log("MP4Mux: illegal render offset %lld, start %lld,"
453                        "stop %lld, sum_dur %lld",
454                        offset, buf->start, buf->stop, m->sum_dur );
455                 offset = 0;
456             }
457         }
458
459         /* Add the sample before the new frame.
460            It is important that this be calculated prior to the duration
461            of the new video sample, as we want to sync to right after it.
462            (This is because of how durations for text tracks work in QT) */
463         if( job->chapter_markers && buf->new_chap )
464         {    
465             hb_chapter_t *chapter = NULL;
466
467             // this chapter is postioned by writing out the previous chapter.
468             // the duration of the previous chapter is the duration up to but
469             // not including the current frame minus the duration of all
470             // chapters up to the previous.
471             // The initial and final chapters can be very short (a second or
472             // less) since they're not really chapters but just a placeholder to
473             // insert a cell command. We don't write chapters shorter than 1.5 sec.
474             duration = m->sum_dur - m->chapter_duration + offset;
475             if ( duration >= (90000*3)/2 )
476             {
477                 chapter = hb_list_item( m->job->title->list_chapter,
478                                         buf->new_chap - 2 );
479
480                 MP4AddChapter( m->file,
481                                m->chapter_track,
482                                duration,
483                                (chapter != NULL) ? chapter->title : NULL);
484
485                 m->current_chapter = buf->new_chap;
486                 m->chapter_duration += duration;
487             }
488         }
489
490         // We're getting the frames in decode order but the timestamps are
491         // for presentation so we have to use durations and effectively
492         // compute a DTS.
493         duration = buf->stop - buf->start;
494         if ( duration <= 0 )
495         {
496             /* We got an illegal mp4/h264 duration. This shouldn't
497                be possible and usually indicates a bug in the upstream code.
498                Complain in the hope that someone will go find the bug but
499                try to fix the error so that the file will still be playable. */
500             hb_log("MP4Mux: illegal duration %lld, start %lld,"
501                    "stop %lld, sum_dur %lld",
502                    duration, buf->start, buf->stop, m->sum_dur );
503             /* we don't know when the next frame starts so we can't pick a
504                valid duration for this one. we pick something "short"
505                (roughly 1/3 of an NTSC frame time) to take time from
506                the next frame. */
507             duration = 1000;
508         }
509         m->sum_dur += duration;
510     }
511     else
512     {
513         /* Audio */
514         duration = MP4_INVALID_DURATION;
515     }
516
517     /* Here's where the sample actually gets muxed. */
518     if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
519     {
520         /* Compute dependency flags.
521          *
522          * This mechanism is (optionally) used by media players such as QuickTime
523          * to offer better scrubbing performance. The most influential bits are
524          * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
525          *
526          * Other bits are possible but no example media using such bits have been
527          * found.
528          *
529          * It is acceptable to supply 0-bits for any samples which characteristics
530          * cannot be positively guaranteed.
531          */
532         int sync = 0;
533         uint32_t dflags = 0;
534
535         /* encoding layer signals if frame is referenced by other frames */
536         if( buf->flags & HB_FRAME_REF )
537             dflags |= MP4_SDT_HAS_DEPENDENTS;
538         else
539             dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
540
541         switch( buf->frametype )
542         {
543             case HB_FRAME_IDR:
544                 sync = 1;
545                 break;
546             case HB_FRAME_I:
547                 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
548                 break;
549             case HB_FRAME_P:
550                 dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
551                 break;
552             case HB_FRAME_BREF:
553             case HB_FRAME_B:
554             default:
555                 break; /* nothing to mark */
556         }
557
558         if( !MP4WriteSampleDependency( m->file,
559                                        mux_data->track,
560                                        buf->data,
561                                        buf->size,
562                                        duration,
563                                        offset,
564                                        sync,
565                                        dflags ))
566         {
567             hb_error("Failed to write to output file, disk full?");
568             *job->die = 1;
569         }
570     }
571     else
572     {
573         if( !MP4WriteSample( m->file,
574                              mux_data->track,
575                              buf->data,
576                              buf->size,
577                              duration,
578                              offset,
579                              ( buf->frametype & HB_FRAME_KEY ) != 0 ))
580         {
581             hb_error("Failed to write to output file, disk full?");
582             *job->die = 1;
583         }
584     }
585
586     return 0;
587 }
588
589 static int MP4End( hb_mux_object_t * m )
590 {
591     hb_job_t   * job   = m->job;
592     hb_title_t * title = job->title;
593     int i;
594
595     /* Write our final chapter marker */
596     if( m->job->chapter_markers )
597     {
598         hb_chapter_t *chapter = NULL;
599         int64_t duration = m->sum_dur - m->chapter_duration;
600         /* The final chapter can have a very short duration - if it's less
601          * than 1.5 seconds just skip it. */
602         if ( duration >= (90000*3)/2 )
603         {
604
605             chapter = hb_list_item( m->job->title->list_chapter,
606                                     m->current_chapter - 1 );
607
608             MP4AddChapter( m->file,
609                            m->chapter_track,
610                            duration,
611                            (chapter != NULL) ? chapter->title : NULL);
612         }
613     }
614
615     if (job->areBframes)
616     {
617            // Insert track edit to get A/V back in sync.  The edit amount is
618            // the init_delay.
619            int64_t edit_amt = m->init_delay;
620            MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
621                            MP4GetTrackDuration(m->file, 1), 0);
622             if ( m->job->chapter_markers )
623             {
624                 // apply same edit to chapter track to keep it in sync with video
625                 MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
626                                 edit_amt,
627                                 MP4GetTrackDuration(m->file, m->chapter_track), 0);
628             }
629      }
630
631     /*
632      * Write the MP4 iTunes metadata if we have any metadata
633      */
634     if( title->metadata )
635     {
636         hb_metadata_t *md = title->metadata;
637         const MP4Tags* tags;
638
639         hb_deep_log( 2, "Writing Metadata to output file...");
640
641         /* allocate tags structure */
642         tags = MP4TagsAlloc();
643         /* fetch data from MP4 file (in case it already has some data) */
644         MP4TagsFetch( tags, m->file );
645
646         /* populate */
647         MP4TagsSetName( tags, md->name );
648         MP4TagsSetArtist( tags, md->artist );
649         MP4TagsSetComposer( tags, md->composer );
650         MP4TagsSetComments( tags, md->comment );
651         MP4TagsSetReleaseDate( tags, md->release_date );
652         MP4TagsSetAlbum( tags, md->album );
653         MP4TagsSetGenre( tags, md->genre );
654
655         if( md->coverart )
656         {
657             MP4TagArtwork art;
658             art.data = md->coverart;
659             art.size = md->coverart_size;
660             art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
661             MP4TagsAddArtwork( tags, &art );
662         }
663
664         /* push data to MP4 file */
665         MP4TagsStore( tags, m->file );
666         /* free memory associated with structure */
667         MP4TagsFree( tags );
668     }
669
670     /*
671      * Display any text subs.
672      *
673      * This is placeholder code, what needs to happen is that we need to
674      * convert these PTS (which are pre-sync ones) into HB timestamps,
675      * I guess sync should do that?
676      *
677      * And then this needs to move into the Mux code above and insert
678      * subtitle samples into the MP4 at the correct times.
679      */
680     for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
681     {
682         hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
683         
684         if( subtitle && subtitle->format == TEXTSUB && 
685             subtitle->dest == PASSTHRUSUB )
686         {
687             /*
688              * Should be adding this one if the timestamp is right.
689              */
690             hb_buffer_t *buffer;
691
692             while( (buffer = hb_fifo_get( subtitle->fifo_raw )) != NULL )
693             {
694                 hb_log("MuxMP4: Text Sub: %s", buffer->data);
695
696                 hb_buffer_close( &buffer );
697             }
698         }
699     }
700
701     MP4Close( m->file );
702
703     if ( job->mp4_optimize )
704     {
705         hb_log( "muxmp4: optimizing file" );
706         char filename[1024]; memset( filename, 0, 1024 );
707         snprintf( filename, 1024, "%s.tmp", job->file );
708         MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
709         remove( job->file );
710         rename( filename, job->file );
711     }
712
713     return 0;
714 }
715
716 hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
717 {
718     hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );
719     m->init      = MP4Init;
720     m->mux       = MP4Mux;
721     m->end       = MP4End;
722     m->job       = job;
723     return m;
724 }
725