diff options
Diffstat (limited to 'libavformat/movenc.c')
-rw-r--r-- | libavformat/movenc.c | 210 |
1 files changed, 146 insertions, 64 deletions
diff --git a/libavformat/movenc.c b/libavformat/movenc.c index ace7b2f..d1337a0 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -4,20 +4,20 @@ * Copyright (c) 2004 Gildas Bazin <gbazin at videolan dot org> * Copyright (c) 2009 Baptiste Coudurier <baptiste dot coudurier at gmail dot com> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * 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.1 of the License, or (at your option) any later version. * - * Libav 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 Libav; 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 */ @@ -46,13 +46,14 @@ static const AVOption options[] = { { "movflags", "MOV muxer flags", offsetof(MOVMuxContext, flags), AV_OPT_TYPE_FLAGS, {.dbl = 0}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "rtphint", "Add RTP hint tracks", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_RTP_HINT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "moov_size", "maximum moov size so it can be placed at the begin", offsetof(MOVMuxContext, reserved_moov_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, 0 }, { "empty_moov", "Make the initial moov atom empty (not supported by QuickTime)", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_EMPTY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "frag_keyframe", "Fragment at video keyframes", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_FRAG_KEYFRAME}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), - { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.dbl = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.dbl = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.dbl = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_video_profile", "iods video profile atom.", offsetof(MOVMuxContext, iods_video_profile), AV_OPT_TYPE_INT, {.dbl = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_duration", "Maximum fragment duration", offsetof(MOVMuxContext, max_fragment_duration), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, @@ -93,8 +94,10 @@ static int mov_write_stco_tag(AVIOContext *pb, MOVTrack *track) } else ffio_wfourcc(pb, "stco"); avio_wb32(pb, 0); /* version & flags */ - avio_wb32(pb, track->entry); /* entry count */ + avio_wb32(pb, track->chunkCount); /* entry count */ for (i=0; i<track->entry; i++) { + if(!track->cluster[i].chunkNum) + continue; if(mode64 == 1) avio_wb64(pb, track->cluster[i].pos); else @@ -152,11 +155,11 @@ static int mov_write_stsc_tag(AVIOContext *pb, MOVTrack *track) ffio_wfourcc(pb, "stsc"); avio_wb32(pb, 0); // version & flags entryPos = avio_tell(pb); - avio_wb32(pb, track->entry); // entry count + avio_wb32(pb, track->chunkCount); // entry count for (i=0; i<track->entry; i++) { - if (oldval != track->cluster[i].samples_in_chunk) + if (oldval != track->cluster[i].samples_in_chunk && track->cluster[i].chunkNum) { - avio_wb32(pb, i+1); // first chunk + avio_wb32(pb, track->cluster[i].chunkNum); // first chunk avio_wb32(pb, track->cluster[i].samples_in_chunk); // samples per chunk avio_wb32(pb, 0x1); // sample description index oldval = track->cluster[i].samples_in_chunk; @@ -280,10 +283,22 @@ static void put_descr(AVIOContext *pb, int tag, unsigned int size) avio_w8(pb, size & 0x7F); } +static unsigned compute_avg_bitrate(MOVTrack *track) +{ + uint64_t size = 0; + int i; + if (!track->track_duration) + return 0; + for (i = 0; i < track->entry; i++) + size += track->cluster[i].size; + return size * 8 * track->timescale / track->track_duration; +} + static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic { int64_t pos = avio_tell(pb); int decoder_specific_info_len = track->vos_len ? 5 + track->vos_len : 0; + unsigned avg_bitrate; avio_wb32(pb, 0); // size ffio_wfourcc(pb, "esds"); @@ -315,11 +330,10 @@ static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic avio_w8(pb, track->enc->rc_buffer_size>>(3+16)); // Buffersize DB (24 bits) avio_wb16(pb, (track->enc->rc_buffer_size>>3)&0xFFFF); // Buffersize DB - avio_wb32(pb, FFMAX(track->enc->bit_rate, track->enc->rc_max_rate)); // maxbitrate (FIXME should be max rate in any 1 sec window) - if(track->enc->rc_max_rate != track->enc->rc_min_rate || track->enc->rc_min_rate==0) - avio_wb32(pb, 0); // vbr - else - avio_wb32(pb, track->enc->rc_max_rate); // avg bitrate + avg_bitrate = compute_avg_bitrate(track); + // maxbitrate (FIXME should be max rate in any 1 sec window) + avio_wb32(pb, FFMAX3(track->enc->bit_rate, track->enc->rc_max_rate, avg_bitrate)); + avio_wb32(pb, avg_bitrate); if (track->vos_len) { // DecoderSpecific info descriptor @@ -558,9 +572,15 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) uint32_t tag = track->tag; if (track->mode == MODE_MOV) { - if (mov_get_lpcm_flags(track->enc->codec_id)) - tag = AV_RL32("lpcm"); - version = 2; + if (track->timescale > UINT16_MAX) { + if (mov_get_lpcm_flags(track->enc->codec_id)) + tag = AV_RL32("lpcm"); + version = 2; + } else if (track->audio_vbr || mov_pcm_le_gt16(track->enc->codec_id) || + track->enc->codec_id == CODEC_ID_ADPCM_MS || + track->enc->codec_id == CODEC_ID_ADPCM_IMA_WAV) { + version = 1; + } } avio_wb32(pb, 0); /* size */ @@ -587,18 +607,34 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, av_get_bits_per_sample(track->enc->codec_id)); avio_wb32(pb, mov_get_lpcm_flags(track->enc->codec_id)); avio_wb32(pb, track->sample_size); - avio_wb32(pb, track->audio_vbr ? track->enc->frame_size : 1); + avio_wb32(pb, track->enc->frame_size); } else { - /* reserved for mp4/3gp */ - avio_wb16(pb, 2); - avio_wb16(pb, 16); - avio_wb16(pb, 0); + if (track->mode == MODE_MOV) { + avio_wb16(pb, track->enc->channels); + if (track->enc->codec_id == CODEC_ID_PCM_U8 || + track->enc->codec_id == CODEC_ID_PCM_S8) + avio_wb16(pb, 8); /* bits per sample */ + else + avio_wb16(pb, 16); + avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ + } else { /* reserved for mp4/3gp */ + avio_wb16(pb, 2); + avio_wb16(pb, 16); + avio_wb16(pb, 0); + } avio_wb16(pb, 0); /* packet size (= 0) */ avio_wb16(pb, track->enc->sample_rate); avio_wb16(pb, 0); /* Reserved */ } + if(version == 1) { /* SoundDescription V1 extended info */ + avio_wb32(pb, track->enc->frame_size); /* Samples per packet */ + avio_wb32(pb, track->sample_size / track->enc->channels); /* Bytes per packet */ + avio_wb32(pb, track->sample_size); /* Bytes per frame */ + avio_wb32(pb, 2); /* Bytes per sample */ + } + if(track->mode == MODE_MOV && (track->enc->codec_id == CODEC_ID_AAC || track->enc->codec_id == CODEC_ID_AC3 || @@ -1141,6 +1177,7 @@ static int mov_write_dref_tag(AVIOContext *pb) avio_wb32(pb, 1); /* entry count */ avio_wb32(pb, 0xc); /* size */ + //FIXME add the alis and rsrc atom ffio_wfourcc(pb, "url "); avio_wb32(pb, 1); /* version & flags */ @@ -1534,11 +1571,8 @@ static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "trak"); mov_write_tkhd_tag(pb, track, st); - if (track->mode == MODE_PSP || track->flags & MOV_TRACK_CTTS || - (track->entry && track->cluster[0].dts)) { - if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) - mov_write_edts_tag(pb, track); // PSP Movies require edts box - } + if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) // EDTS with fragments is tricky as we dont know the duration when its written + mov_write_edts_tag(pb, track); // PSP Movies and several other cases require edts box if (track->tref_tag) mov_write_tref_tag(pb, track); mov_write_mdia_tag(pb, track); @@ -1896,32 +1930,32 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov, if(ret < 0) return ret; - if (mov->mode & MODE_3GP) { - mov_write_3gp_udta_tag(pb_buf, s, "perf", "artist"); - mov_write_3gp_udta_tag(pb_buf, s, "titl", "title"); - mov_write_3gp_udta_tag(pb_buf, s, "auth", "author"); - mov_write_3gp_udta_tag(pb_buf, s, "gnre", "genre"); - mov_write_3gp_udta_tag(pb_buf, s, "dscp", "comment"); - mov_write_3gp_udta_tag(pb_buf, s, "albm", "album"); - mov_write_3gp_udta_tag(pb_buf, s, "cprt", "copyright"); - mov_write_3gp_udta_tag(pb_buf, s, "yrrc", "date"); - } else if (mov->mode == MODE_MOV) { // the title field breaks gtkpod with mp4 and my suspicion is that stuff is not valid in mp4 - mov_write_string_metadata(s, pb_buf, "\251ART", "artist" , 0); - mov_write_string_metadata(s, pb_buf, "\251nam", "title" , 0); - mov_write_string_metadata(s, pb_buf, "\251aut", "author" , 0); - mov_write_string_metadata(s, pb_buf, "\251alb", "album" , 0); - mov_write_string_metadata(s, pb_buf, "\251day", "date" , 0); - mov_write_string_metadata(s, pb_buf, "\251swr", "encoder" , 0); - mov_write_string_metadata(s, pb_buf, "\251des", "comment" , 0); - mov_write_string_metadata(s, pb_buf, "\251gen", "genre" , 0); - mov_write_string_metadata(s, pb_buf, "\251cpy", "copyright" , 0); - } else { - /* iTunes meta data */ - mov_write_meta_tag(pb_buf, mov, s); - } + if (mov->mode & MODE_3GP) { + mov_write_3gp_udta_tag(pb_buf, s, "perf", "artist"); + mov_write_3gp_udta_tag(pb_buf, s, "titl", "title"); + mov_write_3gp_udta_tag(pb_buf, s, "auth", "author"); + mov_write_3gp_udta_tag(pb_buf, s, "gnre", "genre"); + mov_write_3gp_udta_tag(pb_buf, s, "dscp", "comment"); + mov_write_3gp_udta_tag(pb_buf, s, "albm", "album"); + mov_write_3gp_udta_tag(pb_buf, s, "cprt", "copyright"); + mov_write_3gp_udta_tag(pb_buf, s, "yrrc", "date"); + } else if (mov->mode == MODE_MOV) { // the title field breaks gtkpod with mp4 and my suspicion is that stuff is not valid in mp4 + mov_write_string_metadata(s, pb_buf, "\251ART", "artist" , 0); + mov_write_string_metadata(s, pb_buf, "\251nam", "title" , 0); + mov_write_string_metadata(s, pb_buf, "\251aut", "author" , 0); + mov_write_string_metadata(s, pb_buf, "\251alb", "album" , 0); + mov_write_string_metadata(s, pb_buf, "\251day", "date" , 0); + mov_write_string_metadata(s, pb_buf, "\251swr", "encoder" , 0); + mov_write_string_metadata(s, pb_buf, "\251des", "comment" , 0); + mov_write_string_metadata(s, pb_buf, "\251gen", "genre" , 0); + mov_write_string_metadata(s, pb_buf, "\251cpy", "copyright" , 0); + } else { + /* iTunes meta data */ + mov_write_meta_tag(pb_buf, mov, s); + } - if (s->nb_chapters) - mov_write_chpl_tag(pb_buf, s); + if (s->nb_chapters) + mov_write_chpl_tag(pb_buf, s); if ((size = avio_close_dyn_buf(pb_buf, &buf)) > 0) { avio_wb32(pb, size+8); @@ -1984,6 +2018,27 @@ static int mov_write_uuidusmt_tag(AVIOContext *pb, AVFormatContext *s) return 0; } +static void build_chunks(MOVTrack *trk) +{ + int i; + MOVIentry *chunk= &trk->cluster[0]; + uint64_t chunkSize = chunk->size; + chunk->chunkNum= 1; + trk->chunkCount= 1; + for(i=1; i<trk->entry; i++){ + if(chunk->pos + chunkSize == trk->cluster[i].pos && + chunkSize + trk->cluster[i].size < (1<<20)){ + chunkSize += trk->cluster[i].size; + chunk->samples_in_chunk += trk->cluster[i].entries; + }else{ + trk->cluster[i].chunkNum = chunk->chunkNum+1; + chunk=&trk->cluster[i]; + chunkSize = chunk->size; + trk->chunkCount++; + } + } +} + static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) { @@ -1998,6 +2053,9 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, mov->tracks[i].time = mov->time; mov->tracks[i].track_id = i+1; + + if (mov->tracks[i].entry) + build_chunks(&mov->tracks[i]); } if (mov->chapter_track) @@ -2813,6 +2871,9 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_ERROR, "fatal error, input is not a single packet, implement a AVParser for it\n"); return -1; } + } else if (enc->codec_id == CODEC_ID_ADPCM_MS || + enc->codec_id == CODEC_ID_ADPCM_IMA_WAV) { + samples_in_chunk = enc->frame_size; } else if (trk->sample_size) samples_in_chunk = size / trk->sample_size; else @@ -2835,6 +2896,10 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) } else { size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size); } + } else if (enc->codec_id == CODEC_ID_AAC && pkt->size > 2 && + (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { + av_log(s, AV_LOG_ERROR, "malformated aac bitstream, use -absf aac_adtstoasc\n"); + return -1; } else { avio_write(pb, pkt->data, size); } @@ -2850,13 +2915,14 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) } if (!(trk->entry % MOV_INDEX_CLUSTER_SIZE)) { - trk->cluster = av_realloc(trk->cluster, (trk->entry + MOV_INDEX_CLUSTER_SIZE) * sizeof(*trk->cluster)); + trk->cluster = av_realloc_f(trk->cluster, sizeof(*trk->cluster), (trk->entry + MOV_INDEX_CLUSTER_SIZE)); if (!trk->cluster) return -1; } trk->cluster[trk->entry].pos = avio_tell(pb) - size; trk->cluster[trk->entry].samples_in_chunk = samples_in_chunk; + trk->cluster[trk->entry].chunkNum = 0; trk->cluster[trk->entry].size = size; trk->cluster[trk->entry].entries = samples_in_chunk; trk->cluster[trk->entry].dts = pkt->dts; @@ -3048,21 +3114,20 @@ static int mov_write_header(AVFormatContext *s) "or choose different container.\n"); }else if(st->codec->codec_type == AVMEDIA_TYPE_AUDIO){ track->timescale = st->codec->sample_rate; - /* set sample_size for PCM and ADPCM */ - if (av_get_bits_per_sample(st->codec->codec_id)) { + if(!st->codec->frame_size && !av_get_bits_per_sample(st->codec->codec_id)) { + av_log(s, AV_LOG_ERROR, "track %d: codec frame size is not set\n", i); + goto error; + }else if(st->codec->codec_id == CODEC_ID_ADPCM_MS || + st->codec->codec_id == CODEC_ID_ADPCM_IMA_WAV){ if (!st->codec->block_align) { - av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set\n", i); + av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set for adpcm\n", i); goto error; } track->sample_size = st->codec->block_align; - } - /* set audio_vbr for compressed audio */ - if (av_get_bits_per_sample(st->codec->codec_id) < 8) { - if (!st->codec->frame_size && track->mode == MODE_MOV) { - av_log(s, AV_LOG_ERROR, "track %d: codec frame size is not set\n", i); - goto error; - } + }else if(st->codec->frame_size > 1){ /* assume compressed audio */ track->audio_vbr = 1; + }else{ + track->sample_size = (av_get_bits_per_sample(st->codec->codec_id) >> 3) * st->codec->channels; } if (track->mode != MODE_MOV) { if (track->timescale > UINT16_MAX) { @@ -3105,6 +3170,11 @@ static int mov_write_header(AVFormatContext *s) mov->flags |= FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_SEPARATE_MOOF; } + if(mov->reserved_moov_size){ + mov->reserved_moov_pos= avio_tell(pb); + avio_skip(pb, mov->reserved_moov_size); + } + /* Set the FRAGMENT flag if any of the fragmentation methods are * enabled. */ if (mov->max_fragment_duration || mov->max_fragment_size || @@ -3175,9 +3245,21 @@ static int mov_write_trailer(AVFormatContext *s) ffio_wfourcc(pb, "mdat"); avio_wb64(pb, mov->mdat_size + 16); } - avio_seek(pb, moov_pos, SEEK_SET); + avio_seek(pb, mov->reserved_moov_size ? mov->reserved_moov_pos : moov_pos, SEEK_SET); mov_write_moov_tag(pb, mov, s); + if(mov->reserved_moov_size){ + int64_t size= mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_moov_pos); + if(size < 8){ + av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size); + return -1; + } + avio_wb32(pb, size); + ffio_wfourcc(pb, "free"); + for(i=0; i<size; i++) + avio_w8(pb, 0); + avio_seek(pb, moov_pos, SEEK_SET); + } } else { mov_flush_fragment(s); mov_write_mfra_tag(pb, mov); |