summaryrefslogtreecommitdiff
path: root/libavformat/movenc.c
diff options
context:
space:
mode:
Diffstat (limited to 'libavformat/movenc.c')
-rw-r--r--libavformat/movenc.c210
1 files changed, 150 insertions, 60 deletions
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 10bcedc66a..7f970be35f 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 + track->data_offset);
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
@@ -570,6 +584,8 @@ static int get_samples_per_packet(MOVTrack *track)
{
int i, first_duration;
+// return track->enc->frame_size;
+
/* use 1 for raw PCM */
if (!track->audio_vbr)
return 1;
@@ -592,9 +608,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 */
@@ -623,16 +645,32 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track)
avio_wb32(pb, track->sample_size);
avio_wb32(pb, get_samples_per_packet(track));
} 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 ||
@@ -1173,6 +1211,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 */
@@ -1578,11 +1617,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);
@@ -1592,7 +1628,7 @@ static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov,
mov_write_udta_sdp(pb, track->rtp_ctx, track->track_id);
if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO && track->mode == MODE_MOV) {
double sample_aspect_ratio = av_q2d(st->sample_aspect_ratio);
- if (0.0 != sample_aspect_ratio && 1.0 != sample_aspect_ratio)
+ if (st->sample_aspect_ratio.num && 1.0 != sample_aspect_ratio)
mov_write_tapt_tag(pb, track);
};
return update_size(pb, pos);
@@ -1940,32 +1976,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);
@@ -2028,6 +2064,29 @@ 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;
+ if (trk->chunkCount)
+ return;
+ 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)
{
@@ -2042,6 +2101,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)
@@ -2879,6 +2941,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
@@ -2901,6 +2966,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);
}
@@ -2916,13 +2985,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;
@@ -3121,17 +3191,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) {
+ }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) {
@@ -3175,6 +3248,11 @@ static int mov_write_header(AVFormatContext *s)
FF_MOV_FLAG_FRAGMENT;
}
+ if(mov->reserved_moov_size){
+ mov->reserved_moov_pos= avio_tell(pb);
+ avio_skip(pb, mov->reserved_moov_size);
+ }
+
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT))
mov_write_mdat_tag(pb, mov);
@@ -3237,9 +3315,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);