OSDN Git Service

H.264 B-frame muxing for MP4, including B-pyramids. The latter are QuickTime incompat...
[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.m0k.org/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 /* libmp4v2 header */
8 #include "mp4.h"
9
10 #include "hb.h"
11
12 void AddIPodUUID(MP4FileHandle, MP4TrackId);
13
14 /* B-frame muxing variables */
15 MP4SampleId thisSample = 0;
16 uint64_t initDelay;
17
18 struct hb_mux_object_s
19 {
20     HB_MUX_COMMON;
21
22     hb_job_t * job;
23
24     /* libmp4v2 handle */
25     MP4FileHandle file;
26
27     /* Cumulated durations so far, in timescale units (see MP4Mux) */
28     uint64_t sum_dur;
29         
30 };
31
32 struct hb_mux_data_s
33 {
34     MP4TrackId track;
35 };
36
37 /**********************************************************************
38  * MP4Init
39  **********************************************************************
40  * Allocates hb_mux_data_t structures, create file and write headers
41  *********************************************************************/
42 static int MP4Init( hb_mux_object_t * m )
43 {
44     hb_job_t   * job   = m->job;
45     hb_title_t * title = job->title;
46     
47     hb_audio_t    * audio;
48     hb_mux_data_t * mux_data;
49     int i;
50     u_int16_t language_code;
51
52     /* Create an empty mp4 file */
53     m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
54
55     /* Video track */
56     mux_data      = malloc( sizeof( hb_mux_data_t ) );
57     job->mux_data = mux_data;
58
59     /* When using the standard 90000 timescale, QuickTime tends to have
60        synchronization issues (audio not playing at the correct speed).
61        To workaround this, we use the audio samplerate as the
62        timescale */
63     MP4SetTimeScale( m->file, job->arate );
64
65     if( job->vcodec == HB_VCODEC_X264 )
66     {
67         /* Stolen from mp4creator */
68         MP4SetVideoProfileLevel( m->file, 0x7F );
69
70                 if (job->areBframes >= 1)
71                 {
72                         hb_log("muxmp4: Adjusting duration for B-frames");
73                     mux_data->track = MP4AddH264VideoTrack( m->file, job->arate,
74                             MP4_INVALID_DURATION+1, job->width, job->height,
75                             job->config.h264.sps[1], /* AVCProfileIndication */
76                             job->config.h264.sps[2], /* profile_compat */
77                             job->config.h264.sps[3], /* AVCLevelIndication */
78                             3 );      /* 4 bytes length before each NAL unit */                 
79                 }
80                 else
81                 {
82                         hb_log("muxmp4: Using default duration as there are no B-frames");
83                 mux_data->track = MP4AddH264VideoTrack( m->file, job->arate,
84                         MP4_INVALID_DURATION, job->width, job->height,
85                         job->config.h264.sps[1], /* AVCProfileIndication */
86                         job->config.h264.sps[2], /* profile_compat */
87                         job->config.h264.sps[3], /* AVCLevelIndication */
88                         3 );      /* 4 bytes length before each NAL unit */
89                 }
90
91         MP4AddH264SequenceParameterSet( m->file, mux_data->track,
92                 job->config.h264.sps, job->config.h264.sps_length );
93         MP4AddH264PictureParameterSet( m->file, mux_data->track,
94                 job->config.h264.pps, job->config.h264.pps_length );
95
96                 if( job->h264_level == 30)
97                 {
98                         hb_log("About to add iPod atom");
99                         AddIPodUUID(m->file, mux_data->track);
100                 }
101
102     }
103     else /* FFmpeg or XviD */
104     {
105         MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
106         mux_data->track = MP4AddVideoTrack( m->file, job->arate,
107                 MP4_INVALID_DURATION, job->width, job->height,
108                 MP4_MPEG4_VIDEO_TYPE );
109
110         /* VOL from FFmpeg or XviD */
111         MP4SetTrackESConfiguration( m->file, mux_data->track,
112                 job->config.mpeg4.bytes, job->config.mpeg4.length );
113     }
114
115         /* apply the anamorphic transformation matrix if needed */
116
117         if( job->pixel_ratio ) {
118
119                 uint8_t* val;
120                 uint8_t nval[38];
121                 uint32_t *ptr32 = (uint32_t*) (nval + 2);
122                 uint32_t size;
123
124                 MP4GetBytesProperty(m->file, "moov.trak.tkhd.reserved3", &val, &size);
125
126                 if (size == 38) {
127
128                         memcpy(nval, val, size);
129
130                         float width, height;
131                         float widthRatio;
132                         width = job->pixel_aspect_width;
133                         height = job->pixel_aspect_height;
134                         widthRatio = (width / height) * 0x10000;
135
136                         uint32_t widthRatioInt;
137                         widthRatioInt = (uint32_t)widthRatio;
138
139 #ifdef WORDS_BIGENDIAN
140                         ptr32[0] = widthRatioInt;
141 #else
142                         /* we need to switch the endianness, as the file format expects big endian */
143                         ptr32[0] = ((widthRatioInt & 0x000000FF) << 24) + ((widthRatioInt & 0x0000FF00) << 8) + ((widthRatioInt & 0x00FF0000) >> 8) + ((widthRatioInt & 0xFF000000) >> 24);
144 #endif
145
146                         if(!MP4SetBytesProperty(m->file, "moov.trak.tkhd.reserved3", nval, size)) {
147                                 hb_log("Problem setting transform matrix");
148                         }
149                         
150                 }
151
152         }
153
154         /* end of transformation matrix */
155
156         /* firstAudioTrack will be used to reference the first audio track when we add a chapter track */
157         MP4TrackId firstAudioTrack;
158
159         /* add the audio tracks */
160     for( i = 0; i < hb_list_count( title->list_audio ); i++ )
161     {
162         audio = hb_list_item( title->list_audio, i );
163         mux_data = malloc( sizeof( hb_mux_data_t ) );
164         audio->mux_data = mux_data;
165
166         mux_data->track = MP4AddAudioTrack( m->file,
167                 job->arate, 1024, MP4_MPEG4_AUDIO_TYPE );
168         MP4SetAudioProfileLevel( m->file, 0x0F );
169         MP4SetTrackESConfiguration( m->file, mux_data->track,
170                 audio->config.aac.bytes, audio->config.aac.length );
171                 
172         /* Set the language for this track */
173         /* The language is stored as 5-bit text - 0x60 */
174         language_code = audio->iso639_2[0] - 0x60;   language_code <<= 5;
175         language_code |= audio->iso639_2[1] - 0x60;  language_code <<= 5;
176         language_code |= audio->iso639_2[2] - 0x60;
177         MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.mdhd.language", language_code);
178                                 
179                 /* store a reference to the first audio track,
180                 so we can use it to feed the chapter text track's sample rate */
181                 if (i == 0) {
182                         firstAudioTrack = mux_data->track;
183                 }
184                 
185     }
186
187         if (job->chapter_markers) {
188
189                 /* add a text track for the chapters */
190                 MP4TrackId textTrack;
191
192                 textTrack = MP4AddChapterTextTrack(m->file, firstAudioTrack);
193
194                 /* write the chapter markers for each selected chapter */
195                 char markerBuf[13];
196                 hb_chapter_t  * chapter;
197                 MP4Duration chapterDuration;
198                 float fOrigDuration, fTimescale;
199                 float fTSDuration;
200
201                 for( i = job->chapter_start - 1; i <= job->chapter_end - 1; i++ )
202                 {
203                         chapter = hb_list_item( title->list_chapter, i );
204
205                         fOrigDuration = chapter->duration;
206                         fTimescale = job->arate;
207                         fTSDuration = (fOrigDuration / 90000) * fTimescale;
208                         chapterDuration = (MP4Duration)fTSDuration;
209                         
210                         sprintf(markerBuf, "  Chapter %03i", i + 1);
211                         markerBuf[0] = 0;
212                         markerBuf[1] = 11; // "Chapter xxx"
213                         MP4WriteSample(m->file, textTrack, (u_int8_t*)markerBuf, 13, chapterDuration, 0, true);
214
215                 }
216                 
217         }
218         
219     return 0;
220 }
221
222 static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
223                    hb_buffer_t * buf )
224 {
225     hb_job_t * job = m->job;
226
227     uint64_t duration;
228
229     if( mux_data == job->mux_data )
230     {
231         /* Video */
232         /* Because we use the audio samplerate as the timescale,
233            we have to use potentially variable durations so the video
234            doesn't go out of sync */
235         duration    = ( buf->stop * job->arate / 90000 ) - m->sum_dur;
236         m->sum_dur += duration;
237     }
238     else
239     {
240         /* Audio */
241         duration = MP4_INVALID_DURATION;
242     }
243
244     /* If for some reason the first frame muxmp4 gets isn't a key-frame,
245        drop frames until we get one. (Yes, very bad. Quick and dirty.)
246        This is for QuickTime--when it sees a non-IDR frame first, it
247        displays a white box instead of video until the second GOP.
248        Also, you've got to save the skipped duration to keep from
249        throwing off the offset values. */
250     if((mux_data->track == 1) && (thisSample == 0) && (buf->key != 1))
251     {
252             initDelay +=duration;
253             return 0;
254     }
255     /* When we do get the first keyframe, use its duration as the
256        initial delay added to the frame order offset for b-frames.
257        Because of b-pyramid, double this duration when there are
258        b-pyramids, as denoted by job->areBframes equalling 2. */
259     if ((mux_data->track == 1) && (thisSample == 0) && (buf->key == 1))
260     {
261         initDelay += duration * job->areBframes;
262         thisSample++;
263     }
264
265     /* Here's where the sample actually gets muxed. 
266        If it's an audio sample, don't offset the sample's playback.
267        If it's a video sample and there are no b-frames, ditto.
268        If there are b-frames, offset by the initDelay plus the
269        difference between the presentation time stamp x264 gives
270        and the decoding time stamp from the buffer data. */
271        MP4WriteSample( m->file, mux_data->track, buf->data, buf->size,
272             duration, ((mux_data->track != 1) || (job->areBframes==0)) ? 0 : ( initDelay + ((buf->encodedPTS - buf->start) * job->arate / 90000)),
273             (buf->key == 1) );
274                                 
275     return 0;
276 }
277
278 static int MP4End( hb_mux_object_t * m )
279 {
280 #if 0
281     hb_job_t * job = m->job;
282     char filename[1024]; memset( filename, 0, 1024 );
283 #endif
284
285     MP4Close( m->file );
286
287 #if 0
288     hb_log( "muxmp4: optimizing file" );
289     snprintf( filename, 1024, "%s.tmp", job->file );
290     MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
291     remove( job->file );
292     rename( filename, job->file );
293 #endif
294
295     return 0;
296 }
297
298 hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
299 {
300     hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );
301     m->init      = MP4Init;
302     m->mux       = MP4Mux;
303     m->end       = MP4End;
304     m->job       = job;
305     return m;
306 }
307