Commit | Line | Data |
---|---|---|
1cb5f7fd MN |
1 | /* |
2 | * MOV, 3GP, MP4 encoder. | |
3 | * Copyright (c) 2003 Thomas Raivio. | |
69dde1ad | 4 | * Copyright (c) 2004 Gildas Bazin <gbazin at videolan dot org>. |
1cb5f7fd MN |
5 | * |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with this library; if not, write to the Free Software | |
5509bffa | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1cb5f7fd MN |
19 | */ |
20 | #include "avformat.h" | |
f578f938 | 21 | #include "avi.h" |
1cb5f7fd | 22 | #include "avio.h" |
e60b4ced | 23 | #include "mov.h" |
1cb5f7fd | 24 | |
6e6d6dc0 MN |
25 | #undef NDEBUG |
26 | #include <assert.h> | |
27 | ||
1cb5f7fd MN |
28 | #define MOV_INDEX_CLUSTER_SIZE 16384 |
29 | #define globalTimescale 1000 | |
30 | ||
69dde1ad GB |
31 | #define MODE_MP4 0 |
32 | #define MODE_MOV 1 | |
33 | #define MODE_3GP 2 | |
115329f1 | 34 | #define MODE_PSP 3 // example working PSP command line: |
8af18154 | 35 | // ffmpeg -i testinput.avi -f psp -r 14.985 -s 320x240 -b 768 -ar 24000 -ab 32 M4V00001.MP4 |
8536ab89 | 36 | #define MODE_3G2 4 |
69dde1ad | 37 | |
1cb5f7fd | 38 | typedef struct MOVIentry { |
b29af723 MN |
39 | unsigned int flags, size; |
40 | uint64_t pos; | |
e45ccf79 | 41 | unsigned int samplesInChunk; |
f578f938 | 42 | char key_frame; |
1cb5f7fd | 43 | unsigned int entries; |
b4712e3c | 44 | int64_t cts; |
1cb5f7fd MN |
45 | } MOVIentry; |
46 | ||
47 | typedef struct MOVIndex { | |
69dde1ad | 48 | int mode; |
1cb5f7fd | 49 | int entry; |
b29af723 | 50 | uint64_t mdat_size; |
1cb5f7fd MN |
51 | int ents_allocated; |
52 | long timescale; | |
53 | long time; | |
f578f938 | 54 | long trackDuration; |
e45ccf79 GB |
55 | long sampleCount; |
56 | long sampleDuration; | |
f578f938 | 57 | int hasKeyframes; |
b4712e3c | 58 | int hasBframes; |
ab561df9 | 59 | int language; |
1cb5f7fd MN |
60 | int trackID; |
61 | AVCodecContext *enc; | |
62 | ||
63 | int vosLen; | |
6e6d6dc0 | 64 | uint8_t *vosData; |
1cb5f7fd MN |
65 | MOVIentry** cluster; |
66 | } MOVTrack; | |
67 | ||
8af18154 | 68 | typedef struct MOVContext { |
69dde1ad | 69 | int mode; |
1cb5f7fd MN |
70 | long time; |
71 | int nb_streams; | |
f578f938 TR |
72 | int mdat_written; |
73 | offset_t mdat_pos; | |
1cb5f7fd MN |
74 | long timescale; |
75 | MOVTrack tracks[MAX_STREAMS]; | |
76 | } MOVContext; | |
77 | ||
e45ccf79 GB |
78 | static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack* track); |
79 | ||
ab561df9 FR |
80 | /* output language code from iso639 language name */ |
81 | extern int ff_mov_iso639_to_lang(const char *lang, int mp4); | |
82 | ||
6e6d6dc0 | 83 | //FIXME supprt 64bit varaint with wide placeholders |
b29af723 | 84 | static offset_t updateSize (ByteIOContext *pb, offset_t pos) |
1cb5f7fd | 85 | { |
b29af723 | 86 | offset_t curpos = url_ftell(pb); |
1cb5f7fd | 87 | url_fseek(pb, pos, SEEK_SET); |
6e6d6dc0 | 88 | put_be32(pb, curpos - pos); /* rewrite size */ |
1cb5f7fd | 89 | url_fseek(pb, curpos, SEEK_SET); |
6e6d6dc0 MN |
90 | |
91 | return curpos - pos; | |
1cb5f7fd MN |
92 | } |
93 | ||
e45ccf79 | 94 | /* Chunk offset atom */ |
6e6d6dc0 | 95 | static int mov_write_stco_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd MN |
96 | { |
97 | int i; | |
ed06cf6d | 98 | int mode64 = 0; // use 32 bit size variant if possible |
b29af723 | 99 | offset_t pos = url_ftell(pb); |
f578f938 | 100 | put_be32(pb, 0); /* size */ |
b29af723 MN |
101 | if (pos > UINT32_MAX) { |
102 | mode64 = 1; | |
103 | put_tag(pb, "co64"); | |
104 | } else | |
105 | put_tag(pb, "stco"); | |
1cb5f7fd MN |
106 | put_be32(pb, 0); /* version & flags */ |
107 | put_be32(pb, track->entry); /* entry count */ | |
108 | for (i=0; i<track->entry; i++) { | |
109 | int cl = i / MOV_INDEX_CLUSTER_SIZE; | |
110 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
b29af723 MN |
111 | if(mode64 == 1) |
112 | put_be64(pb, track->cluster[cl][id].pos); | |
113 | else | |
114 | put_be32(pb, track->cluster[cl][id].pos); | |
1cb5f7fd | 115 | } |
f578f938 | 116 | return updateSize (pb, pos); |
1cb5f7fd MN |
117 | } |
118 | ||
e45ccf79 | 119 | /* Sample size atom */ |
6e6d6dc0 | 120 | static int mov_write_stsz_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 121 | { |
f578f938 | 122 | int equalChunks = 1; |
e45ccf79 | 123 | int i, j, entries = 0, tst = -1, oldtst = -1; |
1cb5f7fd | 124 | |
b29af723 | 125 | offset_t pos = url_ftell(pb); |
f578f938 | 126 | put_be32(pb, 0); /* size */ |
1cb5f7fd MN |
127 | put_tag(pb, "stsz"); |
128 | put_be32(pb, 0); /* version & flags */ | |
129 | ||
f578f938 TR |
130 | for (i=0; i<track->entry; i++) { |
131 | int cl = i / MOV_INDEX_CLUSTER_SIZE; | |
132 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
e45ccf79 GB |
133 | tst = track->cluster[cl][id].size/track->cluster[cl][id].entries; |
134 | if(oldtst != -1 && tst != oldtst) { | |
135 | equalChunks = 0; | |
f578f938 TR |
136 | } |
137 | oldtst = tst; | |
e45ccf79 | 138 | entries += track->cluster[cl][id].entries; |
f578f938 | 139 | } |
e45ccf79 GB |
140 | if (equalChunks) { |
141 | int sSize = track->cluster[0][0].size/track->cluster[0][0].entries; | |
115329f1 | 142 | put_be32(pb, sSize); // sample size |
e45ccf79 | 143 | put_be32(pb, entries); // sample count |
1cb5f7fd | 144 | } |
f578f938 | 145 | else { |
115329f1 DB |
146 | put_be32(pb, 0); // sample size |
147 | put_be32(pb, entries); // sample count | |
f578f938 | 148 | for (i=0; i<track->entry; i++) { |
1cb5f7fd MN |
149 | int cl = i / MOV_INDEX_CLUSTER_SIZE; |
150 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
e45ccf79 GB |
151 | for ( j=0; j<track->cluster[cl][id].entries; j++) { |
152 | put_be32(pb, track->cluster[cl][id].size / | |
153 | track->cluster[cl][id].entries); | |
154 | } | |
1cb5f7fd MN |
155 | } |
156 | } | |
f578f938 | 157 | return updateSize (pb, pos); |
1cb5f7fd MN |
158 | } |
159 | ||
e45ccf79 | 160 | /* Sample to chunk atom */ |
6e6d6dc0 | 161 | static int mov_write_stsc_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 162 | { |
b29af723 MN |
163 | int index = 0, oldval = -1, i; |
164 | offset_t entryPos, curpos; | |
f578f938 | 165 | |
b29af723 | 166 | offset_t pos = url_ftell(pb); |
f578f938 | 167 | put_be32(pb, 0); /* size */ |
1cb5f7fd | 168 | put_tag(pb, "stsc"); |
115329f1 | 169 | put_be32(pb, 0); // version & flags |
f578f938 | 170 | entryPos = url_ftell(pb); |
115329f1 | 171 | put_be32(pb, track->entry); // entry count |
f578f938 TR |
172 | for (i=0; i<track->entry; i++) { |
173 | int cl = i / MOV_INDEX_CLUSTER_SIZE; | |
174 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
e45ccf79 | 175 | if(oldval != track->cluster[cl][id].samplesInChunk) |
f578f938 | 176 | { |
115329f1 | 177 | put_be32(pb, i+1); // first chunk |
e45ccf79 | 178 | put_be32(pb, track->cluster[cl][id].samplesInChunk); // samples per chunk |
115329f1 | 179 | put_be32(pb, 0x1); // sample description index |
e45ccf79 | 180 | oldval = track->cluster[cl][id].samplesInChunk; |
f578f938 | 181 | index++; |
1cb5f7fd MN |
182 | } |
183 | } | |
f578f938 TR |
184 | curpos = url_ftell(pb); |
185 | url_fseek(pb, entryPos, SEEK_SET); | |
115329f1 | 186 | put_be32(pb, index); // rewrite size |
f578f938 | 187 | url_fseek(pb, curpos, SEEK_SET); |
1cb5f7fd | 188 | |
f578f938 | 189 | return updateSize (pb, pos); |
1cb5f7fd MN |
190 | } |
191 | ||
e45ccf79 | 192 | /* Sync sample atom */ |
f578f938 | 193 | static int mov_write_stss_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 194 | { |
b29af723 MN |
195 | offset_t curpos, entryPos; |
196 | int i, index = 0; | |
197 | offset_t pos = url_ftell(pb); | |
115329f1 | 198 | put_be32(pb, 0); // size |
1cb5f7fd | 199 | put_tag(pb, "stss"); |
115329f1 | 200 | put_be32(pb, 0); // version & flags |
f578f938 | 201 | entryPos = url_ftell(pb); |
115329f1 | 202 | put_be32(pb, track->entry); // entry count |
f578f938 TR |
203 | for (i=0; i<track->entry; i++) { |
204 | int cl = i / MOV_INDEX_CLUSTER_SIZE; | |
205 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
206 | if(track->cluster[cl][id].key_frame == 1) { | |
207 | put_be32(pb, i+1); | |
208 | index++; | |
209 | } | |
210 | } | |
211 | curpos = url_ftell(pb); | |
212 | url_fseek(pb, entryPos, SEEK_SET); | |
115329f1 | 213 | put_be32(pb, index); // rewrite size |
f578f938 TR |
214 | url_fseek(pb, curpos, SEEK_SET); |
215 | return updateSize (pb, pos); | |
1cb5f7fd MN |
216 | } |
217 | ||
6e6d6dc0 | 218 | static int mov_write_damr_tag(ByteIOContext *pb) |
1cb5f7fd MN |
219 | { |
220 | put_be32(pb, 0x11); /* size */ | |
221 | put_tag(pb, "damr"); | |
222 | put_tag(pb, "FFMP"); | |
223 | put_byte(pb, 0); | |
f578f938 TR |
224 | |
225 | put_be16(pb, 0x80); /* Mode set (all modes for AMR_NB) */ | |
226 | put_be16(pb, 0xa); /* Mode change period (no restriction) */ | |
227 | //put_be16(pb, 0x81ff); /* Mode set (all modes for AMR_NB) */ | |
228 | //put_be16(pb, 1); /* Mode change period (no restriction) */ | |
1cb5f7fd MN |
229 | return 0x11; |
230 | } | |
231 | ||
69dde1ad GB |
232 | static int mov_write_wave_tag(ByteIOContext *pb, MOVTrack* track) |
233 | { | |
b29af723 | 234 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
235 | |
236 | put_be32(pb, 0); /* size */ | |
237 | put_tag(pb, "wave"); | |
238 | ||
239 | put_be32(pb, 12); /* size */ | |
240 | put_tag(pb, "frma"); | |
241 | put_tag(pb, "mp4a"); | |
242 | ||
243 | put_be32(pb, 12); /* size */ | |
244 | put_tag(pb, "mp4a"); | |
245 | put_be32(pb, 0); | |
246 | ||
247 | mov_write_esds_tag(pb, track); | |
248 | ||
249 | put_be32(pb, 12); /* size */ | |
250 | put_tag(pb, "srcq"); | |
251 | put_be32(pb, 0x40); | |
252 | ||
253 | put_be32(pb, 8); /* size */ | |
254 | put_be32(pb, 0); /* null tag */ | |
255 | ||
256 | return updateSize (pb, pos); | |
257 | } | |
258 | ||
0c1e0bab | 259 | static const CodecTag codec_movaudio_tags[] = { |
8cc7a34d AB |
260 | { CODEC_ID_PCM_MULAW, MKTAG('u', 'l', 'a', 'w') }, |
261 | { CODEC_ID_PCM_ALAW, MKTAG('a', 'l', 'a', 'w') }, | |
262 | { CODEC_ID_ADPCM_IMA_QT, MKTAG('i', 'm', 'a', '4') }, | |
263 | { CODEC_ID_MACE3, MKTAG('M', 'A', 'C', '3') }, | |
264 | { CODEC_ID_MACE6, MKTAG('M', 'A', 'C', '6') }, | |
265 | { CODEC_ID_AAC, MKTAG('m', 'p', '4', 'a') }, | |
266 | { CODEC_ID_AMR_NB, MKTAG('s', 'a', 'm', 'r') }, | |
11bec294 | 267 | { CODEC_ID_AMR_WB, MKTAG('s', 'a', 'w', 'b') }, |
8cc7a34d AB |
268 | { CODEC_ID_PCM_S16BE, MKTAG('t', 'w', 'o', 's') }, |
269 | { CODEC_ID_PCM_S16LE, MKTAG('s', 'o', 'w', 't') }, | |
270 | { CODEC_ID_MP3, MKTAG('.', 'm', 'p', '3') }, | |
8e321619 | 271 | { CODEC_ID_NONE, 0 }, |
8cc7a34d AB |
272 | }; |
273 | ||
f578f938 | 274 | static int mov_write_audio_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 275 | { |
b29af723 | 276 | offset_t pos = url_ftell(pb); |
ffdd57d4 | 277 | int tag; |
115329f1 | 278 | |
1cb5f7fd | 279 | put_be32(pb, 0); /* size */ |
f578f938 | 280 | |
caacd4de RG |
281 | tag = track->enc->codec_tag; |
282 | if (!tag) | |
8cc7a34d AB |
283 | tag = codec_get_tag(codec_movaudio_tags, track->enc->codec_id); |
284 | // if no mac fcc found, try with Microsoft tags | |
285 | if (!tag) | |
286 | { | |
bb270c08 | 287 | int tmp = codec_get_tag(codec_wav_tags, track->enc->codec_id); |
ffdd57d4 | 288 | tag = MKTAG('m', 's', ((tmp >> 8) & 0xff), (tmp & 0xff)); |
8cc7a34d | 289 | } |
ffdd57d4 | 290 | put_le32(pb, tag); // store it byteswapped |
f578f938 | 291 | |
1cb5f7fd MN |
292 | put_be32(pb, 0); /* Reserved */ |
293 | put_be16(pb, 0); /* Reserved */ | |
294 | put_be16(pb, 1); /* Data-reference index, XXX == 1 */ | |
69dde1ad | 295 | |
e45ccf79 | 296 | /* SoundDescription */ |
69dde1ad GB |
297 | if(track->mode == MODE_MOV && track->enc->codec_id == CODEC_ID_AAC) |
298 | put_be16(pb, 1); /* Version 1 */ | |
299 | else | |
300 | put_be16(pb, 0); /* Version 0 */ | |
e45ccf79 | 301 | put_be16(pb, 0); /* Revision level */ |
1cb5f7fd MN |
302 | put_be32(pb, 0); /* Reserved */ |
303 | ||
f578f938 TR |
304 | put_be16(pb, track->enc->channels); /* Number of channels */ |
305 | /* TODO: Currently hard-coded to 16-bit, there doesn't seem | |
e45ccf79 | 306 | to be a good way to get number of bits of audio */ |
1cb5f7fd | 307 | put_be16(pb, 0x10); /* Reserved */ |
ef19c7eb GB |
308 | |
309 | if(track->enc->codec_id == CODEC_ID_AAC || | |
310 | track->enc->codec_id == CODEC_ID_MP3) | |
311 | { | |
312 | put_be16(pb, 0xfffe); /* compression ID (vbr)*/ | |
313 | } | |
314 | else | |
315 | { | |
316 | put_be16(pb, 0); /* compression ID (= 0) */ | |
317 | } | |
9a4d9388 | 318 | put_be16(pb, 0); /* packet size (= 0) */ |
1cb5f7fd MN |
319 | put_be16(pb, track->timescale); /* Time scale */ |
320 | put_be16(pb, 0); /* Reserved */ | |
321 | ||
69dde1ad GB |
322 | if(track->mode == MODE_MOV && track->enc->codec_id == CODEC_ID_AAC) |
323 | { | |
324 | /* SoundDescription V1 extended info */ | |
325 | put_be32(pb, track->enc->frame_size); /* Samples per packet */ | |
326 | put_be32(pb, 1536); /* Bytes per packet */ | |
327 | put_be32(pb, 2); /* Bytes per frame */ | |
328 | put_be32(pb, 2); /* Bytes per sample */ | |
329 | } | |
330 | ||
331 | if(track->enc->codec_id == CODEC_ID_AAC) { | |
332 | if( track->mode == MODE_MOV ) mov_write_wave_tag(pb, track); | |
333 | else mov_write_esds_tag(pb, track); | |
334 | } | |
f578f938 TR |
335 | if(track->enc->codec_id == CODEC_ID_AMR_NB) |
336 | mov_write_damr_tag(pb); | |
6e6d6dc0 | 337 | return updateSize (pb, pos); |
1cb5f7fd MN |
338 | } |
339 | ||
6e6d6dc0 | 340 | static int mov_write_d263_tag(ByteIOContext *pb) |
1cb5f7fd MN |
341 | { |
342 | put_be32(pb, 0xf); /* size */ | |
343 | put_tag(pb, "d263"); | |
344 | put_tag(pb, "FFMP"); | |
345 | put_be16(pb, 0x0a); | |
346 | put_byte(pb, 0); | |
347 | return 0xf; | |
348 | } | |
349 | ||
f578f938 TR |
350 | /* TODO: No idea about these values */ |
351 | static int mov_write_svq3_tag(ByteIOContext *pb) | |
1cb5f7fd | 352 | { |
f578f938 TR |
353 | put_be32(pb, 0x15); |
354 | put_tag(pb, "SMI "); | |
355 | put_tag(pb, "SEQH"); | |
356 | put_be32(pb, 0x5); | |
357 | put_be32(pb, 0xe2c0211d); | |
358 | put_be32(pb, 0xc0000000); | |
115329f1 | 359 | put_byte(pb, 0); |
f578f938 | 360 | return 0x15; |
1cb5f7fd MN |
361 | } |
362 | ||
c1b8e6d8 BC |
363 | static uint8_t *avc_find_startcode( uint8_t *p, uint8_t *end ) |
364 | { | |
365 | uint8_t *a = p + 4 - ((int)p & 3); | |
366 | ||
367 | for( end -= 3; p < a && p < end; p++ ) { | |
368 | if( p[0] == 0 && p[1] == 0 && p[2] == 1 ) | |
369 | return p; | |
370 | } | |
371 | ||
372 | for( end -= 3; p < end; p += 4 ) { | |
373 | uint32_t x = *(uint32_t*)p; | |
374 | // if( (x - 0x01000100) & (~x) & 0x80008000 ) // little endian | |
375 | // if( (x - 0x00010001) & (~x) & 0x00800080 ) // big endian | |
376 | if( (x - 0x01010101) & (~x) & 0x80808080 ) { // generic | |
377 | if( p[1] == 0 ) { | |
378 | if( p[0] == 0 && p[2] == 1 ) | |
379 | return p; | |
380 | if( p[2] == 0 && p[3] == 1 ) | |
381 | return p+1; | |
382 | } | |
383 | if( p[3] == 0 ) { | |
384 | if( p[2] == 0 && p[4] == 1 ) | |
385 | return p+2; | |
386 | if( p[4] == 0 && p[5] == 1 ) | |
387 | return p+3; | |
388 | } | |
389 | } | |
390 | } | |
391 | ||
392 | for( end += 3; p < end; p++ ) { | |
393 | if( p[0] == 0 && p[1] == 0 && p[2] == 1 ) | |
394 | return p; | |
395 | } | |
396 | ||
397 | return end + 3; | |
398 | } | |
399 | ||
400 | static void avc_parse_nal_units(uint8_t **buf, int *size) | |
401 | { | |
402 | ByteIOContext pb; | |
403 | uint8_t *p = *buf; | |
404 | uint8_t *end = p + *size; | |
405 | uint8_t *nal_start, *nal_end; | |
406 | ||
407 | url_open_dyn_buf(&pb); | |
408 | nal_start = avc_find_startcode(p, end); | |
409 | while (nal_start < end) { | |
410 | while(!*(nal_start++)); | |
411 | nal_end = avc_find_startcode(nal_start, end); | |
412 | put_be32(&pb, nal_end - nal_start); | |
413 | put_buffer(&pb, nal_start, nal_end - nal_start); | |
414 | nal_start = nal_end; | |
415 | } | |
416 | av_freep(buf); | |
417 | *size = url_close_dyn_buf(&pb, buf); | |
418 | } | |
419 | ||
420 | static int mov_write_avcc_tag(ByteIOContext *pb, MOVTrack *track) | |
421 | { | |
422 | offset_t pos = url_ftell(pb); | |
423 | ||
424 | put_be32(pb, 0); | |
425 | put_tag(pb, "avcC"); | |
426 | if (track->vosLen > 6) { | |
427 | /* check for h264 start code */ | |
428 | if (BE_32(track->vosData) == 0x00000001) { | |
429 | uint8_t *buf, *end; | |
430 | uint32_t sps_size=0, pps_size=0; | |
431 | uint8_t *sps=0, *pps=0; | |
432 | ||
433 | avc_parse_nal_units(&track->vosData, &track->vosLen); | |
434 | buf = track->vosData; | |
435 | end = track->vosData + track->vosLen; | |
436 | ||
437 | put_byte(pb, 1); /* version */ | |
438 | put_byte(pb, 77); /* profile */ | |
439 | put_byte(pb, 64); /* profile compat */ | |
440 | put_byte(pb, 30); /* level */ | |
441 | put_byte(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */ | |
442 | put_byte(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */ | |
443 | ||
444 | /* look for sps and pps */ | |
445 | while (buf < end) { | |
446 | unsigned int size; | |
447 | uint8_t nal_type; | |
448 | size = BE_32(buf); | |
449 | nal_type = buf[4] & 0x1f; | |
450 | if (nal_type == 7) { /* SPS */ | |
451 | sps = buf + 4; | |
452 | sps_size = size; | |
453 | } else if (nal_type == 8) { /* PPS */ | |
454 | pps = buf + 4; | |
455 | pps_size = size; | |
456 | } | |
457 | buf += size + 4; | |
458 | } | |
459 | assert(sps); | |
460 | assert(pps); | |
461 | put_be16(pb, sps_size); | |
462 | put_buffer(pb, sps, sps_size); | |
463 | put_byte(pb, 1); /* number of pps */ | |
464 | put_be16(pb, pps_size); | |
465 | put_buffer(pb, pps, pps_size); | |
466 | } else { | |
467 | put_buffer(pb, track->vosData, track->vosLen); | |
468 | } | |
469 | } | |
470 | return updateSize(pb, pos); | |
471 | } | |
472 | ||
e45ccf79 GB |
473 | static unsigned int descrLength(unsigned int len) |
474 | { | |
475 | if (len < 0x00000080) | |
476 | return 2 + len; | |
477 | else if (len < 0x00004000) | |
478 | return 3 + len; | |
479 | else if(len < 0x00200000) | |
480 | return 4 + len; | |
481 | else | |
482 | return 5 + len; | |
483 | } | |
484 | ||
485 | static void putDescr(ByteIOContext *pb, int tag, int size) | |
1cb5f7fd | 486 | { |
e45ccf79 GB |
487 | uint32_t len; |
488 | uint8_t vals[4]; | |
489 | ||
490 | len = size; | |
491 | vals[3] = (uint8_t)(len & 0x7f); | |
492 | len >>= 7; | |
115329f1 | 493 | vals[2] = (uint8_t)((len & 0x7f) | 0x80); |
e45ccf79 | 494 | len >>= 7; |
115329f1 | 495 | vals[1] = (uint8_t)((len & 0x7f) | 0x80); |
e45ccf79 GB |
496 | len >>= 7; |
497 | vals[0] = (uint8_t)((len & 0x7f) | 0x80); | |
498 | ||
499 | put_byte(pb, tag); // DescriptorTag | |
500 | ||
501 | if (size < 0x00000080) | |
502 | { | |
503 | put_byte(pb, vals[3]); | |
504 | } | |
505 | else if (size < 0x00004000) | |
506 | { | |
507 | put_byte(pb, vals[2]); | |
508 | put_byte(pb, vals[3]); | |
509 | } | |
510 | else if (size < 0x00200000) | |
511 | { | |
512 | put_byte(pb, vals[1]); | |
513 | put_byte(pb, vals[2]); | |
514 | put_byte(pb, vals[3]); | |
515 | } | |
516 | else if (size < 0x10000000) | |
517 | { | |
518 | put_byte(pb, vals[0]); | |
519 | put_byte(pb, vals[1]); | |
520 | put_byte(pb, vals[2]); | |
521 | put_byte(pb, vals[3]); | |
522 | } | |
1cb5f7fd MN |
523 | } |
524 | ||
6e6d6dc0 | 525 | static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack* track) // Basic |
1cb5f7fd | 526 | { |
46103f6b | 527 | int decoderSpecificInfoLen; |
b29af723 | 528 | offset_t pos = url_ftell(pb); |
e45ccf79 | 529 | |
46103f6b FR |
530 | decoderSpecificInfoLen = track->vosLen ? descrLength(track->vosLen):0; |
531 | ||
e45ccf79 | 532 | put_be32(pb, 0); // size |
1cb5f7fd | 533 | put_tag(pb, "esds"); |
e45ccf79 | 534 | put_be32(pb, 0); // Version |
1cb5f7fd | 535 | |
e45ccf79 GB |
536 | // ES descriptor |
537 | putDescr(pb, 0x03, 3 + descrLength(13 + decoderSpecificInfoLen) + | |
538 | descrLength(1)); | |
6c13c880 | 539 | put_be16(pb, track->trackID); |
1cb5f7fd MN |
540 | put_byte(pb, 0x00); // flags (= no flags) |
541 | ||
e45ccf79 GB |
542 | // DecoderConfig descriptor |
543 | putDescr(pb, 0x04, 13 + decoderSpecificInfoLen); | |
544 | ||
6c13c880 MN |
545 | // Object type indication |
546 | put_byte(pb, codec_get_tag(ff_mov_obj_type, track->enc->codec_id)); | |
e45ccf79 | 547 | |
5cad192d NS |
548 | // the following fields is made of 6 bits to identify the streamtype (4 for video, 5 for audio) |
549 | // plus 1 bit to indicate upstream and 1 bit set to 1 (reserved) | |
e45ccf79 GB |
550 | if(track->enc->codec_type == CODEC_TYPE_AUDIO) |
551 | put_byte(pb, 0x15); // flags (= Audiostream) | |
552 | else | |
553 | put_byte(pb, 0x11); // flags (= Visualstream) | |
554 | ||
6c13c880 MN |
555 | put_byte(pb, track->enc->rc_buffer_size>>(3+16)); // Buffersize DB (24 bits) |
556 | put_be16(pb, (track->enc->rc_buffer_size>>3)&0xFFFF); // Buffersize DB | |
1cb5f7fd | 557 | |
6c13c880 MN |
558 | put_be32(pb, FFMAX(track->enc->bit_rate, track->enc->rc_max_rate)); // maxbitrate (FIXME should be max rate in any 1 sec window) |
559 | if(track->enc->rc_max_rate != track->enc->rc_min_rate || track->enc->rc_min_rate==0) | |
560 | put_be32(pb, 0); // vbr | |
561 | else | |
562 | put_be32(pb, track->enc->rc_max_rate); // avg bitrate | |
1cb5f7fd | 563 | |
e45ccf79 GB |
564 | if (track->vosLen) |
565 | { | |
566 | // DecoderSpecific info descriptor | |
567 | putDescr(pb, 0x05, track->vosLen); | |
568 | put_buffer(pb, track->vosData, track->vosLen); | |
569 | } | |
570 | ||
8af18154 | 571 | |
e45ccf79 | 572 | // SL descriptor |
4bfc029f | 573 | putDescr(pb, 0x06, 1); |
1cb5f7fd | 574 | put_byte(pb, 0x02); |
e45ccf79 | 575 | return updateSize (pb, pos); |
1cb5f7fd MN |
576 | } |
577 | ||
0c1e0bab | 578 | static const CodecTag codec_movvideo_tags[] = { |
8cc7a34d AB |
579 | { CODEC_ID_SVQ1, MKTAG('S', 'V', 'Q', '1') }, |
580 | { CODEC_ID_SVQ3, MKTAG('S', 'V', 'Q', '3') }, | |
581 | { CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') }, | |
582 | { CODEC_ID_H263, MKTAG('s', '2', '6', '3') }, | |
0c1e0bab | 583 | { CODEC_ID_H264, MKTAG('a', 'v', 'c', '1') }, |
8e321619 BC |
584 | /* special handling in mov_find_video_codec_tag */ |
585 | { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', ' ') }, /* DV NTSC */ | |
586 | { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', 'p') }, /* DV PAL */ | |
587 | { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'p', 'p') }, /* DVCPRO PAL */ | |
588 | { CODEC_ID_DVVIDEO, MKTAG('d', 'v', '5', 'n') }, /* DVCPRO50 NTSC */ | |
589 | { CODEC_ID_DVVIDEO, MKTAG('d', 'v', '5', 'p') }, /* DVCPRO50 PAL */ | |
590 | { CODEC_ID_NONE, 0 }, | |
8cc7a34d AB |
591 | }; |
592 | ||
8e321619 BC |
593 | static int mov_find_video_codec_tag(MOVTrack* track) |
594 | { | |
595 | int tag; | |
596 | ||
597 | tag = track->enc->codec_tag; | |
598 | if (!tag) { | |
599 | if (track->enc->codec_id == CODEC_ID_DVVIDEO) { | |
600 | if (track->enc->height == 480) { /* NTSC */ | |
601 | if (track->enc->pix_fmt == PIX_FMT_YUV422P) | |
602 | tag = MKTAG('d', 'v', '5', 'n'); | |
603 | else | |
604 | tag = MKTAG('d', 'v', 'c', ' '); | |
605 | } else { /* assume PAL */ | |
606 | if (track->enc->pix_fmt == PIX_FMT_YUV422P) | |
607 | tag = MKTAG('d', 'v', '5', 'p'); | |
608 | else if (track->enc->pix_fmt == PIX_FMT_YUV420P) | |
8e321619 | 609 | tag = MKTAG('d', 'v', 'c', 'p'); |
3cdc7eb4 BC |
610 | else |
611 | tag = MKTAG('d', 'v', 'p', 'p'); | |
8e321619 BC |
612 | } |
613 | } else { | |
614 | tag = codec_get_tag(codec_movvideo_tags, track->enc->codec_id); | |
615 | } | |
616 | } | |
617 | // if no mac fcc found, try with Microsoft tags | |
618 | if (!tag) | |
619 | tag = codec_get_tag(codec_bmp_tags, track->enc->codec_id); | |
620 | assert(tag); | |
621 | return tag; | |
622 | } | |
623 | ||
f578f938 | 624 | static int mov_write_video_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 625 | { |
b29af723 | 626 | offset_t pos = url_ftell(pb); |
53ffdd14 | 627 | char compressor_name[32]; |
ffdd57d4 | 628 | int tag; |
8cc7a34d | 629 | |
f578f938 | 630 | put_be32(pb, 0); /* size */ |
8cc7a34d | 631 | |
8e321619 | 632 | tag = mov_find_video_codec_tag(track); |
ffdd57d4 | 633 | put_le32(pb, tag); // store it byteswapped |
6e6d6dc0 | 634 | |
f578f938 TR |
635 | put_be32(pb, 0); /* Reserved */ |
636 | put_be16(pb, 0); /* Reserved */ | |
637 | put_be16(pb, 1); /* Data-reference index */ | |
638 | ||
53ffdd14 RG |
639 | put_be16(pb, 0); /* Codec stream version */ |
640 | put_be16(pb, 0); /* Codec stream revision (=0) */ | |
641 | put_tag(pb, "FFMP"); /* Vendor */ | |
642 | if(track->enc->codec_id == CODEC_ID_RAWVIDEO) { | |
643 | put_be32(pb, 0); /* Temporal Quality */ | |
644 | put_be32(pb, 0x400); /* Spatial Quality = lossless*/ | |
645 | } else { | |
646 | put_be32(pb, 0x200); /* Temporal Quality = normal */ | |
647 | put_be32(pb, 0x200); /* Spatial Quality = normal */ | |
648 | } | |
f578f938 TR |
649 | put_be16(pb, track->enc->width); /* Video width */ |
650 | put_be16(pb, track->enc->height); /* Video height */ | |
53ffdd14 RG |
651 | put_be32(pb, 0x00480000); /* Horizontal resolution 72dpi */ |
652 | put_be32(pb, 0x00480000); /* Vertical resolution 72dpi */ | |
9a4d9388 RS |
653 | put_be32(pb, 0); /* Data size (= 0) */ |
654 | put_be16(pb, 1); /* Frame count (= 1) */ | |
115329f1 | 655 | |
53ffdd14 | 656 | memset(compressor_name,0,32); |
25cf9062 | 657 | if (track->enc->codec && track->enc->codec->name) |
53ffdd14 | 658 | strncpy(compressor_name,track->enc->codec->name,31); |
25cf9062 | 659 | put_byte(pb, strlen(compressor_name)); |
53ffdd14 | 660 | put_buffer(pb, compressor_name, 31); |
115329f1 | 661 | |
f578f938 TR |
662 | put_be16(pb, 0x18); /* Reserved */ |
663 | put_be16(pb, 0xffff); /* Reserved */ | |
664 | if(track->enc->codec_id == CODEC_ID_MPEG4) | |
665 | mov_write_esds_tag(pb, track); | |
666 | else if(track->enc->codec_id == CODEC_ID_H263) | |
667 | mov_write_d263_tag(pb); | |
668 | else if(track->enc->codec_id == CODEC_ID_SVQ3) | |
115329f1 | 669 | mov_write_svq3_tag(pb); |
c1b8e6d8 BC |
670 | else if(track->enc->codec_id == CODEC_ID_H264) |
671 | mov_write_avcc_tag(pb, track); | |
f578f938 TR |
672 | |
673 | return updateSize (pb, pos); | |
1cb5f7fd MN |
674 | } |
675 | ||
6e6d6dc0 | 676 | static int mov_write_stsd_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 677 | { |
b29af723 | 678 | offset_t pos = url_ftell(pb); |
1cb5f7fd MN |
679 | put_be32(pb, 0); /* size */ |
680 | put_tag(pb, "stsd"); | |
681 | put_be32(pb, 0); /* version & flags */ | |
682 | put_be32(pb, 1); /* entry count */ | |
f578f938 TR |
683 | if (track->enc->codec_type == CODEC_TYPE_VIDEO) |
684 | mov_write_video_tag(pb, track); | |
685 | else if (track->enc->codec_type == CODEC_TYPE_AUDIO) | |
686 | mov_write_audio_tag(pb, track); | |
6e6d6dc0 | 687 | return updateSize(pb, pos); |
1cb5f7fd MN |
688 | } |
689 | ||
b4712e3c BC |
690 | static int mov_write_ctts_tag(ByteIOContext *pb, MOVTrack* track) |
691 | { | |
692 | Time2Sample *ctts_entries; | |
693 | uint32_t entries = 0; | |
694 | uint32_t atom_size; | |
695 | int i; | |
696 | ||
697 | ctts_entries = av_malloc((track->entry + 1) * sizeof(*ctts_entries)); /* worst case */ | |
698 | ctts_entries[0].count = 1; | |
699 | ctts_entries[0].duration = track->cluster[0][0].cts; | |
700 | for (i=1; i<track->entry; i++) { | |
701 | int cl = i / MOV_INDEX_CLUSTER_SIZE; | |
702 | int id = i % MOV_INDEX_CLUSTER_SIZE; | |
703 | if (track->cluster[cl][id].cts == ctts_entries[entries].duration) { | |
704 | ctts_entries[entries].count++; /* compress */ | |
705 | } else { | |
706 | entries++; | |
707 | ctts_entries[entries].duration = track->cluster[cl][id].cts; | |
708 | ctts_entries[entries].count = 1; | |
709 | } | |
710 | } | |
711 | entries++; /* last one */ | |
712 | atom_size = 16 + (entries * 8); | |
713 | put_be32(pb, atom_size); /* size */ | |
714 | put_tag(pb, "ctts"); | |
715 | put_be32(pb, 0); /* version & flags */ | |
716 | put_be32(pb, entries); /* entry count */ | |
717 | for (i=0; i<entries; i++) { | |
718 | put_be32(pb, ctts_entries[i].count); | |
719 | put_be32(pb, ctts_entries[i].duration); | |
720 | } | |
721 | av_free(ctts_entries); | |
722 | return atom_size; | |
723 | } | |
724 | ||
0078e9b9 | 725 | /* TODO: */ |
e45ccf79 | 726 | /* Time to sample atom */ |
6e6d6dc0 | 727 | static int mov_write_stts_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd MN |
728 | { |
729 | put_be32(pb, 0x18); /* size */ | |
730 | put_tag(pb, "stts"); | |
731 | put_be32(pb, 0); /* version & flags */ | |
732 | put_be32(pb, 1); /* entry count */ | |
733 | ||
e45ccf79 GB |
734 | put_be32(pb, track->sampleCount); /* sample count */ |
735 | put_be32(pb, track->sampleDuration); /* sample duration */ | |
1cb5f7fd MN |
736 | return 0x18; |
737 | } | |
738 | ||
6e6d6dc0 | 739 | static int mov_write_dref_tag(ByteIOContext *pb) |
1cb5f7fd MN |
740 | { |
741 | put_be32(pb, 28); /* size */ | |
742 | put_tag(pb, "dref"); | |
743 | put_be32(pb, 0); /* version & flags */ | |
744 | put_be32(pb, 1); /* entry count */ | |
745 | ||
746 | put_be32(pb, 0xc); /* size */ | |
747 | put_tag(pb, "url "); | |
748 | put_be32(pb, 1); /* version & flags */ | |
749 | ||
750 | return 28; | |
751 | } | |
752 | ||
6e6d6dc0 | 753 | static int mov_write_stbl_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 754 | { |
b29af723 | 755 | offset_t pos = url_ftell(pb); |
1cb5f7fd MN |
756 | put_be32(pb, 0); /* size */ |
757 | put_tag(pb, "stbl"); | |
6e6d6dc0 MN |
758 | mov_write_stsd_tag(pb, track); |
759 | mov_write_stts_tag(pb, track); | |
f578f938 TR |
760 | if (track->enc->codec_type == CODEC_TYPE_VIDEO && |
761 | track->hasKeyframes) | |
762 | mov_write_stss_tag(pb, track); | |
b4712e3c BC |
763 | if (track->enc->codec_type == CODEC_TYPE_VIDEO && |
764 | track->hasBframes) | |
765 | mov_write_ctts_tag(pb, track); | |
6e6d6dc0 MN |
766 | mov_write_stsc_tag(pb, track); |
767 | mov_write_stsz_tag(pb, track); | |
768 | mov_write_stco_tag(pb, track); | |
769 | return updateSize(pb, pos); | |
1cb5f7fd MN |
770 | } |
771 | ||
6e6d6dc0 | 772 | static int mov_write_dinf_tag(ByteIOContext *pb) |
1cb5f7fd | 773 | { |
b29af723 | 774 | offset_t pos = url_ftell(pb); |
1cb5f7fd MN |
775 | put_be32(pb, 0); /* size */ |
776 | put_tag(pb, "dinf"); | |
6e6d6dc0 MN |
777 | mov_write_dref_tag(pb); |
778 | return updateSize(pb, pos); | |
1cb5f7fd MN |
779 | } |
780 | ||
6e6d6dc0 | 781 | static int mov_write_smhd_tag(ByteIOContext *pb) |
1cb5f7fd MN |
782 | { |
783 | put_be32(pb, 16); /* size */ | |
784 | put_tag(pb, "smhd"); | |
785 | put_be32(pb, 0); /* version & flags */ | |
786 | put_be16(pb, 0); /* reserved (balance, normally = 0) */ | |
787 | put_be16(pb, 0); /* reserved */ | |
788 | return 16; | |
789 | } | |
790 | ||
6e6d6dc0 | 791 | static int mov_write_vmhd_tag(ByteIOContext *pb) |
1cb5f7fd MN |
792 | { |
793 | put_be32(pb, 0x14); /* size (always 0x14) */ | |
794 | put_tag(pb, "vmhd"); | |
795 | put_be32(pb, 0x01); /* version & flags */ | |
796 | put_be64(pb, 0); /* reserved (graphics mode = copy) */ | |
797 | return 0x14; | |
798 | } | |
799 | ||
6e6d6dc0 | 800 | static int mov_write_hdlr_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 801 | { |
9a4d9388 | 802 | char *descr, *hdlr, *hdlr_type; |
b29af723 | 803 | offset_t pos = url_ftell(pb); |
115329f1 | 804 | |
9a4d9388 | 805 | if (!track) { /* no media --> data handler */ |
bb270c08 DB |
806 | hdlr = "dhlr"; |
807 | hdlr_type = "url "; | |
808 | descr = "DataHandler"; | |
9a4d9388 | 809 | } else { |
bb270c08 DB |
810 | hdlr = (track->mode == MODE_MOV) ? "mhlr" : "\0\0\0\0"; |
811 | if (track->enc->codec_type == CODEC_TYPE_VIDEO) { | |
812 | hdlr_type = "vide"; | |
813 | descr = "VideoHandler"; | |
814 | } else { | |
815 | hdlr_type = "soun"; | |
816 | descr = "SoundHandler"; | |
817 | } | |
9a4d9388 | 818 | } |
115329f1 | 819 | |
f578f938 | 820 | put_be32(pb, 0); /* size */ |
1cb5f7fd MN |
821 | put_tag(pb, "hdlr"); |
822 | put_be32(pb, 0); /* Version & flags */ | |
906b578f | 823 | put_buffer(pb, hdlr, 4); /* handler */ |
9a4d9388 | 824 | put_tag(pb, hdlr_type); /* handler type */ |
f578f938 TR |
825 | put_be32(pb ,0); /* reserved */ |
826 | put_be32(pb ,0); /* reserved */ | |
827 | put_be32(pb ,0); /* reserved */ | |
9a4d9388 RS |
828 | put_byte(pb, strlen(descr)); /* string counter */ |
829 | put_buffer(pb, descr, strlen(descr)); /* handler description */ | |
830 | return updateSize(pb, pos); | |
831 | } | |
832 | ||
833 | static int mov_write_minf_tag(ByteIOContext *pb, MOVTrack* track) | |
834 | { | |
b29af723 | 835 | offset_t pos = url_ftell(pb); |
9a4d9388 RS |
836 | put_be32(pb, 0); /* size */ |
837 | put_tag(pb, "minf"); | |
1cb5f7fd | 838 | if(track->enc->codec_type == CODEC_TYPE_VIDEO) |
9a4d9388 | 839 | mov_write_vmhd_tag(pb); |
1cb5f7fd | 840 | else |
9a4d9388 RS |
841 | mov_write_smhd_tag(pb); |
842 | if (track->mode == MODE_MOV) /* FIXME: Why do it for MODE_MOV only ? */ | |
843 | mov_write_hdlr_tag(pb, NULL); | |
844 | mov_write_dinf_tag(pb); | |
845 | mov_write_stbl_tag(pb, track); | |
f578f938 | 846 | return updateSize(pb, pos); |
1cb5f7fd MN |
847 | } |
848 | ||
6e6d6dc0 | 849 | static int mov_write_mdhd_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd MN |
850 | { |
851 | put_be32(pb, 32); /* size */ | |
852 | put_tag(pb, "mdhd"); | |
853 | put_be32(pb, 0); /* Version & flags */ | |
854 | put_be32(pb, track->time); /* creation time */ | |
855 | put_be32(pb, track->time); /* modification time */ | |
115329f1 | 856 | put_be32(pb, track->timescale); /* time scale (sample rate for audio) */ |
e45ccf79 | 857 | put_be32(pb, track->trackDuration); /* duration */ |
ab561df9 | 858 | put_be16(pb, track->language); /* language */ |
1cb5f7fd MN |
859 | put_be16(pb, 0); /* reserved (quality) */ |
860 | return 32; | |
861 | } | |
862 | ||
6e6d6dc0 | 863 | static int mov_write_mdia_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 864 | { |
b29af723 | 865 | offset_t pos = url_ftell(pb); |
1cb5f7fd MN |
866 | put_be32(pb, 0); /* size */ |
867 | put_tag(pb, "mdia"); | |
6e6d6dc0 MN |
868 | mov_write_mdhd_tag(pb, track); |
869 | mov_write_hdlr_tag(pb, track); | |
870 | mov_write_minf_tag(pb, track); | |
871 | return updateSize(pb, pos); | |
1cb5f7fd MN |
872 | } |
873 | ||
6e6d6dc0 | 874 | static int mov_write_tkhd_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd MN |
875 | { |
876 | put_be32(pb, 0x5c); /* size (always 0x5c) */ | |
877 | put_tag(pb, "tkhd"); | |
f578f938 | 878 | put_be32(pb, 0xf); /* version & flags (track enabled) */ |
1cb5f7fd MN |
879 | put_be32(pb, track->time); /* creation time */ |
880 | put_be32(pb, track->time); /* modification time */ | |
881 | put_be32(pb, track->trackID); /* track-id */ | |
882 | put_be32(pb, 0); /* reserved */ | |
83fa2ef8 | 883 | put_be32(pb, av_rescale_rnd(track->trackDuration, globalTimescale, track->timescale, AV_ROUND_UP)); /* duration */ |
1cb5f7fd MN |
884 | |
885 | put_be32(pb, 0); /* reserved */ | |
886 | put_be32(pb, 0); /* reserved */ | |
887 | put_be32(pb, 0x0); /* reserved (Layer & Alternate group) */ | |
888 | /* Volume, only for audio */ | |
889 | if(track->enc->codec_type == CODEC_TYPE_AUDIO) | |
890 | put_be16(pb, 0x0100); | |
891 | else | |
892 | put_be16(pb, 0); | |
893 | put_be16(pb, 0); /* reserved */ | |
894 | ||
895 | /* Matrix structure */ | |
896 | put_be32(pb, 0x00010000); /* reserved */ | |
897 | put_be32(pb, 0x0); /* reserved */ | |
898 | put_be32(pb, 0x0); /* reserved */ | |
899 | put_be32(pb, 0x0); /* reserved */ | |
900 | put_be32(pb, 0x00010000); /* reserved */ | |
901 | put_be32(pb, 0x0); /* reserved */ | |
902 | put_be32(pb, 0x0); /* reserved */ | |
903 | put_be32(pb, 0x0); /* reserved */ | |
904 | put_be32(pb, 0x40000000); /* reserved */ | |
905 | ||
906 | /* Track width and height, for visual only */ | |
907 | if(track->enc->codec_type == CODEC_TYPE_VIDEO) { | |
69dde1ad GB |
908 | double sample_aspect_ratio = av_q2d(track->enc->sample_aspect_ratio); |
909 | if( !sample_aspect_ratio ) sample_aspect_ratio = 1; | |
910 | put_be32(pb, sample_aspect_ratio * track->enc->width*0x10000); | |
f578f938 | 911 | put_be32(pb, track->enc->height*0x10000); |
1cb5f7fd MN |
912 | } |
913 | else { | |
914 | put_be32(pb, 0); | |
915 | put_be32(pb, 0); | |
916 | } | |
917 | return 0x5c; | |
918 | } | |
919 | ||
8af18154 | 920 | // This box seems important for the psp playback ... without it the movie seems to hang |
921 | static int mov_write_edts_tag(ByteIOContext *pb, MOVTrack *track) | |
922 | { | |
8af18154 | 923 | put_be32(pb, 0x24); /* size */ |
924 | put_tag(pb, "edts"); | |
925 | put_be32(pb, 0x1c); /* size */ | |
926 | put_tag(pb, "elst"); | |
927 | put_be32(pb, 0x0); | |
928 | put_be32(pb, 0x1); | |
929 | ||
83fa2ef8 | 930 | put_be32(pb, av_rescale_rnd(track->trackDuration, globalTimescale, track->timescale, AV_ROUND_UP)); /* duration ... doesn't seem to effect psp */ |
8af18154 | 931 | |
c6e722e5 | 932 | put_be32(pb, track->sampleDuration); |
8af18154 | 933 | put_be32(pb, 0x00010000); |
934 | return 0x24; | |
935 | } | |
936 | ||
937 | // goes at the end of each track! ... Critical for PSP playback ("Incompatible data" without it) | |
938 | static int mov_write_uuid_tag_psp(ByteIOContext *pb, MOVTrack *mov) | |
939 | { | |
940 | put_be32(pb, 0x34); /* size ... reports as 28 in mp4box! */ | |
941 | put_tag(pb, "uuid"); | |
942 | put_tag(pb, "USMT"); | |
943 | put_be32(pb, 0x21d24fce); | |
944 | put_be32(pb, 0xbb88695c); | |
945 | put_be32(pb, 0xfac9c740); | |
946 | put_be32(pb, 0x1c); // another size here! | |
947 | put_tag(pb, "MTDT"); | |
948 | put_be32(pb, 0x00010012); | |
949 | put_be32(pb, 0x0a); | |
950 | put_be32(pb, 0x55c40000); | |
951 | put_be32(pb, 0x1); | |
952 | put_be32(pb, 0x0); | |
953 | return 0x34; | |
954 | } | |
955 | ||
6e6d6dc0 | 956 | static int mov_write_trak_tag(ByteIOContext *pb, MOVTrack* track) |
1cb5f7fd | 957 | { |
b29af723 | 958 | offset_t pos = url_ftell(pb); |
1cb5f7fd MN |
959 | put_be32(pb, 0); /* size */ |
960 | put_tag(pb, "trak"); | |
6e6d6dc0 | 961 | mov_write_tkhd_tag(pb, track); |
c6e722e5 | 962 | if (track->mode == MODE_PSP || track->hasBframes) |
8af18154 | 963 | mov_write_edts_tag(pb, track); // PSP Movies require edts box |
6e6d6dc0 | 964 | mov_write_mdia_tag(pb, track); |
115329f1 | 965 | if (track->mode == MODE_PSP) |
8af18154 | 966 | mov_write_uuid_tag_psp(pb,track); // PSP Movies require this uuid box |
6e6d6dc0 | 967 | return updateSize(pb, pos); |
1cb5f7fd MN |
968 | } |
969 | ||
88730be6 | 970 | #if 0 |
1cb5f7fd | 971 | /* TODO: Not sorted out, but not necessary either */ |
6e6d6dc0 | 972 | static int mov_write_iods_tag(ByteIOContext *pb, MOVContext *mov) |
1cb5f7fd MN |
973 | { |
974 | put_be32(pb, 0x15); /* size */ | |
975 | put_tag(pb, "iods"); | |
976 | put_be32(pb, 0); /* version & flags */ | |
977 | put_be16(pb, 0x1007); | |
978 | put_byte(pb, 0); | |
979 | put_be16(pb, 0x4fff); | |
980 | put_be16(pb, 0xfffe); | |
981 | put_be16(pb, 0x01ff); | |
982 | return 0x15; | |
983 | } | |
88730be6 | 984 | #endif |
1cb5f7fd | 985 | |
6e6d6dc0 | 986 | static int mov_write_mvhd_tag(ByteIOContext *pb, MOVContext *mov) |
1cb5f7fd | 987 | { |
b29af723 MN |
988 | int maxTrackID = 1, i; |
989 | int64_t maxTrackLenTemp, maxTrackLen = 0; | |
1cb5f7fd MN |
990 | |
991 | put_be32(pb, 0x6c); /* size (always 0x6c) */ | |
992 | put_tag(pb, "mvhd"); | |
993 | put_be32(pb, 0); /* version & flags */ | |
994 | put_be32(pb, mov->time); /* creation time */ | |
995 | put_be32(pb, mov->time); /* modification time */ | |
996 | put_be32(pb, mov->timescale); /* timescale */ | |
997 | for (i=0; i<MAX_STREAMS; i++) { | |
998 | if(mov->tracks[i].entry > 0) { | |
83fa2ef8 | 999 | maxTrackLenTemp = av_rescale_rnd(mov->tracks[i].trackDuration, globalTimescale, mov->tracks[i].timescale, AV_ROUND_UP); |
f578f938 TR |
1000 | if(maxTrackLen < maxTrackLenTemp) |
1001 | maxTrackLen = maxTrackLenTemp; | |
1cb5f7fd MN |
1002 | if(maxTrackID < mov->tracks[i].trackID) |
1003 | maxTrackID = mov->tracks[i].trackID; | |
1004 | } | |
1005 | } | |
1006 | put_be32(pb, maxTrackLen); /* duration of longest track */ | |
1007 | ||
1008 | put_be32(pb, 0x00010000); /* reserved (preferred rate) 1.0 = normal */ | |
1009 | put_be16(pb, 0x0100); /* reserved (preferred volume) 1.0 = normal */ | |
1010 | put_be16(pb, 0); /* reserved */ | |
1011 | put_be32(pb, 0); /* reserved */ | |
1012 | put_be32(pb, 0); /* reserved */ | |
1013 | ||
1014 | /* Matrix structure */ | |
1015 | put_be32(pb, 0x00010000); /* reserved */ | |
1016 | put_be32(pb, 0x0); /* reserved */ | |
1017 | put_be32(pb, 0x0); /* reserved */ | |
1018 | put_be32(pb, 0x0); /* reserved */ | |
1019 | put_be32(pb, 0x00010000); /* reserved */ | |
1020 | put_be32(pb, 0x0); /* reserved */ | |
1021 | put_be32(pb, 0x0); /* reserved */ | |
1022 | put_be32(pb, 0x0); /* reserved */ | |
1023 | put_be32(pb, 0x40000000); /* reserved */ | |
1024 | ||
1025 | put_be32(pb, 0); /* reserved (preview time) */ | |
1026 | put_be32(pb, 0); /* reserved (preview duration) */ | |
1027 | put_be32(pb, 0); /* reserved (poster time) */ | |
1028 | put_be32(pb, 0); /* reserved (selection time) */ | |
1029 | put_be32(pb, 0); /* reserved (selection duration) */ | |
1030 | put_be32(pb, 0); /* reserved (current time) */ | |
1031 | put_be32(pb, maxTrackID+1); /* Next track id */ | |
1032 | return 0x6c; | |
1033 | } | |
1034 | ||
b6c50eb1 PB |
1035 | static int mov_write_itunes_hdlr_tag(ByteIOContext *pb, MOVContext* mov, |
1036 | AVFormatContext *s) | |
1037 | { | |
b29af723 | 1038 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1039 | put_be32(pb, 0); /* size */ |
1040 | put_tag(pb, "hdlr"); | |
1041 | put_be32(pb, 0); | |
1042 | put_be32(pb, 0); | |
1043 | put_tag(pb, "mdir"); | |
1044 | put_tag(pb, "appl"); | |
1045 | put_be32(pb, 0); | |
1046 | put_be32(pb, 0); | |
1047 | put_be16(pb, 0); | |
1048 | return updateSize(pb, pos); | |
1049 | } | |
1050 | ||
1051 | /* helper function to write a data tag with the specified string as data */ | |
1052 | static int mov_write_string_data_tag(ByteIOContext *pb, MOVContext* mov, | |
1053 | AVFormatContext *s, const char *data) | |
1054 | { | |
b29af723 | 1055 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1056 | put_be32(pb, 0); /* size */ |
1057 | put_tag(pb, "data"); | |
1058 | put_be32(pb, 1); | |
1059 | put_be32(pb, 0); | |
1060 | put_buffer(pb, data, strlen(data)); | |
1061 | return updateSize(pb, pos); | |
1062 | } | |
1063 | ||
1064 | /* iTunes name of the song/movie */ | |
1065 | static int mov_write_nam_tag(ByteIOContext *pb, MOVContext* mov, | |
1066 | AVFormatContext *s) | |
1067 | { | |
1068 | int size = 0; | |
1069 | if ( s->title[0] ) { | |
b29af723 | 1070 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1071 | put_be32(pb, 0); /* size */ |
1072 | put_tag(pb, "\251nam"); | |
1073 | mov_write_string_data_tag(pb, mov, s, s->title); | |
1074 | size = updateSize(pb, pos); | |
1075 | } | |
1076 | return size; | |
1077 | } | |
1078 | ||
1079 | /* iTunes name of the artist/performer */ | |
1080 | static int mov_write_ART_tag(ByteIOContext *pb, MOVContext* mov, | |
1081 | AVFormatContext *s) | |
1082 | { | |
1083 | int size = 0; | |
1084 | if ( s->author[0] ) { | |
b29af723 | 1085 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1086 | put_be32(pb, 0); /* size */ |
1087 | put_tag(pb, "\251ART"); | |
1088 | // we use the author here as this is the only thing that we have... | |
1089 | mov_write_string_data_tag(pb, mov, s, s->author); | |
1090 | size = updateSize(pb, pos); | |
1091 | } | |
1092 | return size; | |
1093 | } | |
1094 | ||
1095 | /* iTunes name of the writer */ | |
1096 | static int mov_write_wrt_tag(ByteIOContext *pb, MOVContext* mov, | |
1097 | AVFormatContext *s) | |
1098 | { | |
1099 | int size = 0; | |
1100 | if ( s->author[0] ) { | |
b29af723 | 1101 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1102 | put_be32(pb, 0); /* size */ |
1103 | put_tag(pb, "\251wrt"); | |
1104 | mov_write_string_data_tag(pb, mov, s, s->author); | |
1105 | size = updateSize(pb, pos); | |
1106 | } | |
1107 | return size; | |
1108 | } | |
1109 | ||
1110 | /* iTunes name of the album */ | |
1111 | static int mov_write_alb_tag(ByteIOContext *pb, MOVContext* mov, | |
1112 | AVFormatContext *s) | |
1113 | { | |
1114 | int size = 0; | |
1115 | if ( s->album[0] ) { | |
b29af723 | 1116 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1117 | put_be32(pb, 0); /* size */ |
1118 | put_tag(pb, "\251alb"); | |
1119 | mov_write_string_data_tag(pb, mov, s, s->album); | |
1120 | size = updateSize(pb, pos); | |
1121 | } | |
1122 | return size; | |
1123 | } | |
1124 | ||
1125 | /* iTunes year */ | |
1126 | static int mov_write_day_tag(ByteIOContext *pb, MOVContext* mov, | |
1127 | AVFormatContext *s) | |
1128 | { | |
1129 | char year[5]; | |
1130 | int size = 0; | |
1131 | if ( s->year ) { | |
b29af723 | 1132 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1133 | put_be32(pb, 0); /* size */ |
1134 | put_tag(pb, "\251day"); | |
1135 | snprintf(year, 5, "%04d", s->year); | |
1136 | mov_write_string_data_tag(pb, mov, s, year); | |
1137 | size = updateSize(pb, pos); | |
1138 | } | |
1139 | return size; | |
1140 | } | |
1141 | ||
1142 | /* iTunes tool used to create the file */ | |
1143 | static int mov_write_too_tag(ByteIOContext *pb, MOVContext* mov, | |
1144 | AVFormatContext *s) | |
1145 | { | |
b29af723 | 1146 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1147 | put_be32(pb, 0); /* size */ |
1148 | put_tag(pb, "\251too"); | |
1149 | mov_write_string_data_tag(pb, mov, s, LIBAVFORMAT_IDENT); | |
1150 | return updateSize(pb, pos); | |
1151 | } | |
1152 | ||
1153 | /* iTunes comment */ | |
1154 | static int mov_write_cmt_tag(ByteIOContext *pb, MOVContext* mov, | |
1155 | AVFormatContext *s) | |
1156 | { | |
1157 | int size = 0; | |
1158 | if ( s->comment[0] ) { | |
b29af723 | 1159 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1160 | put_be32(pb, 0); /* size */ |
1161 | put_tag(pb, "\251cmt"); | |
1162 | mov_write_string_data_tag(pb, mov, s, s->comment); | |
1163 | size = updateSize(pb, pos); | |
1164 | } | |
1165 | return size; | |
1166 | } | |
1167 | ||
1168 | /* iTunes custom genre */ | |
1169 | static int mov_write_gen_tag(ByteIOContext *pb, MOVContext* mov, | |
1170 | AVFormatContext *s) | |
1171 | { | |
1172 | int size = 0; | |
1173 | if ( s->genre[0] ) { | |
b29af723 | 1174 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1175 | put_be32(pb, 0); /* size */ |
1176 | put_tag(pb, "\251gen"); | |
1177 | mov_write_string_data_tag(pb, mov, s, s->genre); | |
1178 | size = updateSize(pb, pos); | |
1179 | } | |
1180 | return size; | |
1181 | } | |
1182 | ||
1183 | /* iTunes track number */ | |
1184 | static int mov_write_trkn_tag(ByteIOContext *pb, MOVContext* mov, | |
1185 | AVFormatContext *s) | |
1186 | { | |
1187 | int size = 0; | |
1188 | if ( s->track ) { | |
b29af723 | 1189 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1190 | put_be32(pb, 0); /* size */ |
1191 | put_tag(pb, "trkn"); | |
1192 | { | |
b29af723 | 1193 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1194 | put_be32(pb, 0); /* size */ |
1195 | put_tag(pb, "data"); | |
1196 | put_be32(pb, 0); // 8 bytes empty | |
1197 | put_be32(pb, 0); | |
1198 | put_be16(pb, 0); // empty | |
1199 | put_be16(pb, s->track); // track number | |
1200 | put_be16(pb, 0); // total track number | |
1201 | put_be16(pb, 0); // empty | |
1202 | updateSize(pb, pos); | |
1203 | } | |
1204 | size = updateSize(pb, pos); | |
1205 | } | |
1206 | return size; | |
1207 | } | |
1208 | ||
1209 | /* iTunes meta data list */ | |
1210 | static int mov_write_ilst_tag(ByteIOContext *pb, MOVContext* mov, | |
1211 | AVFormatContext *s) | |
1212 | { | |
b29af723 | 1213 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1214 | put_be32(pb, 0); /* size */ |
1215 | put_tag(pb, "ilst"); | |
1216 | mov_write_nam_tag(pb, mov, s); | |
1217 | mov_write_ART_tag(pb, mov, s); | |
1218 | mov_write_wrt_tag(pb, mov, s); | |
1219 | mov_write_alb_tag(pb, mov, s); | |
1220 | mov_write_day_tag(pb, mov, s); | |
1221 | mov_write_too_tag(pb, mov, s); | |
1222 | mov_write_cmt_tag(pb, mov, s); | |
1223 | mov_write_gen_tag(pb, mov, s); | |
1224 | mov_write_trkn_tag(pb, mov, s); | |
1225 | return updateSize(pb, pos); | |
1226 | } | |
1227 | ||
1228 | /* iTunes meta data tag */ | |
1229 | static int mov_write_meta_tag(ByteIOContext *pb, MOVContext* mov, | |
1230 | AVFormatContext *s) | |
1231 | { | |
1232 | int size = 0; | |
1233 | ||
1234 | // only save meta tag if required | |
115329f1 | 1235 | if ( s->title[0] || s->author[0] || s->album[0] || s->year || |
b6c50eb1 | 1236 | s->comment[0] || s->genre[0] || s->track ) { |
b29af723 | 1237 | offset_t pos = url_ftell(pb); |
b6c50eb1 PB |
1238 | put_be32(pb, 0); /* size */ |
1239 | put_tag(pb, "meta"); | |
1240 | put_be32(pb, 0); | |
1241 | mov_write_itunes_hdlr_tag(pb, mov, s); | |
1242 | mov_write_ilst_tag(pb, mov, s); | |
1243 | size = updateSize(pb, pos); | |
1244 | } | |
1245 | return size; | |
1246 | } | |
115329f1 | 1247 | |
69dde1ad GB |
1248 | static int mov_write_udta_tag(ByteIOContext *pb, MOVContext* mov, |
1249 | AVFormatContext *s) | |
1250 | { | |
b29af723 | 1251 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1252 | int i; |
1253 | ||
1254 | put_be32(pb, 0); /* size */ | |
1255 | put_tag(pb, "udta"); | |
1256 | ||
b6c50eb1 PB |
1257 | /* iTunes meta data */ |
1258 | mov_write_meta_tag(pb, mov, s); | |
1259 | ||
69dde1ad GB |
1260 | /* Requirements */ |
1261 | for (i=0; i<MAX_STREAMS; i++) { | |
1262 | if(mov->tracks[i].entry <= 0) continue; | |
1263 | if (mov->tracks[i].enc->codec_id == CODEC_ID_AAC || | |
1264 | mov->tracks[i].enc->codec_id == CODEC_ID_MPEG4) { | |
b29af723 | 1265 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1266 | put_be32(pb, 0); /* size */ |
1267 | put_tag(pb, "\251req"); | |
1268 | put_be16(pb, sizeof("QuickTime 6.0 or greater") - 1); | |
1269 | put_be16(pb, 0); | |
1270 | put_buffer(pb, "QuickTime 6.0 or greater", | |
1271 | sizeof("QuickTime 6.0 or greater") - 1); | |
1272 | updateSize(pb, pos); | |
1273 | break; | |
1274 | } | |
1275 | } | |
1276 | ||
1277 | /* Encoder */ | |
501866a1 | 1278 | if(mov->tracks[0].enc && !(mov->tracks[0].enc->flags & CODEC_FLAG_BITEXACT)) |
69dde1ad | 1279 | { |
b29af723 | 1280 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1281 | put_be32(pb, 0); /* size */ |
1282 | put_tag(pb, "\251enc"); | |
1283 | put_be16(pb, sizeof(LIBAVFORMAT_IDENT) - 1); /* string length */ | |
1284 | put_be16(pb, 0); | |
1285 | put_buffer(pb, LIBAVFORMAT_IDENT, sizeof(LIBAVFORMAT_IDENT) - 1); | |
1286 | updateSize(pb, pos); | |
1287 | } | |
1288 | ||
1289 | if( s->title[0] ) | |
1290 | { | |
b29af723 | 1291 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1292 | put_be32(pb, 0); /* size */ |
1293 | put_tag(pb, "\251nam"); | |
1294 | put_be16(pb, strlen(s->title)); /* string length */ | |
1295 | put_be16(pb, 0); | |
1296 | put_buffer(pb, s->title, strlen(s->title)); | |
1297 | updateSize(pb, pos); | |
1298 | } | |
1299 | ||
1300 | if( s->author[0] ) | |
1301 | { | |
b29af723 | 1302 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1303 | put_be32(pb, 0); /* size */ |
1304 | put_tag(pb, /*"\251aut"*/ "\251day" ); | |
1305 | put_be16(pb, strlen(s->author)); /* string length */ | |
1306 | put_be16(pb, 0); | |
1307 | put_buffer(pb, s->author, strlen(s->author)); | |
1308 | updateSize(pb, pos); | |
1309 | } | |
1310 | ||
1311 | if( s->comment[0] ) | |
1312 | { | |
b29af723 | 1313 | offset_t pos = url_ftell(pb); |
69dde1ad GB |
1314 | put_be32(pb, 0); /* size */ |
1315 | put_tag(pb, "\251des"); | |
1316 | put_be16(pb, strlen(s->comment)); /* string length */ | |
1317 | put_be16(pb, 0); | |
1318 | put_buffer(pb, s->comment, strlen(s->comment)); | |
1319 | updateSize(pb, pos); | |
1320 | } | |
1321 | ||
1322 | return updateSize(pb, pos); | |
1323 | } | |
1324 | ||
dcfdb046 BL |
1325 | |
1326 | static size_t ascii_to_wc (ByteIOContext *pb, char *b, size_t n) | |
1327 | { | |
1328 | size_t i; | |
1329 | unsigned char c; | |
1330 | for (i = 0; i < n - 1; i++) { | |
1331 | c = b[i]; | |
1332 | if (! (0x20 <= c && c <= 0x7f )) | |
1333 | c = 0x3f; /* '?' */ | |
1334 | put_be16(pb, c); | |
1335 | } | |
1336 | put_be16(pb, 0x00); | |
1337 | return 2*n; | |
1338 | } | |
1339 | ||
1340 | static uint16_t language_code (char *str) | |
1341 | { | |
1342 | return ((((str[0]-'a') & 0x1F)<<10) + (((str[1]-'a') & 0x1F)<<5) + ((str[2]-'a') & 0x1F)); | |
1343 | } | |
1344 | ||
1345 | static int mov_write_uuidusmt_tag (ByteIOContext *pb, AVFormatContext *s) | |
1346 | { | |
1347 | size_t len, size; | |
1348 | offset_t pos, curpos; | |
1349 | ||
1350 | size = 0; | |
1351 | if (s->title[0]) { | |
1352 | pos = url_ftell(pb); | |
1353 | put_be32(pb, 0); /* size placeholder*/ | |
1354 | put_tag(pb, "uuid"); | |
1355 | put_tag(pb, "USMT"); | |
1356 | put_be32(pb, 0x21d24fce ); /* 96 bit UUID */ | |
1357 | put_be32(pb, 0xbb88695c ); | |
1358 | put_be32(pb, 0xfac9c740 ); | |
1359 | size += 24; | |
1360 | ||
1361 | put_be32(pb, 0); /* size placeholder*/ | |
1362 | put_tag(pb, "MTDT"); | |
1363 | put_be16(pb, 1); | |
1364 | size += 10; | |
1365 | ||
1366 | // Title | |
1367 | len = strlen(s->title)+1; | |
1368 | put_be16(pb, len*2+10); /* size */ | |
1369 | put_be32(pb, 0x01); /* type */ | |
1370 | put_be16(pb, language_code("und")); /* language */ | |
1371 | put_be16(pb, 0x01); /* ? */ | |
1372 | ascii_to_wc (pb, s->title, len); | |
1373 | size += len*2+10; | |
1374 | ||
1375 | // size | |
1376 | curpos = url_ftell(pb); | |
1377 | url_fseek(pb, pos, SEEK_SET); | |
1378 | put_be32(pb, size); | |
1379 | url_fseek(pb, pos+24, SEEK_SET); | |
1380 | put_be32(pb, size-24); | |
1381 | url_fseek(pb, curpos, SEEK_SET); | |
1382 | } | |
1383 | ||
1384 | return size; | |
1385 | } | |
1386 | ||
69dde1ad GB |
1387 | static int mov_write_moov_tag(ByteIOContext *pb, MOVContext *mov, |
1388 | AVFormatContext *s) | |
1cb5f7fd | 1389 | { |
b29af723 MN |
1390 | int i; |
1391 | offset_t pos = url_ftell(pb); | |
1cb5f7fd MN |
1392 | put_be32(pb, 0); /* size placeholder*/ |
1393 | put_tag(pb, "moov"); | |
1394 | mov->timescale = globalTimescale; | |
1395 | ||
1396 | for (i=0; i<MAX_STREAMS; i++) { | |
e45ccf79 GB |
1397 | if(mov->tracks[i].entry <= 0) continue; |
1398 | ||
1399 | if(mov->tracks[i].enc->codec_type == CODEC_TYPE_VIDEO) { | |
c0df9d75 MN |
1400 | mov->tracks[i].timescale = mov->tracks[i].enc->time_base.den; |
1401 | mov->tracks[i].sampleDuration = mov->tracks[i].enc->time_base.num; | |
e45ccf79 GB |
1402 | } |
1403 | else if(mov->tracks[i].enc->codec_type == CODEC_TYPE_AUDIO) { | |
1404 | /* If AMR, track timescale = 8000, AMR_WB = 16000 */ | |
1405 | if(mov->tracks[i].enc->codec_id == CODEC_ID_AMR_NB) { | |
1406 | mov->tracks[i].sampleDuration = 160; // Bytes per chunk | |
1407 | mov->tracks[i].timescale = 8000; | |
1cb5f7fd | 1408 | } |
e45ccf79 GB |
1409 | else { |
1410 | mov->tracks[i].timescale = mov->tracks[i].enc->sample_rate; | |
1411 | mov->tracks[i].sampleDuration = mov->tracks[i].enc->frame_size; | |
1cb5f7fd | 1412 | } |
1cb5f7fd | 1413 | } |
e45ccf79 | 1414 | |
115329f1 | 1415 | mov->tracks[i].trackDuration = |
e45ccf79 GB |
1416 | mov->tracks[i].sampleCount * mov->tracks[i].sampleDuration; |
1417 | mov->tracks[i].time = mov->time; | |
1418 | mov->tracks[i].trackID = i+1; | |
1cb5f7fd MN |
1419 | } |
1420 | ||
6e6d6dc0 MN |
1421 | mov_write_mvhd_tag(pb, mov); |
1422 | //mov_write_iods_tag(pb, mov); | |
1cb5f7fd MN |
1423 | for (i=0; i<MAX_STREAMS; i++) { |
1424 | if(mov->tracks[i].entry > 0) { | |
6e6d6dc0 | 1425 | mov_write_trak_tag(pb, &(mov->tracks[i])); |
1cb5f7fd MN |
1426 | } |
1427 | } | |
1428 | ||
dcfdb046 BL |
1429 | if (mov->mode == MODE_PSP) |
1430 | mov_write_uuidusmt_tag(pb, s); | |
1431 | else | |
69dde1ad GB |
1432 | mov_write_udta_tag(pb, mov, s); |
1433 | ||
6e6d6dc0 | 1434 | return updateSize(pb, pos); |
1cb5f7fd MN |
1435 | } |
1436 | ||
f578f938 | 1437 | int mov_write_mdat_tag(ByteIOContext *pb, MOVContext* mov) |
1cb5f7fd | 1438 | { |
b29af723 MN |
1439 | put_be32(pb, 8); // placeholder for extended size field (64 bit) |
1440 | put_tag(pb, "wide"); | |
1441 | ||
115329f1 | 1442 | mov->mdat_pos = url_ftell(pb); |
1cb5f7fd MN |
1443 | put_be32(pb, 0); /* size placeholder*/ |
1444 | put_tag(pb, "mdat"); | |
1445 | return 0; | |
1446 | } | |
1447 | ||
1448 | /* TODO: This needs to be more general */ | |
dcfdb046 | 1449 | static void mov_write_ftyp_tag (ByteIOContext *pb, AVFormatContext *s) |
1cb5f7fd | 1450 | { |
69dde1ad GB |
1451 | MOVContext *mov = s->priv_data; |
1452 | ||
1cb5f7fd MN |
1453 | put_be32(pb, 0x14 ); /* size */ |
1454 | put_tag(pb, "ftyp"); | |
e45ccf79 | 1455 | |
69dde1ad | 1456 | if ( mov->mode == MODE_3GP ) |
e45ccf79 | 1457 | put_tag(pb, "3gp4"); |
8536ab89 | 1458 | else if ( mov->mode == MODE_3G2 ) |
1459 | put_tag(pb, "3g2a"); | |
8af18154 | 1460 | else if ( mov->mode == MODE_PSP ) |
1461 | put_tag(pb, "MSNV"); | |
26f86eaf | 1462 | else if ( mov->mode == MODE_MP4 ) |
e45ccf79 | 1463 | put_tag(pb, "isom"); |
26f86eaf BC |
1464 | else |
1465 | put_tag(pb, "qt "); | |
e45ccf79 | 1466 | |
1cb5f7fd | 1467 | put_be32(pb, 0x200 ); |
e45ccf79 | 1468 | |
69dde1ad | 1469 | if ( mov->mode == MODE_3GP ) |
e45ccf79 | 1470 | put_tag(pb, "3gp4"); |
8536ab89 | 1471 | else if ( mov->mode == MODE_3G2 ) |
1472 | put_tag(pb, "3g2a"); | |
8af18154 | 1473 | else if ( mov->mode == MODE_PSP ) |
1474 | put_tag(pb, "MSNV"); | |
26f86eaf | 1475 | else if ( mov->mode == MODE_MP4 ) |
e45ccf79 | 1476 | put_tag(pb, "mp41"); |
26f86eaf BC |
1477 | else |
1478 | put_tag(pb, "qt "); | |
1cb5f7fd MN |
1479 | } |
1480 | ||
0c716ab7 | 1481 | static void mov_write_uuidprof_tag(ByteIOContext *pb, AVFormatContext *s) |
8af18154 | 1482 | { |
dcfdb046 BL |
1483 | AVCodecContext *VideoCodec = s->streams[0]->codec; |
1484 | AVCodecContext *AudioCodec = s->streams[1]->codec; | |
1485 | int AudioRate = AudioCodec->sample_rate; | |
1486 | int FrameRate = ((VideoCodec->time_base.den) * (0x10000))/ (VideoCodec->time_base.num); | |
8af18154 | 1487 | |
1488 | put_be32(pb, 0x94 ); /* size */ | |
1489 | put_tag(pb, "uuid"); | |
1490 | put_tag(pb, "PROF"); | |
1491 | ||
1492 | put_be32(pb, 0x21d24fce ); /* 96 bit UUID */ | |
1493 | put_be32(pb, 0xbb88695c ); | |
1494 | put_be32(pb, 0xfac9c740 ); | |
1495 | ||
1496 | put_be32(pb, 0x0 ); /* ? */ | |
1497 | put_be32(pb, 0x3 ); /* 3 sections ? */ | |
1498 | ||
1499 | put_be32(pb, 0x14 ); /* size */ | |
1500 | put_tag(pb, "FPRF"); | |
1501 | put_be32(pb, 0x0 ); /* ? */ | |
1502 | put_be32(pb, 0x0 ); /* ? */ | |
1503 | put_be32(pb, 0x0 ); /* ? */ | |
1504 | ||
1505 | put_be32(pb, 0x2c ); /* size */ | |
1506 | put_tag(pb, "APRF"); /* audio */ | |
1507 | put_be32(pb, 0x0 ); | |
dcfdb046 | 1508 | put_be32(pb, 0x2 ); /* TrackID */ |
8af18154 | 1509 | put_tag(pb, "mp4a"); |
1510 | put_be32(pb, 0x20f ); | |
1511 | put_be32(pb, 0x0 ); | |
dcfdb046 BL |
1512 | put_be32(pb, AudioCodec->bit_rate / 1000); |
1513 | put_be32(pb, AudioCodec->bit_rate / 1000); | |
1514 | put_be32(pb, AudioRate ); | |
1515 | put_be32(pb, AudioCodec->channels ); | |
8af18154 | 1516 | |
1517 | put_be32(pb, 0x34 ); /* size */ | |
1518 | put_tag(pb, "VPRF"); /* video */ | |
1519 | put_be32(pb, 0x0 ); | |
dcfdb046 | 1520 | put_be32(pb, 0x1 ); /* TrackID */ |
8af18154 | 1521 | put_tag(pb, "mp4v"); |
1522 | put_be32(pb, 0x103 ); | |
1523 | put_be32(pb, 0x0 ); | |
dcfdb046 BL |
1524 | put_be32(pb, VideoCodec->bit_rate / 1000); |
1525 | put_be32(pb, VideoCodec->bit_rate / 1000); | |
1526 | put_be32(pb, FrameRate); | |
1527 | put_be32(pb, FrameRate); | |
1528 | put_be16(pb, VideoCodec->width); | |
1529 | put_be16(pb, VideoCodec->height); | |
1530 | put_be32(pb, 0x010001); /* ? */ | |
8af18154 | 1531 | } |
1532 | ||
1cb5f7fd MN |
1533 | static int mov_write_header(AVFormatContext *s) |
1534 | { | |
1535 | ByteIOContext *pb = &s->pb; | |
69dde1ad GB |
1536 | MOVContext *mov = s->priv_data; |
1537 | int i; | |
1cb5f7fd | 1538 | |
1522767d BC |
1539 | /* Default mode == MP4 */ |
1540 | mov->mode = MODE_MP4; | |
1541 | ||
1542 | if (s->oformat != NULL) { | |
1543 | if (!strcmp("3gp", s->oformat->name)) mov->mode = MODE_3GP; | |
1544 | else if (!strcmp("3g2", s->oformat->name)) mov->mode = MODE_3G2; | |
1545 | else if (!strcmp("mov", s->oformat->name)) mov->mode = MODE_MOV; | |
1546 | else if (!strcmp("psp", s->oformat->name)) mov->mode = MODE_PSP; | |
1547 | ||
26f86eaf | 1548 | mov_write_ftyp_tag(pb,s); |
1522767d BC |
1549 | if ( mov->mode == MODE_PSP ) { |
1550 | if ( s->nb_streams != 2 ) { | |
1551 | av_log(s, AV_LOG_ERROR, "PSP mode need one video and one audio stream\n"); | |
1552 | return -1; | |
1553 | } | |
1554 | mov_write_uuidprof_tag(pb,s); | |
1555 | } | |
1556 | } | |
1557 | ||
ffdd57d4 | 1558 | for(i=0; i<s->nb_streams; i++){ |
01f4895c | 1559 | AVCodecContext *c= s->streams[i]->codec; |
ffdd57d4 | 1560 | |
b4712e3c BC |
1561 | if(c->codec_type == CODEC_TYPE_VIDEO){ |
1562 | av_set_pts_info(s->streams[i], 64, 1, c->time_base.den); | |
ffdd57d4 MN |
1563 | if (!codec_get_tag(codec_movvideo_tags, c->codec_id)){ |
1564 | if(!codec_get_tag(codec_bmp_tags, c->codec_id)) | |
1565 | return -1; | |
1566 | else | |
1567 | av_log(s, AV_LOG_INFO, "Warning, using MS style video codec tag, the file may be unplayable!\n"); | |
1568 | } | |
1569 | }else if(c->codec_type == CODEC_TYPE_AUDIO){ | |
b4712e3c | 1570 | av_set_pts_info(s->streams[i], 64, 1, c->sample_rate); |
ffdd57d4 MN |
1571 | if (!codec_get_tag(codec_movaudio_tags, c->codec_id)){ |
1572 | if(!codec_get_tag(codec_wav_tags, c->codec_id)) | |
1573 | return -1; | |
1574 | else | |
1575 | av_log(s, AV_LOG_INFO, "Warning, using MS style audio codec tag, the file may be unplayable!\n"); | |
1576 | } | |
1577 | } | |
1522767d | 1578 | mov->tracks[i].language = ff_mov_iso639_to_lang(s->streams[i]->language, mov->mode != MODE_MOV); |
69dde1ad GB |
1579 | } |
1580 | ||
1581 | for (i=0; i<MAX_STREAMS; i++) { | |
1582 | mov->tracks[i].mode = mov->mode; | |
f578f938 TR |
1583 | } |
1584 | ||
1cb5f7fd MN |
1585 | put_flush_packet(pb); |
1586 | ||
1587 | return 0; | |
1588 | } | |
1589 | ||
e928649b | 1590 | static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) |
1cb5f7fd MN |
1591 | { |
1592 | MOVContext *mov = s->priv_data; | |
1593 | ByteIOContext *pb = &s->pb; | |
01f4895c | 1594 | AVCodecContext *enc = s->streams[pkt->stream_index]->codec; |
e928649b | 1595 | MOVTrack* trk = &mov->tracks[pkt->stream_index]; |
1a31840c | 1596 | int cl, id; |
e45ccf79 | 1597 | unsigned int samplesInChunk = 0; |
e928649b | 1598 | int size= pkt->size; |
1cb5f7fd | 1599 | |
e45ccf79 GB |
1600 | if (url_is_streamed(&s->pb)) return 0; /* Can't handle that */ |
1601 | if (!size) return 0; /* Discard 0 sized packets */ | |
1cb5f7fd | 1602 | |
e45ccf79 GB |
1603 | if (enc->codec_type == CODEC_TYPE_VIDEO ) { |
1604 | samplesInChunk = 1; | |
1605 | } | |
1606 | else if (enc->codec_type == CODEC_TYPE_AUDIO ) { | |
1607 | if( enc->codec_id == CODEC_ID_AMR_NB) { | |
f578f938 | 1608 | /* We must find out how many AMR blocks there are in one packet */ |
e45ccf79 GB |
1609 | static uint16_t packed_size[16] = |
1610 | {13, 14, 16, 18, 20, 21, 27, 32, 6, 0, 0, 0, 0, 0, 0, 0}; | |
1611 | int len = 0; | |
1612 | ||
1613 | while (len < size && samplesInChunk < 100) { | |
e928649b | 1614 | len += packed_size[(pkt->data[len] >> 3) & 0x0F]; |
e45ccf79 | 1615 | samplesInChunk++; |
f578f938 | 1616 | } |
1cb5f7fd | 1617 | } |
e45ccf79 GB |
1618 | else if(enc->codec_id == CODEC_ID_PCM_ALAW) { |
1619 | samplesInChunk = size/enc->channels; | |
1cb5f7fd | 1620 | } |
bb270c08 DB |
1621 | else if(enc->codec_id == CODEC_ID_PCM_S16BE || enc->codec_id == CODEC_ID_PCM_S16LE) { |
1622 | samplesInChunk = size/(2*enc->channels); | |
115329f1 | 1623 | } |
e45ccf79 GB |
1624 | else { |
1625 | samplesInChunk = 1; | |
1cb5f7fd | 1626 | } |
e45ccf79 GB |
1627 | } |
1628 | ||
1629 | if ((enc->codec_id == CODEC_ID_MPEG4 || enc->codec_id == CODEC_ID_AAC) | |
1630 | && trk->vosLen == 0) { | |
21f52609 | 1631 | // assert(enc->extradata_size); |
e45ccf79 GB |
1632 | |
1633 | trk->vosLen = enc->extradata_size; | |
1634 | trk->vosData = av_malloc(trk->vosLen); | |
1635 | memcpy(trk->vosData, enc->extradata, trk->vosLen); | |
1636 | } | |
1637 | ||
c1b8e6d8 BC |
1638 | if (enc->codec_id == CODEC_ID_H264) { |
1639 | if (trk->vosLen == 0) { | |
1640 | /* copy extradata */ | |
1641 | trk->vosLen = enc->extradata_size; | |
1642 | trk->vosData = av_malloc(trk->vosLen); | |
1643 | memcpy(trk->vosData, enc->extradata, trk->vosLen); | |
1644 | } | |
1645 | if (*(uint8_t *)trk->vosData != 1) { | |
1646 | /* from x264 or from bytestream h264 */ | |
1647 | /* nal reformating needed */ | |
1648 | avc_parse_nal_units(&pkt->data, &pkt->size); | |
1649 | assert(pkt->size); | |
1650 | size = pkt->size; | |
1651 | } | |
1652 | } | |
1653 | ||
e45ccf79 GB |
1654 | cl = trk->entry / MOV_INDEX_CLUSTER_SIZE; |
1655 | id = trk->entry % MOV_INDEX_CLUSTER_SIZE; | |
1656 | ||
1657 | if (trk->ents_allocated <= trk->entry) { | |
115329f1 | 1658 | trk->cluster = av_realloc(trk->cluster, (cl+1)*sizeof(void*)); |
e45ccf79 GB |
1659 | if (!trk->cluster) |
1660 | return -1; | |
1661 | trk->cluster[cl] = av_malloc(MOV_INDEX_CLUSTER_SIZE*sizeof(MOVIentry)); | |
1662 | if (!trk->cluster[cl]) | |
1663 | return -1; | |
1664 | trk->ents_allocated += MOV_INDEX_CLUSTER_SIZE; | |
1665 | } | |
1666 | if (mov->mdat_written == 0) { | |
1667 | mov_write_mdat_tag(pb, mov); | |
1668 | mov->mdat_written = 1; | |
8272de4e | 1669 | mov->time = s->timestamp + 0x7C25B080; //1970 based -> 1904 based |
e45ccf79 GB |
1670 | } |
1671 | ||
69dde1ad | 1672 | trk->cluster[cl][id].pos = url_ftell(pb); |
e45ccf79 GB |
1673 | trk->cluster[cl][id].samplesInChunk = samplesInChunk; |
1674 | trk->cluster[cl][id].size = size; | |
1675 | trk->cluster[cl][id].entries = samplesInChunk; | |
1676 | if(enc->codec_type == CODEC_TYPE_VIDEO) { | |
b4712e3c BC |
1677 | if (pkt->dts != pkt->pts) |
1678 | trk->hasBframes = 1; | |
1679 | trk->cluster[cl][id].cts = pkt->pts - pkt->dts; | |
e928649b MN |
1680 | trk->cluster[cl][id].key_frame = !!(pkt->flags & PKT_FLAG_KEY); |
1681 | if(trk->cluster[cl][id].key_frame) | |
f578f938 | 1682 | trk->hasKeyframes = 1; |
1cb5f7fd | 1683 | } |
e45ccf79 GB |
1684 | trk->enc = enc; |
1685 | trk->entry++; | |
1686 | trk->sampleCount += samplesInChunk; | |
1687 | trk->mdat_size += size; | |
1688 | ||
e928649b | 1689 | put_buffer(pb, pkt->data, size); |
1cb5f7fd MN |
1690 | |
1691 | put_flush_packet(pb); | |
1692 | return 0; | |
1693 | } | |
1694 | ||
1695 | static int mov_write_trailer(AVFormatContext *s) | |
1696 | { | |
1697 | MOVContext *mov = s->priv_data; | |
1698 | ByteIOContext *pb = &s->pb; | |
1699 | int res = 0; | |
b29af723 MN |
1700 | int i; |
1701 | uint64_t j; | |
1cb5f7fd | 1702 | |
69dde1ad | 1703 | offset_t moov_pos = url_ftell(pb); |
1cb5f7fd MN |
1704 | |
1705 | /* Write size of mdat tag */ | |
69dde1ad | 1706 | for (i=0, j=0; i<MAX_STREAMS; i++) { |
1cb5f7fd MN |
1707 | if(mov->tracks[i].ents_allocated > 0) { |
1708 | j += mov->tracks[i].mdat_size; | |
1709 | } | |
1710 | } | |
b29af723 MN |
1711 | if (j+8 <= UINT32_MAX) { |
1712 | url_fseek(pb, mov->mdat_pos, SEEK_SET); | |
1713 | put_be32(pb, j+8); | |
1714 | } else { | |
1715 | /* overwrite 'wide' placeholder atom */ | |
1716 | url_fseek(pb, mov->mdat_pos - 8, SEEK_SET); | |
1717 | put_be32(pb, 1); /* special value: real atom size will be 64 bit value after tag field */ | |
1718 | put_tag(pb, "mdat"); | |
1719 | put_be64(pb, j+16); | |
1720 | } | |
69dde1ad | 1721 | url_fseek(pb, moov_pos, SEEK_SET); |
1cb5f7fd | 1722 | |
69dde1ad | 1723 | mov_write_moov_tag(pb, mov, s); |
1cb5f7fd MN |
1724 | |
1725 | for (i=0; i<MAX_STREAMS; i++) { | |
1726 | for (j=0; j<mov->tracks[i].ents_allocated/MOV_INDEX_CLUSTER_SIZE; j++) { | |
1727 | av_free(mov->tracks[i].cluster[j]); | |
1728 | } | |
1729 | av_free(mov->tracks[i].cluster); | |
ec7d0d2e GB |
1730 | if( mov->tracks[i].vosLen ) av_free( mov->tracks[i].vosData ); |
1731 | ||
1cb5f7fd MN |
1732 | mov->tracks[i].cluster = NULL; |
1733 | mov->tracks[i].ents_allocated = mov->tracks[i].entry = 0; | |
1734 | } | |
69dde1ad | 1735 | |
1cb5f7fd MN |
1736 | put_flush_packet(pb); |
1737 | ||
1738 | return res; | |
1739 | } | |
1740 | ||
1741 | static AVOutputFormat mov_oformat = { | |
1742 | "mov", | |
1743 | "mov format", | |
1744 | NULL, | |
1745 | "mov", | |
1746 | sizeof(MOVContext), | |
69dde1ad | 1747 | CODEC_ID_AAC, |
2187d948 | 1748 | CODEC_ID_MPEG4, |
1cb5f7fd MN |
1749 | mov_write_header, |
1750 | mov_write_packet, | |
1751 | mov_write_trailer, | |
c64d476c | 1752 | .flags = AVFMT_GLOBALHEADER, |
1cb5f7fd MN |
1753 | }; |
1754 | ||
1755 | static AVOutputFormat _3gp_oformat = { | |
1756 | "3gp", | |
1757 | "3gp format", | |
1758 | NULL, | |
1759 | "3gp", | |
1760 | sizeof(MOVContext), | |
1761 | CODEC_ID_AMR_NB, | |
1762 | CODEC_ID_H263, | |
1763 | mov_write_header, | |
1764 | mov_write_packet, | |
1765 | mov_write_trailer, | |
c64d476c | 1766 | .flags = AVFMT_GLOBALHEADER, |
1cb5f7fd MN |
1767 | }; |
1768 | ||
1769 | static AVOutputFormat mp4_oformat = { | |
1770 | "mp4", | |
1771 | "mp4 format", | |
4cb3f3b6 DC |
1772 | "application/mp4", |
1773 | "mp4,m4a", | |
1cb5f7fd MN |
1774 | sizeof(MOVContext), |
1775 | CODEC_ID_AAC, | |
1776 | CODEC_ID_MPEG4, | |
1777 | mov_write_header, | |
1778 | mov_write_packet, | |
1779 | mov_write_trailer, | |
c64d476c | 1780 | .flags = AVFMT_GLOBALHEADER, |
1cb5f7fd MN |
1781 | }; |
1782 | ||
8af18154 | 1783 | static AVOutputFormat psp_oformat = { |
1784 | "psp", | |
1785 | "psp mp4 format", | |
1786 | NULL, | |
1787 | "mp4,psp", | |
1788 | sizeof(MOVContext), | |
1789 | CODEC_ID_AAC, | |
1790 | CODEC_ID_MPEG4, | |
1791 | mov_write_header, | |
1792 | mov_write_packet, | |
1793 | mov_write_trailer, | |
dcfdb046 | 1794 | .flags = AVFMT_GLOBALHEADER, |
8af18154 | 1795 | }; |
1796 | ||
8536ab89 | 1797 | static AVOutputFormat _3g2_oformat = { |
1798 | "3g2", | |
1799 | "3gp2 format", | |
1800 | NULL, | |
1801 | "3g2", | |
1802 | sizeof(MOVContext), | |
1803 | CODEC_ID_AMR_NB, | |
1804 | CODEC_ID_H263, | |
1805 | mov_write_header, | |
1806 | mov_write_packet, | |
1807 | mov_write_trailer, | |
c64d476c | 1808 | .flags = AVFMT_GLOBALHEADER, |
8536ab89 | 1809 | }; |
1810 | ||
1cb5f7fd MN |
1811 | int movenc_init(void) |
1812 | { | |
1813 | av_register_output_format(&mov_oformat); | |
1814 | av_register_output_format(&_3gp_oformat); | |
1815 | av_register_output_format(&mp4_oformat); | |
8af18154 | 1816 | av_register_output_format(&psp_oformat); |
8536ab89 | 1817 | av_register_output_format(&_3g2_oformat); |
1cb5f7fd MN |
1818 | return 0; |
1819 | } |