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
tjcannell@blueyonder.co.uk
committed
#define MODE_PSP 3 // example working PSP command line:
// ffmpeg -i testinput.avi -f psp -r 14.985 -s 320x240 -b 768 -ar 24000 -ab 32 M4V00001.MP4
Gildas Bazin
committed
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;
tjcannell@blueyonder.co.uk
committed
typedef struct MOVContext {
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
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
239
240
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
Roine Gustafsson
committed
tag = track->enc->codec_tag;
if (!tag)
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 */
if(track->enc->codec_id == CODEC_ID_AAC ||
track->enc->codec_id == CODEC_ID_MP3)
{
put_be16(pb, 0xfffe); /* compression ID (vbr)*/
}
else
{
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
{
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
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;
int pos = url_ftell(pb);
tjcannell@blueyonder.co.uk
committed
void *vosDataBackup=track->vosData;
int vosLenBackup=track->vosLen;
// we should be able to have these passed in, via vosData, then we wouldn't need to attack this routine at all
static const char PSPAACData[]={0x13,0x10};
static const char PSPMP4Data[]={0x00,0x00,0x01,0xB0,0x03,0x00,0x00,0x01,0xB5,0x09,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x20,0x00,0x84,0x5D,0x4C,0x28,0x50,0x20,0xF0,0xA3,0x1F };
if (track->mode == MODE_PSP) // fails on psp if this is not here
{
if (track->enc->codec_id == CODEC_ID_AAC)
{
track->vosLen = 2;
track->vosData = PSPAACData;
}
if (track->enc->codec_id == CODEC_ID_MPEG4)
{
track->vosLen = 28;
track->vosData = PSPMP4Data;
}
}
decoderSpecificInfoLen = track->vosLen ? descrLength(track->vosLen):0;
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
tjcannell@blueyonder.co.uk
committed
put_be32(pb, track->enc->bit_rate); // maxbitrate
put_be32(pb, track->enc->bit_rate); // avg bitrate
Michael Niedermayer
committed
if (track->vosLen)
{
// DecoderSpecific info descriptor
putDescr(pb, 0x05, track->vosLen);
put_buffer(pb, track->vosData, track->vosLen);
}
tjcannell@blueyonder.co.uk
committed
track->vosData = vosDataBackup;
track->vosLen = vosLenBackup;
// 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
{
Roine Gustafsson
committed
char compressor_name[32];
Thomas Raivio
committed
put_be32(pb, 0); /* size */
Roine Gustafsson
committed
tag = track->enc->codec_tag;
if (!tag)
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 */
Roine Gustafsson
committed
put_be16(pb, 0); /* Codec stream version */
put_be16(pb, 0); /* Codec stream revision (=0) */
put_tag(pb, "FFMP"); /* Vendor */
if(track->enc->codec_id == CODEC_ID_RAWVIDEO) {
put_be32(pb, 0); /* Temporal Quality */
put_be32(pb, 0x400); /* Spatial Quality = lossless*/
} else {
put_be32(pb, 0x200); /* Temporal Quality = normal */
put_be32(pb, 0x200); /* Spatial Quality = normal */
}
Thomas Raivio
committed
put_be16(pb, track->enc->width); /* Video width */
put_be16(pb, track->enc->height); /* Video height */
Roine Gustafsson
committed
put_be32(pb, 0x00480000); /* Horizontal resolution 72dpi */
put_be32(pb, 0x00480000); /* Vertical resolution 72dpi */
put_be32(pb, 0); /* Data size (= 0) */
put_be16(pb, 1); /* Frame count (= 1) */
Roine Gustafsson
committed
memset(compressor_name,0,32);
if (track->enc->codec->name)
strncpy(compressor_name,track->enc->codec->name,31);
put_byte(pb, FFMAX(strlen(compressor_name),32) );
put_buffer(pb, compressor_name, 31);
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;
}
tjcannell@blueyonder.co.uk
committed
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
770
771
772
773
774
775
776
777
778
779
780
// This box seems important for the psp playback ... without it the movie seems to hang
static int mov_write_edts_tag(ByteIOContext *pb, MOVTrack *track)
{
int64_t maxTrackLenTemp;
put_be32(pb, 0x24); /* size */
put_tag(pb, "edts");
put_be32(pb, 0x1c); /* size */
put_tag(pb, "elst");
put_be32(pb, 0x0);
put_be32(pb, 0x1);
maxTrackLenTemp = ((int64_t)globalTimescale*(int64_t)track->trackDuration)/(int64_t)track->timescale;
put_be32(pb, (long)maxTrackLenTemp); /* duration ... doesn't seem to effect psp */
put_be32(pb, 0x0);
put_be32(pb, 0x00010000);
return 0x24;
}
// goes at the end of each track! ... Critical for PSP playback ("Incompatible data" without it)
static int mov_write_uuid_tag_psp(ByteIOContext *pb, MOVTrack *mov)
{
put_be32(pb, 0x34); /* size ... reports as 28 in mp4box! */
put_tag(pb, "uuid");
put_tag(pb, "USMT");
put_be32(pb, 0x21d24fce);
put_be32(pb, 0xbb88695c);
put_be32(pb, 0xfac9c740);
put_be32(pb, 0x1c); // another size here!
put_tag(pb, "MTDT");
put_be32(pb, 0x00010012);
put_be32(pb, 0x0a);
put_be32(pb, 0x55c40000);
put_be32(pb, 0x1);
put_be32(pb, 0x0);
return 0x34;
}
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);
tjcannell@blueyonder.co.uk
committed
if (track->mode == MODE_PSP)
mov_write_edts_tag(pb, track); // PSP Movies require edts box
Michael Niedermayer
committed
mov_write_mdia_tag(pb, track);
tjcannell@blueyonder.co.uk
committed
if (track->mode == MODE_PSP)
mov_write_uuid_tag_psp(pb,track); // PSP Movies require this uuid box
Michael Niedermayer
committed
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
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
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;
}
Patrice Bensoussan
committed
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
static int mov_write_itunes_hdlr_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "hdlr");
put_be32(pb, 0);
put_be32(pb, 0);
put_tag(pb, "mdir");
put_tag(pb, "appl");
put_be32(pb, 0);
put_be32(pb, 0);
put_be16(pb, 0);
return updateSize(pb, pos);
}
/* helper function to write a data tag with the specified string as data */
static int mov_write_string_data_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s, const char *data)
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "data");
put_be32(pb, 1);
put_be32(pb, 0);
put_buffer(pb, data, strlen(data));
return updateSize(pb, pos);
}
/* iTunes name of the song/movie */
static int mov_write_nam_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->title[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251nam");
mov_write_string_data_tag(pb, mov, s, s->title);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes name of the artist/performer */
static int mov_write_ART_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->author[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251ART");
// we use the author here as this is the only thing that we have...
mov_write_string_data_tag(pb, mov, s, s->author);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes name of the writer */
static int mov_write_wrt_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->author[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251wrt");
mov_write_string_data_tag(pb, mov, s, s->author);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes name of the album */
static int mov_write_alb_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->album[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251alb");
mov_write_string_data_tag(pb, mov, s, s->album);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes year */
static int mov_write_day_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
char year[5];
int size = 0;
if ( s->year ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251day");
snprintf(year, 5, "%04d", s->year);
mov_write_string_data_tag(pb, mov, s, year);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes tool used to create the file */
static int mov_write_too_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251too");
mov_write_string_data_tag(pb, mov, s, LIBAVFORMAT_IDENT);
return updateSize(pb, pos);
}
/* iTunes comment */
static int mov_write_cmt_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->comment[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251cmt");
mov_write_string_data_tag(pb, mov, s, s->comment);
size = updateSize(pb, pos);
}
return size;
}
/* iTunes custom genre */
static int mov_write_gen_tag(ByteIOContext *pb, MOVContext* mov,
AVFormatContext *s)
{
int size = 0;
if ( s->genre[0] ) {
int pos = url_ftell(pb);
put_be32(pb, 0); /* size */
put_tag(pb, "\251gen");
mov_write_string_data_tag(pb, mov, s, s->genre);