X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fmovenc.c;h=c8d32f3c679abf297a6965df6c7cc3f913287c81;hb=446263879b96f03cffe486e97b14b4e28a61f6ab;hp=333495fc1310946ace5800098777e1cd505e5db0;hpb=ffb512e349f2d82fb6a73a11d496c569e7c50a95;p=ffmpeg diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 333495fc131..c8d32f3c679 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1,20 +1,22 @@ /* - * MOV, 3GP, MP4 encoder. + * MOV, 3GP, MP4 muxer * Copyright (c) 2003 Thomas Raivio. * Copyright (c) 2004 Gildas Bazin . * - * This library is free software; you can redistribute it and/or + * This file is part of FFmpeg. + * + * FFmpeg 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. + * version 2.1 of the License, or (at your option) any later version. * - * This library is distributed in the hope that it will be useful, + * FFmpeg 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 + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h" @@ -42,6 +44,7 @@ typedef struct MOVIentry { char key_frame; unsigned int entries; int64_t cts; + int64_t dts; } MOVIentry; typedef struct MOVIndex { @@ -51,7 +54,6 @@ typedef struct MOVIndex { long time; int64_t trackDuration; long sampleCount; - long sampleDuration; long sampleSize; int hasKeyframes; int hasBframes; @@ -63,6 +65,7 @@ typedef struct MOVIndex { int vosLen; uint8_t *vosData; MOVIentry *cluster; + int audio_vbr; } MOVTrack; typedef struct MOVContext { @@ -75,7 +78,7 @@ typedef struct MOVContext { MOVTrack tracks[MAX_STREAMS]; } MOVContext; -//FIXME supprt 64bit varaint with wide placeholders +//FIXME support 64 bit variant with wide placeholders static offset_t updateSize (ByteIOContext *pb, offset_t pos) { offset_t curpos = url_ftell(pb); @@ -257,7 +260,7 @@ static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack* track) // Basic putDescr(pb, 0x04, 13 + decoderSpecificInfoLen); // Object type indication - put_byte(pb, codec_get_tag(ff_mov_obj_type, track->enc->codec_id)); + put_byte(pb, codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)); // the following fields is made of 6 bits to identify the streamtype (4 for video, 5 for audio) // plus 1 bit to indicate upstream and 1 bit set to 1 (reserved) @@ -319,35 +322,13 @@ static int mov_write_wave_tag(ByteIOContext *pb, MOVTrack* track) return updateSize (pb, pos); } -static 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_AMR_WB, MKTAG('s', 'a', 'w', 'b') }, - { CODEC_ID_PCM_S16BE, MKTAG('t', 'w', 'o', 's') }, - { CODEC_ID_PCM_S16LE, MKTAG('s', 'o', 'w', 't') }, - { CODEC_ID_PCM_S24BE, MKTAG('i', 'n', '2', '4') }, - { CODEC_ID_PCM_S24LE, MKTAG('i', 'n', '2', '4') }, - { CODEC_ID_PCM_S32BE, MKTAG('i', 'n', '3', '2') }, - { CODEC_ID_PCM_S32LE, MKTAG('i', 'n', '3', '2') }, - { CODEC_ID_MP3, MKTAG('.', 'm', 'p', '3') }, - { CODEC_ID_NONE, 0 }, -}; - static int mov_write_audio_tag(ByteIOContext *pb, MOVTrack* track) { offset_t pos = url_ftell(pb); - int vbr= track->mode == MODE_MOV && - (track->enc->codec_id == CODEC_ID_AAC || - track->enc->codec_id == CODEC_ID_MP3 || - track->enc->codec_id == CODEC_ID_AMR_NB); - int version = vbr || - track->enc->codec_id == CODEC_ID_PCM_S32LE || - track->enc->codec_id == CODEC_ID_PCM_S24LE; + int version = track->mode == MODE_MOV && + (track->audio_vbr || + track->enc->codec_id == CODEC_ID_PCM_S32LE || + track->enc->codec_id == CODEC_ID_PCM_S24LE); put_be32(pb, 0); /* size */ put_le32(pb, track->tag); // store it byteswapped @@ -360,18 +341,25 @@ static int mov_write_audio_tag(ByteIOContext *pb, MOVTrack* track) put_be16(pb, 0); /* Revision level */ put_be32(pb, 0); /* Reserved */ - put_be16(pb, track->mode == MODE_MOV ? track->enc->channels : 2); /* Number of channels */ - /* FIXME 8 bit for 'raw ' in mov */ - put_be16(pb, 16); /* Reserved */ + if (track->mode == MODE_MOV) { + put_be16(pb, track->enc->channels); + if (track->enc->codec_id == CODEC_ID_PCM_U8 || + track->enc->codec_id == CODEC_ID_PCM_S8) + put_be16(pb, 8); /* bits per sample */ + else + put_be16(pb, 16); + put_be16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ + } else { /* reserved for mp4/3gp */ + put_be16(pb, 2); + put_be16(pb, 16); + put_be16(pb, 0); + } - put_be16(pb, vbr ? 0xfffe : 0); /* compression ID */ put_be16(pb, 0); /* packet size (= 0) */ put_be16(pb, track->timescale); /* Time scale */ put_be16(pb, 0); /* Reserved */ if(version == 1) { /* SoundDescription V1 extended info */ - if (vbr) - track->sampleSize = 0; put_be32(pb, track->enc->frame_size); /* Samples per packet */ put_be32(pb, track->sampleSize / track->enc->channels); /* Bytes per packet */ put_be32(pb, track->sampleSize); /* Bytes per frame */ @@ -419,7 +407,7 @@ static int mov_write_svq3_tag(ByteIOContext *pb) static uint8_t *avc_find_startcode( uint8_t *p, uint8_t *end ) { - uint8_t *a = p + 4 - ((int)p & 3); + uint8_t *a = p + 4 - ((long)p & 3); for( end -= 3; p < a && p < end; p++ ) { if( p[0] == 0 && p[1] == 0 && p[2] == 1 ) @@ -482,7 +470,7 @@ static int mov_write_avcc_tag(ByteIOContext *pb, MOVTrack *track) put_tag(pb, "avcC"); if (track->vosLen > 6) { /* check for h264 start code */ - if (BE_32(track->vosData) == 0x00000001) { + if (AV_RB32(track->vosData) == 0x00000001) { uint8_t *buf, *end; uint32_t sps_size=0, pps_size=0; uint8_t *sps=0, *pps=0; @@ -495,7 +483,7 @@ static int mov_write_avcc_tag(ByteIOContext *pb, MOVTrack *track) while (buf < end) { unsigned int size; uint8_t nal_type; - size = BE_32(buf); + size = AV_RB32(buf); nal_type = buf[4] & 0x1f; if (nal_type == 7) { /* SPS */ sps = buf + 4; @@ -528,21 +516,50 @@ static int mov_write_avcc_tag(ByteIOContext *pb, MOVTrack *track) return updateSize(pb, pos); } -static 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('h', '2', '6', '3') }, - { CODEC_ID_H263, MKTAG('s', '2', '6', '3') }, - { CODEC_ID_H264, MKTAG('a', 'v', 'c', '1') }, - /* special handling in mov_find_video_codec_tag */ - { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', ' ') }, /* DV NTSC */ - { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'c', 'p') }, /* DV PAL */ - { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'p', 'p') }, /* DVCPRO PAL */ - { CODEC_ID_DVVIDEO, MKTAG('d', 'v', '5', 'n') }, /* DVCPRO50 NTSC */ - { CODEC_ID_DVVIDEO, MKTAG('d', 'v', '5', 'p') }, /* DVCPRO50 PAL */ - { CODEC_ID_NONE, 0 }, -}; +/* also used by all avid codecs (dv, imx, meridien) and their variants */ +static int mov_write_avid_tag(ByteIOContext *pb, MOVTrack *track) +{ + int i; + put_be32(pb, 24); /* size */ + put_tag(pb, "ACLR"); + put_tag(pb, "ACLR"); + put_tag(pb, "0001"); + put_be32(pb, 1); /* yuv 1 / rgb 2 ? */ + put_be32(pb, 0); /* unknown */ + + put_be32(pb, 24); /* size */ + put_tag(pb, "APRG"); + put_tag(pb, "APRG"); + put_tag(pb, "0001"); + put_be32(pb, 1); /* unknown */ + put_be32(pb, 0); /* unknown */ + + put_be32(pb, 120); /* size */ + put_tag(pb, "ARES"); + put_tag(pb, "ARES"); + put_tag(pb, "0001"); + put_be32(pb, AV_RB32(track->vosData + 0x28)); /* dnxhd cid, some id ? */ + put_be32(pb, track->enc->width); + /* values below are based on samples created with quicktime and avid codecs */ + if (track->vosData[5] & 2) { // interlaced + put_be32(pb, track->enc->height/2); + put_be32(pb, 2); /* unknown */ + put_be32(pb, 0); /* unknown */ + put_be32(pb, 4); /* unknown */ + } else { + put_be32(pb, track->enc->height); + put_be32(pb, 1); /* unknown */ + put_be32(pb, 0); /* unknown */ + put_be32(pb, 5); /* unknown */ + } + /* padding */ + for (i = 0; i < 10; i++) + put_be64(pb, 0); + + /* extra padding for stsd needed */ + put_be32(pb, 0); + return 0; +} static int mov_find_video_codec_tag(AVFormatContext *s, MOVTrack *track) { @@ -651,6 +668,8 @@ static int mov_write_video_tag(ByteIOContext *pb, MOVTrack* track) mov_write_svq3_tag(pb); else if(track->enc->codec_id == CODEC_ID_H264) mov_write_avcc_tag(pb, track); + else if(track->enc->codec_id == CODEC_ID_DNXHD) + mov_write_avid_tag(pb, track); return updateSize (pb, pos); } @@ -671,7 +690,7 @@ static int mov_write_stsd_tag(ByteIOContext *pb, MOVTrack* track) static int mov_write_ctts_tag(ByteIOContext *pb, MOVTrack* track) { - Time2Sample *ctts_entries; + MOV_stts_t *ctts_entries; uint32_t entries = 0; uint32_t atom_size; int i; @@ -702,18 +721,46 @@ static int mov_write_ctts_tag(ByteIOContext *pb, MOVTrack* track) return atom_size; } -/* TODO: */ /* Time to sample atom */ static int mov_write_stts_tag(ByteIOContext *pb, MOVTrack* track) { - put_be32(pb, 0x18); /* size */ + MOV_stts_t *stts_entries; + uint32_t entries = -1; + uint32_t atom_size; + int i; + + if (track->enc->codec_type == CODEC_TYPE_AUDIO && !track->audio_vbr) { + stts_entries = av_malloc(sizeof(*stts_entries)); /* one entry */ + stts_entries[0].count = track->sampleCount; + stts_entries[0].duration = 1; + entries = 1; + } else { + stts_entries = av_malloc(track->entry * sizeof(*stts_entries)); /* worst case */ + for (i=0; ientry; i++) { + int64_t duration = i + 1 == track->entry ? + track->trackDuration - track->cluster[i].dts + track->cluster[0].dts : /* readjusting */ + track->cluster[i+1].dts - track->cluster[i].dts; + if (i && duration == stts_entries[entries].duration) { + stts_entries[entries].count++; /* compress */ + } else { + entries++; + stts_entries[entries].duration = duration; + stts_entries[entries].count = 1; + } + } + entries++; /* last one */ + } + atom_size = 16 + (entries * 8); + put_be32(pb, atom_size); /* 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 */ - return 0x18; + put_be32(pb, entries); /* entry count */ + for (i=0; itrackDuration) : put_be32(pb, track->trackDuration); /* duration */ put_be16(pb, track->language); /* language */ put_be16(pb, 0); /* reserved (quality) */ + + if(version!=0 && track->mode == MODE_MOV){ + av_log(NULL, AV_LOG_ERROR, + "FATAL error, file duration too long for timebase, this file will not be\n" + "playable with quicktime. Choose a different timebase or a different\n" + "container format\n"); + } + return 32; } @@ -1165,52 +1220,70 @@ static int mov_write_meta_tag(ByteIOContext *pb, MOVContext* mov, static int mov_write_udta_tag(ByteIOContext *pb, MOVContext* mov, AVFormatContext *s) { - offset_t pos = url_ftell(pb); - int i; - - put_be32(pb, 0); /* size */ - put_tag(pb, "udta"); - - /* iTunes meta data */ - mov_write_meta_tag(pb, mov, s); + int i, req = 0; - if(mov->mode == MODE_MOV){ // the title field breaks gtkpod with mp4 and my suspicion is that stuff isnt valid in mp4 /* Requirements */ for (i=0; inb_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) { - mov_write_string_tag(pb, "\251req", "QuickTime 6.0 or greater", 0); + req = 1; break; } } - mov_write_string_tag(pb, "\251nam", s->title , 0); - mov_write_string_tag(pb, "\251aut", s->author , 0); - mov_write_string_tag(pb, "\251alb", s->album , 0); - mov_write_day_tag(pb, s->year, 0); - if(mov->tracks[0].enc && !(mov->tracks[0].enc->flags & CODEC_FLAG_BITEXACT)) - mov_write_string_tag(pb, "\251enc", LIBAVFORMAT_IDENT, 0); - mov_write_string_tag(pb, "\251des", s->comment , 0); - mov_write_string_tag(pb, "\251gen", s->genre , 0); - } + if (s->title[0] || s->author[0] || s->album[0] || s->year || + s->comment[0] || s->genre[0] || s->track || + (mov->mode == MODE_MOV && + ((mov->tracks[0].enc && !mov->tracks[0].enc->flags & CODEC_FLAG_BITEXACT) || req))) { + offset_t pos = url_ftell(pb); - return updateSize(pb, pos); + put_be32(pb, 0); /* size */ + put_tag(pb, "udta"); + + /* iTunes meta data */ + mov_write_meta_tag(pb, mov, s); + + if(mov->mode == MODE_MOV){ // the title field breaks gtkpod with mp4 and my suspicion is that stuff is not valid in mp4 + /* Requirements */ + if (req) + mov_write_string_tag(pb, "\251req", "QuickTime 6.0 or greater", 0); + + mov_write_string_tag(pb, "\251nam", s->title , 0); + mov_write_string_tag(pb, "\251aut", s->author , 0); + mov_write_string_tag(pb, "\251alb", s->album , 0); + mov_write_day_tag(pb, s->year, 0); + if(mov->tracks[0].enc && !(mov->tracks[0].enc->flags & CODEC_FLAG_BITEXACT)) + mov_write_string_tag(pb, "\251enc", LIBAVFORMAT_IDENT, 0); + mov_write_string_tag(pb, "\251des", s->comment , 0); + mov_write_string_tag(pb, "\251gen", s->genre , 0); + } + + return updateSize(pb, pos); + } + + return 0; } +static int utf8len(uint8_t *b){ + int len=0; + int val; + while(*b){ + GET_UTF8(val, *b++, return -1;) + len++; + } + return len; +} -static size_t ascii_to_wc (ByteIOContext *pb, char *b, size_t n) +static int ascii_to_wc (ByteIOContext *pb, uint8_t *b) { - size_t i; - unsigned char c; - for (i = 0; i < n - 1; i++) { - c = b[i]; - if (! (0x20 <= c && c <= 0x7f )) - c = 0x3f; /* '?' */ - put_be16(pb, c); + int val; + while(*b){ + GET_UTF8(val, *b++, return -1;) + put_be16(pb, val); } put_be16(pb, 0x00); - return 2*n; + return 0; } static uint16_t language_code (const char *str) @@ -1248,31 +1321,37 @@ static int mov_write_uuidusmt_tag (ByteIOContext *pb, AVFormatContext *s) size += 12; // Encoder - len = strlen(LIBAVCODEC_IDENT)+1; + len = utf8len(LIBAVCODEC_IDENT)+1; + if(len<=0) + goto not_utf8; put_be16(pb, len*2+10); /* size */ put_be32(pb, 0x04); /* type */ put_be16(pb, language_code("eng")); /* language */ put_be16(pb, 0x01); /* ? */ - ascii_to_wc(pb, LIBAVCODEC_IDENT, len); + ascii_to_wc(pb, LIBAVCODEC_IDENT); size += len*2+10; // Title - len = strlen(s->title)+1; + len = utf8len(s->title)+1; + if(len<=0) + goto not_utf8; put_be16(pb, len*2+10); /* size */ put_be32(pb, 0x01); /* type */ put_be16(pb, language_code("eng")); /* language */ put_be16(pb, 0x01); /* ? */ - ascii_to_wc (pb, s->title, len); + ascii_to_wc (pb, s->title); size += len*2+10; // Date // snprintf(dt,32,"%04d/%02d/%02d %02d:%02d:%02d",t_st->tm_year+1900,t_st->tm_mon+1,t_st->tm_mday,t_st->tm_hour,t_st->tm_min,t_st->tm_sec); - len = strlen("2006/04/01 11:11:11")+1; + len = utf8len("2006/04/01 11:11:11")+1; + if(len<=0) + goto not_utf8; put_be16(pb, len*2+10); /* size */ put_be32(pb, 0x03); /* type */ put_be16(pb, language_code("und")); /* language */ put_be16(pb, 0x01); /* ? */ - ascii_to_wc (pb, "2006/04/01 11:11:11", len); + ascii_to_wc (pb, "2006/04/01 11:11:11"); size += len*2+10; // size @@ -1285,6 +1364,9 @@ static int mov_write_uuidusmt_tag (ByteIOContext *pb, AVFormatContext *s) } return size; +not_utf8: + av_log(s, AV_LOG_ERROR, "not utf8\n"); + return -1; } static int mov_write_moov_tag(ByteIOContext *pb, MOVContext *mov, @@ -1299,8 +1381,6 @@ static int mov_write_moov_tag(ByteIOContext *pb, MOVContext *mov, for (i=0; inb_streams; i++) { if(mov->tracks[i].entry <= 0) continue; - mov->tracks[i].trackDuration = - (int64_t)mov->tracks[i].sampleCount * mov->tracks[i].sampleDuration; mov->tracks[i].time = mov->time; mov->tracks[i].trackID = i+1; } @@ -1315,8 +1395,8 @@ static int mov_write_moov_tag(ByteIOContext *pb, MOVContext *mov, if (mov->mode == MODE_PSP) mov_write_uuidusmt_tag(pb, s); - else - mov_write_udta_tag(pb, mov, s); + else if (mov->mode != MODE_3GP && mov->mode != MODE_3G2) + mov_write_udta_tag(pb, mov, s); return updateSize(pb, pos); } @@ -1432,6 +1512,11 @@ static int mov_write_header(AVFormatContext *s) MOVContext *mov = s->priv_data; int i; + if (url_is_streamed(&s->pb)) { + av_log(s, AV_LOG_ERROR, "muxer does not support non seekable output\n"); + return -1; + } + /* Default mode == MP4 */ mov->mode = MODE_MP4; @@ -1461,18 +1546,24 @@ static int mov_write_header(AVFormatContext *s) if(st->codec->codec_type == CODEC_TYPE_VIDEO){ track->tag = mov_find_video_codec_tag(s, track); track->timescale = st->codec->time_base.den; - track->sampleDuration = st->codec->time_base.num; av_set_pts_info(st, 64, 1, st->codec->time_base.den); + if (track->timescale > 100000) + av_log(NULL, AV_LOG_WARNING, + "WARNING codec timebase is very high. If duration is too long,\n" + "file may not be playable by quicktime. Specify a shorter timebase\n" + "or choose different container.\n"); }else if(st->codec->codec_type == CODEC_TYPE_AUDIO){ track->tag = mov_find_audio_codec_tag(s, track); track->timescale = st->codec->sample_rate; - track->sampleDuration = st->codec->frame_size; av_set_pts_info(st, 64, 1, st->codec->sample_rate); - track->sampleSize = (av_get_bits_per_sample(st->codec->codec_id) >> 3) * st->codec->channels; - } - if (!track->sampleDuration) { - av_log(s, AV_LOG_ERROR, "track %d: sample duration is not set\n", i); - return -1; + if(!st->codec->frame_size){ + av_log(s, AV_LOG_ERROR, "track %d: codec frame size is not set\n", i); + return -1; + }else if(st->codec->frame_size > 1){ /* assume compressed audio */ + track->audio_vbr = 1; + }else{ + track->sampleSize = (av_get_bits_per_sample(st->codec->codec_id) >> 3) * st->codec->channels; + } } } @@ -1508,7 +1599,7 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) samplesInChunk++; } if(samplesInChunk > 1){ - av_log(s, AV_LOG_ERROR, "fatal error, input is not a single packet, inplement a AVParser for it\n"); + av_log(s, AV_LOG_ERROR, "fatal error, input is not a single packet, implement a AVParser for it\n"); return -1; } } else if (trk->sampleSize) @@ -1529,6 +1620,13 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) avc_parse_nal_units(&pkt->data, &pkt->size); assert(pkt->size); size = pkt->size; + } else if (enc->codec_id == CODEC_ID_DNXHD && !trk->vosLen) { + /* copy frame header to create needed atoms */ + if (size < 640) + return -1; + trk->vosLen = 640; + trk->vosData = av_malloc(trk->vosLen); + memcpy(trk->vosData, pkt->data, 640); } if (!(trk->entry % MOV_INDEX_CLUSTER_SIZE)) { @@ -1541,6 +1639,9 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) trk->cluster[trk->entry].samplesInChunk = samplesInChunk; trk->cluster[trk->entry].size = size; trk->cluster[trk->entry].entries = samplesInChunk; + trk->cluster[trk->entry].dts = pkt->dts; + trk->trackDuration = pkt->dts - trk->cluster[0].dts + pkt->duration; + if(enc->codec_type == CODEC_TYPE_VIDEO) { if (pkt->dts != pkt->pts) trk->hasBframes = 1;