Newer
Older
Michael Niedermayer
committed
/*
* MOV, 3GP, MP4 encoder.
* Copyright (c) 2003 Thomas Raivio.
Gildas Bazin
committed
* Copyright (c) 2004 Gildas Bazin <gbazin at videolan dot org>.
Michael Niedermayer
committed
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "avformat.h"
Thomas Raivio
committed
#include "avi.h"
Michael Niedermayer
committed
#include "avio.h"
Michael Niedermayer
committed
#undef NDEBUG
#include <assert.h>
Michael Niedermayer
committed
#define MOV_INDEX_CLUSTER_SIZE 16384
#define globalTimescale 1000
Gildas Bazin
committed
#define MODE_MP4 0
#define MODE_MOV 1
#define MODE_3GP 2
Michael Niedermayer
committed
typedef struct MOVIentry {
unsigned int flags, pos, size;
unsigned int samplesInChunk;
Thomas Raivio
committed
char key_frame;
Michael Niedermayer
committed
unsigned int entries;
} MOVIentry;
typedef struct MOVIndex {
Gildas Bazin
committed
int mode;
Michael Niedermayer
committed
int entry;
int mdat_size;
int ents_allocated;
long timescale;
long time;
Thomas Raivio
committed
long trackDuration;
long sampleCount;
long sampleDuration;
Thomas Raivio
committed
int hasKeyframes;
Michael Niedermayer
committed
int trackID;
AVCodecContext *enc;
int vosLen;
Michael Niedermayer
committed
uint8_t *vosData;
Michael Niedermayer
committed
MOVIentry** cluster;
} MOVTrack;
typedef struct {
Gildas Bazin
committed
int mode;
Michael Niedermayer
committed
long time;
int nb_streams;
Thomas Raivio
committed
int mdat_written;
offset_t mdat_pos;
Michael Niedermayer
committed
long timescale;
MOVTrack tracks[MAX_STREAMS];
} MOVContext;
static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack* track);
Michael Niedermayer
committed
//FIXME supprt 64bit varaint with wide placeholders
static int updateSize (ByteIOContext *pb, int pos)
Michael Niedermayer
committed
{
long curpos = url_ftell(pb);
url_fseek(pb, pos, SEEK_SET);
Michael Niedermayer
committed
put_be32(pb, curpos - pos); /* rewrite size */
Michael Niedermayer
committed
url_fseek(pb, curpos, SEEK_SET);
Michael Niedermayer
committed
return curpos - pos;
Michael Niedermayer
committed
}
/* Chunk offset atom */
Michael Niedermayer
committed
static int mov_write_stco_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
int i;
Thomas Raivio
committed
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
Michael Niedermayer
committed
put_tag(pb, "stco");
put_be32(pb, 0); /* version & flags */
put_be32(pb, track->entry); /* entry count */
for (i=0; i<track->entry; i++) {
int cl = i / MOV_INDEX_CLUSTER_SIZE;
int id = i % MOV_INDEX_CLUSTER_SIZE;
put_be32(pb, track->cluster[cl][id].pos);
}
Thomas Raivio
committed
return updateSize (pb, pos);
Michael Niedermayer
committed
}
/* Sample size atom */
Michael Niedermayer
committed
static int mov_write_stsz_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Thomas Raivio
committed
int equalChunks = 1;
int i, j, entries = 0, tst = -1, oldtst = -1;
Michael Niedermayer
committed
Thomas Raivio
committed
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
Michael Niedermayer
committed
put_tag(pb, "stsz");
put_be32(pb, 0); /* version & flags */
Thomas Raivio
committed
for (i=0; i<track->entry; i++) {
int cl = i / MOV_INDEX_CLUSTER_SIZE;
int id = i % MOV_INDEX_CLUSTER_SIZE;
tst = track->cluster[cl][id].size/track->cluster[cl][id].entries;
if(oldtst != -1 && tst != oldtst) {
equalChunks = 0;
Thomas Raivio
committed
}
oldtst = tst;
entries += track->cluster[cl][id].entries;
Thomas Raivio
committed
}
if (equalChunks) {
int sSize = track->cluster[0][0].size/track->cluster[0][0].entries;
Thomas Raivio
committed
put_be32(pb, sSize); // sample size
put_be32(pb, entries); // sample count
Michael Niedermayer
committed
}
Thomas Raivio
committed
else {
put_be32(pb, 0); // sample size
put_be32(pb, entries); // sample count
Thomas Raivio
committed
for (i=0; i<track->entry; i++) {
Michael Niedermayer
committed
int cl = i / MOV_INDEX_CLUSTER_SIZE;
int id = i % MOV_INDEX_CLUSTER_SIZE;
for ( j=0; j<track->cluster[cl][id].entries; j++) {
put_be32(pb, track->cluster[cl][id].size /
track->cluster[cl][id].entries);
}
Michael Niedermayer
committed
}
}
Thomas Raivio
committed
return updateSize (pb, pos);
Michael Niedermayer
committed
}
/* Sample to chunk atom */
Michael Niedermayer
committed
static int mov_write_stsc_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Thomas Raivio
committed
int index = 0, oldval = -1, i, entryPos, curpos;
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
Michael Niedermayer
committed
put_tag(pb, "stsc");
put_be32(pb, 0); // version & flags
Thomas Raivio
committed
entryPos = url_ftell(pb);
put_be32(pb, track->entry); // entry count
for (i=0; i<track->entry; i++) {
int cl = i / MOV_INDEX_CLUSTER_SIZE;
int id = i % MOV_INDEX_CLUSTER_SIZE;
if(oldval != track->cluster[cl][id].samplesInChunk)
Thomas Raivio
committed
{
Michael Niedermayer
committed
put_be32(pb, i+1); // first chunk
put_be32(pb, track->cluster[cl][id].samplesInChunk); // samples per chunk
Michael Niedermayer
committed
put_be32(pb, 0x1); // sample description index
oldval = track->cluster[cl][id].samplesInChunk;
Thomas Raivio
committed
index++;
Michael Niedermayer
committed
}
}
Thomas Raivio
committed
curpos = url_ftell(pb);
url_fseek(pb, entryPos, SEEK_SET);
put_be32(pb, index); // rewrite size
url_fseek(pb, curpos, SEEK_SET);
Michael Niedermayer
committed
Thomas Raivio
committed
return updateSize (pb, pos);
Michael Niedermayer
committed
}
/* Sync sample atom */
Thomas Raivio
committed
static int mov_write_stss_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Thomas Raivio
committed
long curpos;
int i, index = 0, entryPos;
int pos = url_ftell(pb);
put_be32(pb, 0); // size
Michael Niedermayer
committed
put_tag(pb, "stss");
Thomas Raivio
committed
put_be32(pb, 0); // version & flags
entryPos = url_ftell(pb);
put_be32(pb, track->entry); // entry count
for (i=0; i<track->entry; i++) {
int cl = i / MOV_INDEX_CLUSTER_SIZE;
int id = i % MOV_INDEX_CLUSTER_SIZE;
if(track->cluster[cl][id].key_frame == 1) {
put_be32(pb, i+1);
index++;
}
}
curpos = url_ftell(pb);
url_fseek(pb, entryPos, SEEK_SET);
put_be32(pb, index); // rewrite size
url_fseek(pb, curpos, SEEK_SET);
return updateSize (pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_damr_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
put_be32(pb, 0x11); /* size */
put_tag(pb, "damr");
put_tag(pb, "FFMP");
put_byte(pb, 0);
Thomas Raivio
committed
put_be16(pb, 0x80); /* Mode set (all modes for AMR_NB) */
put_be16(pb, 0xa); /* Mode change period (no restriction) */
//put_be16(pb, 0x81ff); /* Mode set (all modes for AMR_NB) */
//put_be16(pb, 1); /* Mode change period (no restriction) */
Michael Niedermayer
committed
return 0x11;
}
Gildas Bazin
committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
static int mov_write_wave_tag(ByteIOContext *pb, MOVTrack* track)
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "wave");
put_be32(pb, 12); /* size */
put_tag(pb, "frma");
put_tag(pb, "mp4a");
put_be32(pb, 12); /* size */
put_tag(pb, "mp4a");
put_be32(pb, 0);
mov_write_esds_tag(pb, track);
put_be32(pb, 12); /* size */
put_tag(pb, "srcq");
put_be32(pb, 0x40);
put_be32(pb, 8); /* size */
put_be32(pb, 0); /* null tag */
return updateSize (pb, pos);
}
const CodecTag codec_movaudio_tags[] = {
{ CODEC_ID_PCM_MULAW, MKTAG('u', 'l', 'a', 'w') },
{ CODEC_ID_PCM_ALAW, MKTAG('a', 'l', 'a', 'w') },
{ CODEC_ID_ADPCM_IMA_QT, MKTAG('i', 'm', 'a', '4') },
{ CODEC_ID_MACE3, MKTAG('M', 'A', 'C', '3') },
{ CODEC_ID_MACE6, MKTAG('M', 'A', 'C', '6') },
{ CODEC_ID_AAC, MKTAG('m', 'p', '4', 'a') },
{ CODEC_ID_AMR_NB, MKTAG('s', 'a', 'm', 'r') },
{ CODEC_ID_PCM_S16BE, MKTAG('t', 'w', 'o', 's') },
{ CODEC_ID_PCM_S16LE, MKTAG('s', 'o', 'w', 't') },
{ CODEC_ID_MP3, MKTAG('.', 'm', 'p', '3') },
{ 0, 0 },
};
Thomas Raivio
committed
static int mov_write_audio_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
int pos = url_ftell(pb);
int tag;
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
Thomas Raivio
committed
tag = codec_get_tag(codec_movaudio_tags, track->enc->codec_id);
// if no mac fcc found, try with Microsoft tags
if (!tag)
{
int tmp = codec_get_tag(codec_wav_tags, track->enc->codec_id);
tag = MKTAG('m', 's', ((tmp >> 8) & 0xff), (tmp & 0xff));
put_le32(pb, tag); // store it byteswapped
Thomas Raivio
committed
Michael Niedermayer
committed
put_be32(pb, 0); /* Reserved */
put_be16(pb, 0); /* Reserved */
put_be16(pb, 1); /* Data-reference index, XXX == 1 */
Gildas Bazin
committed
/* SoundDescription */
Gildas Bazin
committed
if(track->mode == MODE_MOV && track->enc->codec_id == CODEC_ID_AAC)
put_be16(pb, 1); /* Version 1 */
else
put_be16(pb, 0); /* Version 0 */
put_be16(pb, 0); /* Revision level */
Michael Niedermayer
committed
put_be32(pb, 0); /* Reserved */
Thomas Raivio
committed
put_be16(pb, track->enc->channels); /* Number of channels */
/* TODO: Currently hard-coded to 16-bit, there doesn't seem
to be a good way to get number of bits of audio */
Michael Niedermayer
committed
put_be16(pb, 0x10); /* Reserved */
put_be16(pb, 0); /* compression ID (= 0) */
put_be16(pb, 0); /* packet size (= 0) */
Michael Niedermayer
committed
put_be16(pb, track->timescale); /* Time scale */
put_be16(pb, 0); /* Reserved */
Gildas Bazin
committed
if(track->mode == MODE_MOV && track->enc->codec_id == CODEC_ID_AAC)
{
/* SoundDescription V1 extended info */
put_be32(pb, track->enc->frame_size); /* Samples per packet */
put_be32(pb, 1536); /* Bytes per packet */
put_be32(pb, 2); /* Bytes per frame */
put_be32(pb, 2); /* Bytes per sample */
}
if(track->enc->codec_id == CODEC_ID_AAC) {
if( track->mode == MODE_MOV ) mov_write_wave_tag(pb, track);
else mov_write_esds_tag(pb, track);
}
Thomas Raivio
committed
if(track->enc->codec_id == CODEC_ID_AMR_NB)
mov_write_damr_tag(pb);
Michael Niedermayer
committed
return updateSize (pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_d263_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
put_be32(pb, 0xf); /* size */
put_tag(pb, "d263");
put_tag(pb, "FFMP");
put_be16(pb, 0x0a);
put_byte(pb, 0);
return 0xf;
}
Thomas Raivio
committed
/* TODO: No idea about these values */
static int mov_write_svq3_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
Thomas Raivio
committed
put_be32(pb, 0x15);
put_tag(pb, "SMI ");
put_tag(pb, "SEQH");
put_be32(pb, 0x5);
put_be32(pb, 0xe2c0211d);
put_be32(pb, 0xc0000000);
put_byte(pb, 0);
return 0x15;
Michael Niedermayer
committed
}
static unsigned int descrLength(unsigned int len)
{
if (len < 0x00000080)
return 2 + len;
else if (len < 0x00004000)
return 3 + len;
else if(len < 0x00200000)
return 4 + len;
else
return 5 + len;
}
static void putDescr(ByteIOContext *pb, int tag, int size)
Michael Niedermayer
committed
{
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
uint32_t len;
uint8_t vals[4];
len = size;
vals[3] = (uint8_t)(len & 0x7f);
len >>= 7;
vals[2] = (uint8_t)((len & 0x7f) | 0x80);
len >>= 7;
vals[1] = (uint8_t)((len & 0x7f) | 0x80);
len >>= 7;
vals[0] = (uint8_t)((len & 0x7f) | 0x80);
put_byte(pb, tag); // DescriptorTag
if (size < 0x00000080)
{
put_byte(pb, vals[3]);
}
else if (size < 0x00004000)
{
put_byte(pb, vals[2]);
put_byte(pb, vals[3]);
}
else if (size < 0x00200000)
{
put_byte(pb, vals[1]);
put_byte(pb, vals[2]);
put_byte(pb, vals[3]);
}
else if (size < 0x10000000)
{
put_byte(pb, vals[0]);
put_byte(pb, vals[1]);
put_byte(pb, vals[2]);
put_byte(pb, vals[3]);
}
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack* track) // Basic
Michael Niedermayer
committed
{
int decoderSpecificInfoLen = track->vosLen ? descrLength(track->vosLen):0;
int pos = url_ftell(pb);
put_be32(pb, 0); // size
Michael Niedermayer
committed
put_tag(pb, "esds");
put_be32(pb, 0); // Version
Michael Niedermayer
committed
// ES descriptor
putDescr(pb, 0x03, 3 + descrLength(13 + decoderSpecificInfoLen) +
descrLength(1));
put_be16(pb, 0x0001); // ID (= 1)
Michael Niedermayer
committed
put_byte(pb, 0x00); // flags (= no flags)
// DecoderConfig descriptor
putDescr(pb, 0x04, 13 + decoderSpecificInfoLen);
if(track->enc->codec_id == CODEC_ID_AAC)
put_byte(pb, 0x40); // Object type indication
else if(track->enc->codec_id == CODEC_ID_MPEG4)
put_byte(pb, 0x20); // Object type indication (Visual 14496-2)
if(track->enc->codec_type == CODEC_TYPE_AUDIO)
put_byte(pb, 0x15); // flags (= Audiostream)
else
put_byte(pb, 0x11); // flags (= Visualstream)
Michael Niedermayer
committed
put_byte(pb, 0x0); // Buffersize DB (24 bits)
put_be16(pb, 0x0dd2); // Buffersize DB
// TODO: find real values for these
put_be32(pb, 0x0002e918); // maxbitrate
put_be32(pb, 0x00017e6b); // avg bitrate
if (track->vosLen)
{
// DecoderSpecific info descriptor
putDescr(pb, 0x05, track->vosLen);
put_buffer(pb, track->vosData, track->vosLen);
}
// SL descriptor
putDescr(pb, 0x06, 1);
Michael Niedermayer
committed
put_byte(pb, 0x02);
return updateSize (pb, pos);
Michael Niedermayer
committed
}
const CodecTag codec_movvideo_tags[] = {
{ CODEC_ID_SVQ1, MKTAG('S', 'V', 'Q', '1') },
{ CODEC_ID_SVQ3, MKTAG('S', 'V', 'Q', '3') },
{ CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') },
{ CODEC_ID_H263, MKTAG('s', '2', '6', '3') },
{ CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', ' ') },
{ 0, 0 },
};
Thomas Raivio
committed
static int mov_write_video_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
int pos = url_ftell(pb);
int tag;
Thomas Raivio
committed
put_be32(pb, 0); /* size */
tag = codec_get_tag(codec_movvideo_tags, track->enc->codec_id);
// if no mac fcc found, try with Microsoft tags
if (!tag)
tag = codec_get_tag(codec_bmp_tags, track->enc->codec_id);
put_le32(pb, tag); // store it byteswapped
Michael Niedermayer
committed
Thomas Raivio
committed
put_be32(pb, 0); /* Reserved */
put_be16(pb, 0); /* Reserved */
put_be16(pb, 1); /* Data-reference index */
put_be32(pb, 0); /* Reserved (= 02000c) */
put_be32(pb, 0); /* Reserved ("SVis")*/
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved (400)*/
put_be16(pb, track->enc->width); /* Video width */
put_be16(pb, track->enc->height); /* Video height */
put_be32(pb, 0x00480000); /* Reserved */
put_be32(pb, 0x00480000); /* Reserved */
put_be32(pb, 0); /* Data size (= 0) */
put_be16(pb, 1); /* Frame count (= 1) */
Thomas Raivio
committed
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
put_be32(pb, 0); /* Reserved */
Thomas Raivio
committed
put_be16(pb, 0x18); /* Reserved */
put_be16(pb, 0xffff); /* Reserved */
if(track->enc->codec_id == CODEC_ID_MPEG4)
mov_write_esds_tag(pb, track);
else if(track->enc->codec_id == CODEC_ID_H263)
mov_write_d263_tag(pb);
else if(track->enc->codec_id == CODEC_ID_SVQ3)
mov_write_svq3_tag(pb);
return updateSize (pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_stsd_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
put_tag(pb, "stsd");
put_be32(pb, 0); /* version & flags */
put_be32(pb, 1); /* entry count */
Thomas Raivio
committed
if (track->enc->codec_type == CODEC_TYPE_VIDEO)
mov_write_video_tag(pb, track);
else if (track->enc->codec_type == CODEC_TYPE_AUDIO)
mov_write_audio_tag(pb, track);
Michael Niedermayer
committed
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Thomas Raivio
committed
/* TODO?: Currently all samples/frames seem to have same duration */
/* Time to sample atom */
Michael Niedermayer
committed
static int mov_write_stts_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
put_be32(pb, 0x18); /* size */
put_tag(pb, "stts");
put_be32(pb, 0); /* version & flags */
put_be32(pb, 1); /* entry count */
put_be32(pb, track->sampleCount); /* sample count */
put_be32(pb, track->sampleDuration); /* sample duration */
Michael Niedermayer
committed
return 0x18;
}
Michael Niedermayer
committed
static int mov_write_dref_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
put_be32(pb, 28); /* size */
put_tag(pb, "dref");
put_be32(pb, 0); /* version & flags */
put_be32(pb, 1); /* entry count */
put_be32(pb, 0xc); /* size */
put_tag(pb, "url ");
put_be32(pb, 1); /* version & flags */
return 28;
}
Michael Niedermayer
committed
static int mov_write_stbl_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
put_tag(pb, "stbl");
Michael Niedermayer
committed
mov_write_stsd_tag(pb, track);
mov_write_stts_tag(pb, track);
Thomas Raivio
committed
if (track->enc->codec_type == CODEC_TYPE_VIDEO &&
track->hasKeyframes)
mov_write_stss_tag(pb, track);
Michael Niedermayer
committed
mov_write_stsc_tag(pb, track);
mov_write_stsz_tag(pb, track);
mov_write_stco_tag(pb, track);
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_dinf_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
put_tag(pb, "dinf");
Michael Niedermayer
committed
mov_write_dref_tag(pb);
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_smhd_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
put_be32(pb, 16); /* size */
put_tag(pb, "smhd");
put_be32(pb, 0); /* version & flags */
put_be16(pb, 0); /* reserved (balance, normally = 0) */
put_be16(pb, 0); /* reserved */
return 16;
}
Michael Niedermayer
committed
static int mov_write_vmhd_tag(ByteIOContext *pb)
Michael Niedermayer
committed
{
put_be32(pb, 0x14); /* size (always 0x14) */
put_tag(pb, "vmhd");
put_be32(pb, 0x01); /* version & flags */
put_be64(pb, 0); /* reserved (graphics mode = copy) */
return 0x14;
}
Michael Niedermayer
committed
static int mov_write_hdlr_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
char *descr, *hdlr, *hdlr_type;
Thomas Raivio
committed
int pos = url_ftell(pb);
if (!track) { /* no media --> data handler */
hdlr = "dhlr";
hdlr_type = "url ";
descr = "DataHandler";
} else {
hdlr = (track->mode == MODE_MOV) ? "mhlr" : "\0\0\0\0";
if (track->enc->codec_type == CODEC_TYPE_VIDEO) {
hdlr_type = "vide";
descr = "VideoHandler";
} else {
hdlr_type = "soun";
descr = "SoundHandler";
}
}
Thomas Raivio
committed
put_be32(pb, 0); /* size */
Michael Niedermayer
committed
put_tag(pb, "hdlr");
put_be32(pb, 0); /* Version & flags */
put_buffer(pb, hdlr, 4); /* handler */
put_tag(pb, hdlr_type); /* handler type */
Thomas Raivio
committed
put_be32(pb ,0); /* reserved */
put_be32(pb ,0); /* reserved */
put_be32(pb ,0); /* reserved */
put_byte(pb, strlen(descr)); /* string counter */
put_buffer(pb, descr, strlen(descr)); /* handler description */
return updateSize(pb, pos);
}
static int mov_write_minf_tag(ByteIOContext *pb, MOVTrack* track)
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "minf");
Michael Niedermayer
committed
if(track->enc->codec_type == CODEC_TYPE_VIDEO)
mov_write_vmhd_tag(pb);
Michael Niedermayer
committed
else
mov_write_smhd_tag(pb);
if (track->mode == MODE_MOV) /* FIXME: Why do it for MODE_MOV only ? */
mov_write_hdlr_tag(pb, NULL);
mov_write_dinf_tag(pb);
mov_write_stbl_tag(pb, track);
Thomas Raivio
committed
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_mdhd_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
put_be32(pb, 32); /* size */
put_tag(pb, "mdhd");
put_be32(pb, 0); /* Version & flags */
put_be32(pb, track->time); /* creation time */
put_be32(pb, track->time); /* modification time */
put_be32(pb, track->timescale); /* time scale (sample rate for audio) */
put_be32(pb, track->trackDuration); /* duration */
Michael Niedermayer
committed
put_be16(pb, 0); /* language, 0 = english */
put_be16(pb, 0); /* reserved (quality) */
return 32;
}
Michael Niedermayer
committed
static int mov_write_mdia_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
put_tag(pb, "mdia");
Michael Niedermayer
committed
mov_write_mdhd_tag(pb, track);
mov_write_hdlr_tag(pb, track);
mov_write_minf_tag(pb, track);
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Michael Niedermayer
committed
static int mov_write_tkhd_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Thomas Raivio
committed
int64_t maxTrackLenTemp;
Michael Niedermayer
committed
put_be32(pb, 0x5c); /* size (always 0x5c) */
put_tag(pb, "tkhd");
Thomas Raivio
committed
put_be32(pb, 0xf); /* version & flags (track enabled) */
Michael Niedermayer
committed
put_be32(pb, track->time); /* creation time */
put_be32(pb, track->time); /* modification time */
put_be32(pb, track->trackID); /* track-id */
put_be32(pb, 0); /* reserved */
Thomas Raivio
committed
maxTrackLenTemp = ((int64_t)globalTimescale*(int64_t)track->trackDuration)/(int64_t)track->timescale;
put_be32(pb, (long)maxTrackLenTemp); /* duration */
Michael Niedermayer
committed
put_be32(pb, 0); /* reserved */
put_be32(pb, 0); /* reserved */
put_be32(pb, 0x0); /* reserved (Layer & Alternate group) */
/* Volume, only for audio */
if(track->enc->codec_type == CODEC_TYPE_AUDIO)
put_be16(pb, 0x0100);
else
put_be16(pb, 0);
put_be16(pb, 0); /* reserved */
/* Matrix structure */
put_be32(pb, 0x00010000); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x00010000); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x40000000); /* reserved */
/* Track width and height, for visual only */
if(track->enc->codec_type == CODEC_TYPE_VIDEO) {
Gildas Bazin
committed
double sample_aspect_ratio = av_q2d(track->enc->sample_aspect_ratio);
if( !sample_aspect_ratio ) sample_aspect_ratio = 1;
put_be32(pb, sample_aspect_ratio * track->enc->width*0x10000);
Thomas Raivio
committed
put_be32(pb, track->enc->height*0x10000);
Michael Niedermayer
committed
}
else {
put_be32(pb, 0);
put_be32(pb, 0);
}
return 0x5c;
}
Michael Niedermayer
committed
static int mov_write_trak_tag(ByteIOContext *pb, MOVTrack* track)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size */
put_tag(pb, "trak");
Michael Niedermayer
committed
mov_write_tkhd_tag(pb, track);
mov_write_mdia_tag(pb, track);
return updateSize(pb, pos);
Michael Niedermayer
committed
}
/* TODO: Not sorted out, but not necessary either */
Michael Niedermayer
committed
static int mov_write_iods_tag(ByteIOContext *pb, MOVContext *mov)
Michael Niedermayer
committed
{
put_be32(pb, 0x15); /* size */
put_tag(pb, "iods");
put_be32(pb, 0); /* version & flags */
put_be16(pb, 0x1007);
put_byte(pb, 0);
put_be16(pb, 0x4fff);
put_be16(pb, 0xfffe);
put_be16(pb, 0x01ff);
return 0x15;
}
Michael Niedermayer
committed
static int mov_write_mvhd_tag(ByteIOContext *pb, MOVContext *mov)
Michael Niedermayer
committed
{
int maxTrackID = 1, maxTrackLen = 0, i;
Thomas Raivio
committed
int64_t maxTrackLenTemp;
Michael Niedermayer
committed
put_be32(pb, 0x6c); /* size (always 0x6c) */
put_tag(pb, "mvhd");
put_be32(pb, 0); /* version & flags */
put_be32(pb, mov->time); /* creation time */
put_be32(pb, mov->time); /* modification time */
put_be32(pb, mov->timescale); /* timescale */
for (i=0; i<MAX_STREAMS; i++) {
if(mov->tracks[i].entry > 0) {
Thomas Raivio
committed
maxTrackLenTemp = ((int64_t)globalTimescale*(int64_t)mov->tracks[i].trackDuration)/(int64_t)mov->tracks[i].timescale;
if(maxTrackLen < maxTrackLenTemp)
maxTrackLen = maxTrackLenTemp;
Michael Niedermayer
committed
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
if(maxTrackID < mov->tracks[i].trackID)
maxTrackID = mov->tracks[i].trackID;
}
}
put_be32(pb, maxTrackLen); /* duration of longest track */
put_be32(pb, 0x00010000); /* reserved (preferred rate) 1.0 = normal */
put_be16(pb, 0x0100); /* reserved (preferred volume) 1.0 = normal */
put_be16(pb, 0); /* reserved */
put_be32(pb, 0); /* reserved */
put_be32(pb, 0); /* reserved */
/* Matrix structure */
put_be32(pb, 0x00010000); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x00010000); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x0); /* reserved */
put_be32(pb, 0x40000000); /* reserved */
put_be32(pb, 0); /* reserved (preview time) */
put_be32(pb, 0); /* reserved (preview duration) */
put_be32(pb, 0); /* reserved (poster time) */
put_be32(pb, 0); /* reserved (selection time) */
put_be32(pb, 0); /* reserved (selection duration) */
put_be32(pb, 0); /* reserved (current time) */
put_be32(pb, maxTrackID+1); /* Next track id */
return 0x6c;
}
Gildas Bazin
committed
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
static int mov_write_udta_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int pos = url_ftell(pb);
int i;
put_be32(pb, 0); /* size */
put_tag(pb, "udta");
/* Requirements */
for (i=0; i<MAX_STREAMS; i++) {
if(mov->tracks[i].entry <= 0) continue;
if (mov->tracks[i].enc->codec_id == CODEC_ID_AAC ||
mov->tracks[i].enc->codec_id == CODEC_ID_MPEG4) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251req");
put_be16(pb, sizeof("QuickTime 6.0 or greater") - 1);
put_be16(pb, 0);
put_buffer(pb, "QuickTime 6.0 or greater",
sizeof("QuickTime 6.0 or greater") - 1);
updateSize(pb, pos);
break;
}
}
/* Encoder */
Gildas Bazin
committed
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251enc");
put_be16(pb, sizeof(LIBAVFORMAT_IDENT) - 1); /* string length */
put_be16(pb, 0);
put_buffer(pb, LIBAVFORMAT_IDENT, sizeof(LIBAVFORMAT_IDENT) - 1);
updateSize(pb, pos);
}
if( s->title[0] )
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251nam");
put_be16(pb, strlen(s->title)); /* string length */
put_be16(pb, 0);
put_buffer(pb, s->title, strlen(s->title));
updateSize(pb, pos);
}
if( s->author[0] )
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, /*"\251aut"*/ "\251day" );
put_be16(pb, strlen(s->author)); /* string length */
put_be16(pb, 0);
put_buffer(pb, s->author, strlen(s->author));
updateSize(pb, pos);
}
if( s->comment[0] )
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251des");
put_be16(pb, strlen(s->comment)); /* string length */
put_be16(pb, 0);
put_buffer(pb, s->comment, strlen(s->comment));
updateSize(pb, pos);
}
return updateSize(pb, pos);
}
static int mov_write_moov_tag(ByteIOContext *pb, MOVContext *mov,
AVFormatContext *s)
Michael Niedermayer
committed
{
Michael Niedermayer
committed
int pos, i;
Michael Niedermayer
committed
pos = url_ftell(pb);
put_be32(pb, 0); /* size placeholder*/
put_tag(pb, "moov");
mov->timescale = globalTimescale;
for (i=0; i<MAX_STREAMS; i++) {
if(mov->tracks[i].entry <= 0) continue;
if(mov->tracks[i].enc->codec_type == CODEC_TYPE_VIDEO) {
mov->tracks[i].timescale = mov->tracks[i].enc->frame_rate;
mov->tracks[i].sampleDuration = mov->tracks[i].enc->frame_rate_base;
}
else if(mov->tracks[i].enc->codec_type == CODEC_TYPE_AUDIO) {
/* If AMR, track timescale = 8000, AMR_WB = 16000 */
if(mov->tracks[i].enc->codec_id == CODEC_ID_AMR_NB) {
mov->tracks[i].sampleDuration = 160; // Bytes per chunk
mov->tracks[i].timescale = 8000;
Michael Niedermayer
committed
}
else {
mov->tracks[i].timescale = mov->tracks[i].enc->sample_rate;
mov->tracks[i].sampleDuration = mov->tracks[i].enc->frame_size;
Michael Niedermayer
committed
}
}
mov->tracks[i].trackDuration =
mov->tracks[i].sampleCount * mov->tracks[i].sampleDuration;
mov->tracks[i].time = mov->time;
mov->tracks[i].trackID = i+1;
Michael Niedermayer
committed
}
Michael Niedermayer
committed
mov_write_mvhd_tag(pb, mov);
//mov_write_iods_tag(pb, mov);
Michael Niedermayer
committed
for (i=0; i<MAX_STREAMS; i++) {
if(mov->tracks[i].entry > 0) {
Michael Niedermayer
committed
mov_write_trak_tag(pb, &(mov->tracks[i]));
Michael Niedermayer
committed
}
}
Gildas Bazin
committed
mov_write_udta_tag(pb, mov, s);
Michael Niedermayer
committed
return updateSize(pb, pos);
Michael Niedermayer
committed
}
Thomas Raivio
committed
int mov_write_mdat_tag(ByteIOContext *pb, MOVContext* mov)
Michael Niedermayer
committed
{
Thomas Raivio
committed
mov->mdat_pos = url_ftell(pb);
Michael Niedermayer
committed
put_be32(pb, 0); /* size placeholder*/
put_tag(pb, "mdat");
return 0;
}
/* TODO: This needs to be more general */
int mov_write_ftyp_tag(ByteIOContext *pb, AVFormatContext *s)
Michael Niedermayer
committed
{
Gildas Bazin
committed
MOVContext *mov = s->priv_data;
Michael Niedermayer
committed
put_be32(pb, 0x14 ); /* size */
put_tag(pb, "ftyp");
Gildas Bazin
committed
if ( mov->mode == MODE_3GP )
put_tag(pb, "3gp4");
else
put_tag(pb, "isom");
Michael Niedermayer
committed
put_be32(pb, 0x200 );
Gildas Bazin
committed
if ( mov->mode == MODE_3GP )
put_tag(pb, "3gp4");
else
put_tag(pb, "mp41");
Michael Niedermayer
committed
return 0x14;
}
static int mov_write_header(AVFormatContext *s)
{
ByteIOContext *pb = &s->pb;
Gildas Bazin
committed
MOVContext *mov = s->priv_data;
int i;
Michael Niedermayer
committed
for(i=0; i<s->nb_streams; i++){
AVCodecContext *c= &s->streams[i]->codec;
if (c->codec_type == CODEC_TYPE_VIDEO){
if (!codec_get_tag(codec_movvideo_tags, c->codec_id)){
if(!codec_get_tag(codec_bmp_tags, c->codec_id))
return -1;
else
av_log(s, AV_LOG_INFO, "Warning, using MS style video codec tag, the file may be unplayable!\n");
}
}else if(c->codec_type == CODEC_TYPE_AUDIO){
if (!codec_get_tag(codec_movaudio_tags, c->codec_id)){
if(!codec_get_tag(codec_wav_tags, c->codec_id))
return -1;
else
av_log(s, AV_LOG_INFO, "Warning, using MS style audio codec tag, the file may be unplayable!\n");
}
}
}
Gildas Bazin
committed
/* Default mode == MP4 */
mov->mode = MODE_MP4;
if (s->oformat != NULL) {
if (!strcmp("3gp", s->oformat->name)) mov->mode = MODE_3GP;
else if (!strcmp("mov", s->oformat->name)) mov->mode = MODE_MOV;
if ( mov->mode == MODE_3GP || mov->mode == MODE_MP4 )
mov_write_ftyp_tag(pb,s);
}
for (i=0; i<MAX_STREAMS; i++) {
mov->tracks[i].mode = mov->mode;
Thomas Raivio
committed
}
Michael Niedermayer
committed
put_flush_packet(pb);
return 0;
}
static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
Michael Niedermayer
committed
{
MOVContext *mov = s->priv_data;
ByteIOContext *pb = &s->pb;
AVCodecContext *enc = &s->streams[pkt->stream_index]->codec;
MOVTrack* trk = &mov->tracks[pkt->stream_index];
unsigned int samplesInChunk = 0;
Michael Niedermayer
committed
if (url_is_streamed(&s->pb)) return 0; /* Can't handle that */
if (!size) return 0; /* Discard 0 sized packets */
Michael Niedermayer
committed
if (enc->codec_type == CODEC_TYPE_VIDEO ) {
samplesInChunk = 1;
}
else if (enc->codec_type == CODEC_TYPE_AUDIO ) {
if( enc->codec_id == CODEC_ID_AMR_NB) {
Thomas Raivio
committed
/* We must find out how many AMR blocks there are in one packet */
static uint16_t packed_size[16] =
{13, 14, 16, 18, 20, 21, 27, 32, 6, 0, 0, 0, 0, 0, 0, 0};
int len = 0;
while (len < size && samplesInChunk < 100) {
len += packed_size[(pkt->data[len] >> 3) & 0x0F];
samplesInChunk++;
Thomas Raivio
committed
}
Michael Niedermayer
committed
}
else if(enc->codec_id == CODEC_ID_PCM_ALAW) {
samplesInChunk = size/enc->channels;
Michael Niedermayer
committed
}
else if(enc->codec_id == CODEC_ID_PCM_S16BE || enc->codec_id == CODEC_ID_PCM_S16LE) {
samplesInChunk = size/(2*enc->channels);