diff options
Diffstat (limited to 'libavformat/movenc.c')
-rw-r--r-- | libavformat/movenc.c | 2911 |
1 files changed, 2531 insertions, 380 deletions
diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 840190d..2838286 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 */ @@ -31,23 +31,35 @@ #include "avio.h" #include "isom.h" #include "avc.h" +#include "libavcodec/ac3_parser.h" +#include "libavcodec/dnxhddata.h" +#include "libavcodec/flac.h" +#include "libavcodec/get_bits.h" -#include "libavcodec/bitstream.h" +#include "libavcodec/internal.h" #include "libavcodec/put_bits.h" #include "libavcodec/vc1_common.h" +#include "libavcodec/raw.h" #include "internal.h" #include "libavutil/avstring.h" #include "libavutil/intfloat.h" #include "libavutil/mathematics.h" +#include "libavutil/libm.h" #include "libavutil/opt.h" #include "libavutil/dict.h" +#include "libavutil/pixdesc.h" +#include "libavutil/stereo3d.h" +#include "libavutil/timecode.h" +#include "libavutil/color_utils.h" #include "hevc.h" #include "rtpenc.h" #include "mov_chan.h" +#include "vpcc.h" static const AVOption options[] = { { "movflags", "MOV muxer flags", offsetof(MOVMuxContext, flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "rtphint", "Add RTP hint tracks", 0, AV_OPT_TYPE_CONST, {.i64 = 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, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, 0 }, { "empty_moov", "Make the initial moov atom empty", 0, AV_OPT_TYPE_CONST, {.i64 = 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, {.i64 = 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, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, @@ -61,19 +73,30 @@ static const AVOption options[] = { { "frag_discont", "Signal that the next fragment is discontinuous from earlier ones", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_DISCONT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_colr", "Write colr atom (Experimental, may be renamed or changed, do not use from scripts)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_COLR}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_gama", "Write deprecated gama atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_GAMA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "use_metadata_tags", "Use mdta atom for metadata.", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_USE_MDTA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "skip_trailer", "Skip writing the mfra/tfra/mfro trailer for fragmented files", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_TRAILER}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "negative_cts_offsets", "Use negative CTS offsets (reducing the need for edit lists)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS}, 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, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_video_profile", "iods video profile atom.", offsetof(MOVMuxContext, iods_video_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_duration", "Maximum fragment duration", offsetof(MOVMuxContext, max_fragment_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "min_frag_duration", "Minimum fragment duration", offsetof(MOVMuxContext, min_fragment_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_size", "Maximum fragment size", offsetof(MOVMuxContext, max_fragment_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "ism_lookahead", "Number of lookahead entries for ISM files", offsetof(MOVMuxContext, ism_lookahead), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, + { "video_track_timescale", "set timescale of all video tracks", offsetof(MOVMuxContext, video_track_timescale), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "brand", "Override major brand", offsetof(MOVMuxContext, major_brand), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = AV_OPT_FLAG_ENCODING_PARAM }, - { "use_editlist", "use edit list", offsetof(MOVMuxContext, use_editlist), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { "use_editlist", "use edit list", offsetof(MOVMuxContext, use_editlist), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "fragment_index", "Fragment number of the next fragment", offsetof(MOVMuxContext, fragments), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, + { "mov_gamma", "gamma value for gama atom", offsetof(MOVMuxContext, gamma), AV_OPT_TYPE_FLOAT, {.dbl = 0.0 }, 0.0, 10, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_interleave", "Interleave samples within fragments (max number of consecutive samples, lower is tighter interleaving, but with more overhead)", offsetof(MOVMuxContext, frag_interleave), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, + { "encryption_scheme", "Configures the encryption scheme, allowed values are none, cenc-aes-ctr", offsetof(MOVMuxContext, encryption_scheme_str), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = AV_OPT_FLAG_ENCODING_PARAM }, + { "encryption_key", "The media encryption key (hex)", offsetof(MOVMuxContext, encryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM }, + { "encryption_kid", "The media encryption key identifier (hex)", offsetof(MOVMuxContext, encryption_kid), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM }, + { "use_stream_ids_as_track_ids", "use stream ids as track ids", offsetof(MOVMuxContext, use_stream_ids_as_track_ids), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, + { "write_tmcd", "force or disable writing tmcd", offsetof(MOVMuxContext, write_tmcd), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM}, { NULL }, }; @@ -85,6 +108,8 @@ static const AVClass flavor ## _muxer_class = {\ .version = LIBAVUTIL_VERSION_INT,\ }; +static int get_moov_size(AVFormatContext *s); + static int utf8len(const uint8_t *b) { int len = 0; @@ -121,13 +146,15 @@ static int mov_write_stco_tag(AVIOContext *pb, MOVTrack *track) int mode64 = co64_required(track); // use 32 bit size variant if possible int64_t pos = avio_tell(pb); avio_wb32(pb, 0); /* size */ - if (mode64) { + if (mode64) ffio_wfourcc(pb, "co64"); - } else + 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 @@ -183,10 +210,10 @@ 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) { - avio_wb32(pb, i + 1); // first chunk + if (oldval != track->cluster[i].samples_in_chunk && track->cluster[i].chunkNum) { + 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; @@ -241,7 +268,7 @@ static int mov_write_amr_tag(AVIOContext *pb, MOVTrack *track) static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) { - BitstreamContext bc; + GetBitContext gbc; PutBitContext pbc; uint8_t buf[3]; int fscod, bsid, bsmod, acmod, lfeon, frmsizecod; @@ -252,21 +279,21 @@ static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 11); ffio_wfourcc(pb, "dac3"); - bitstream_init8(&bc, track->vos_data + 4, track->vos_len - 4); - fscod = bitstream_read(&bc, 2); - frmsizecod = bitstream_read(&bc, 6); - bsid = bitstream_read(&bc, 5); - bsmod = bitstream_read(&bc, 3); - acmod = bitstream_read(&bc, 3); + init_get_bits(&gbc, track->vos_data + 4, (track->vos_len - 4) * 8); + fscod = get_bits(&gbc, 2); + frmsizecod = get_bits(&gbc, 6); + bsid = get_bits(&gbc, 5); + bsmod = get_bits(&gbc, 3); + acmod = get_bits(&gbc, 3); if (acmod == 2) { - bitstream_skip(&bc, 2); // dsurmod + skip_bits(&gbc, 2); // dsurmod } else { if ((acmod & 1) && acmod != 1) - bitstream_skip(&bc, 2); // cmixlev + skip_bits(&gbc, 2); // cmixlev if (acmod & 4) - bitstream_skip(&bc, 2); // surmixlev + skip_bits(&gbc, 2); // surmixlev } - lfeon = bitstream_read_bit(&bc); + lfeon = get_bits1(&gbc); init_put_bits(&pbc, buf, sizeof(buf)); put_bits(&pbc, 2, fscod); @@ -283,6 +310,215 @@ static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) return 11; } +struct eac3_info { + AVPacket pkt; + uint8_t ec3_done; + uint8_t num_blocks; + + /* Layout of the EC3SpecificBox */ + /* maximum bitrate */ + uint16_t data_rate; + /* number of independent substreams */ + uint8_t num_ind_sub; + struct { + /* sample rate code (see ff_ac3_sample_rate_tab) 2 bits */ + uint8_t fscod; + /* bit stream identification 5 bits */ + uint8_t bsid; + /* one bit reserved */ + /* audio service mixing (not supported yet) 1 bit */ + /* bit stream mode 3 bits */ + uint8_t bsmod; + /* audio coding mode 3 bits */ + uint8_t acmod; + /* sub woofer on 1 bit */ + uint8_t lfeon; + /* 3 bits reserved */ + /* number of dependent substreams associated with this substream 4 bits */ + uint8_t num_dep_sub; + /* channel locations of the dependent substream(s), if any, 9 bits */ + uint16_t chan_loc; + /* if there is no dependent substream, then one bit reserved instead */ + } substream[1]; /* TODO: support 8 independent substreams */ +}; + +#if CONFIG_AC3_PARSER +static int handle_eac3(MOVMuxContext *mov, AVPacket *pkt, MOVTrack *track) +{ + GetBitContext gbc; + AC3HeaderInfo tmp, *hdr = &tmp; + struct eac3_info *info; + int num_blocks; + + if (!track->eac3_priv && !(track->eac3_priv = av_mallocz(sizeof(*info)))) + return AVERROR(ENOMEM); + info = track->eac3_priv; + + init_get_bits(&gbc, pkt->data, pkt->size * 8); + if (avpriv_ac3_parse_header(&gbc, &hdr) < 0) { + /* drop the packets until we see a good one */ + if (!track->entry) { + av_log(mov, AV_LOG_WARNING, "Dropping invalid packet from start of the stream\n"); + return 0; + } + return AVERROR_INVALIDDATA; + } + + info->data_rate = FFMAX(info->data_rate, hdr->bit_rate / 1000); + num_blocks = hdr->num_blocks; + + if (!info->ec3_done) { + /* AC-3 substream must be the first one */ + if (hdr->bitstream_id <= 10 && hdr->substreamid != 0) + return AVERROR(EINVAL); + + /* this should always be the case, given that our AC-3 parser + * concatenates dependent frames to their independent parent */ + if (hdr->frame_type == EAC3_FRAME_TYPE_INDEPENDENT) { + /* substream ids must be incremental */ + if (hdr->substreamid > info->num_ind_sub + 1) + return AVERROR(EINVAL); + + if (hdr->substreamid == info->num_ind_sub + 1) { + //info->num_ind_sub++; + avpriv_request_sample(track->par, "Multiple independent substreams"); + return AVERROR_PATCHWELCOME; + } else if (hdr->substreamid < info->num_ind_sub || + hdr->substreamid == 0 && info->substream[0].bsid) { + info->ec3_done = 1; + goto concatenate; + } + } + + /* fill the info needed for the "dec3" atom */ + info->substream[hdr->substreamid].fscod = hdr->sr_code; + info->substream[hdr->substreamid].bsid = hdr->bitstream_id; + info->substream[hdr->substreamid].bsmod = hdr->bitstream_mode; + info->substream[hdr->substreamid].acmod = hdr->channel_mode; + info->substream[hdr->substreamid].lfeon = hdr->lfe_on; + + /* Parse dependent substream(s), if any */ + if (pkt->size != hdr->frame_size) { + int cumul_size = hdr->frame_size; + int parent = hdr->substreamid; + + while (cumul_size != pkt->size) { + int i; + init_get_bits(&gbc, pkt->data + cumul_size, (pkt->size - cumul_size) * 8); + if (avpriv_ac3_parse_header(&gbc, &hdr) < 0) + return AVERROR_INVALIDDATA; + if (hdr->frame_type != EAC3_FRAME_TYPE_DEPENDENT) + return AVERROR(EINVAL); + cumul_size += hdr->frame_size; + info->substream[parent].num_dep_sub++; + + /* header is parsed up to lfeon, but custom channel map may be needed */ + /* skip bsid */ + skip_bits(&gbc, 5); + /* skip volume control params */ + for (i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + skip_bits(&gbc, 5); // skip dialog normalization + if (get_bits1(&gbc)) { + skip_bits(&gbc, 8); // skip compression gain word + } + } + /* get the dependent stream channel map, if exists */ + if (get_bits1(&gbc)) + info->substream[parent].chan_loc |= (get_bits(&gbc, 16) >> 5) & 0x1f; + else + info->substream[parent].chan_loc |= hdr->channel_mode; + } + } + } + +concatenate: + if (!info->num_blocks && num_blocks == 6) + return pkt->size; + else if (info->num_blocks + num_blocks > 6) + return AVERROR_INVALIDDATA; + + if (!info->num_blocks) { + int ret = av_packet_ref(&info->pkt, pkt); + if (ret < 0) + return ret; + info->num_blocks = num_blocks; + return 0; + } else { + int ret; + if ((ret = av_grow_packet(&info->pkt, pkt->size)) < 0) + return ret; + memcpy(info->pkt.data + info->pkt.size - pkt->size, pkt->data, pkt->size); + info->num_blocks += num_blocks; + info->pkt.duration += pkt->duration; + if ((ret = av_copy_packet_side_data(&info->pkt, pkt)) < 0) + return ret; + if (info->num_blocks != 6) + return 0; + av_packet_unref(pkt); + ret = av_packet_ref(pkt, &info->pkt); + if (ret < 0) + return ret; + av_packet_unref(&info->pkt); + info->num_blocks = 0; + } + + return pkt->size; +} +#endif + +static int mov_write_eac3_tag(AVIOContext *pb, MOVTrack *track) +{ + PutBitContext pbc; + uint8_t *buf; + struct eac3_info *info; + int size, i; + + if (!track->eac3_priv) + return AVERROR(EINVAL); + + info = track->eac3_priv; + size = 2 + 4 * (info->num_ind_sub + 1); + buf = av_malloc(size); + if (!buf) { + size = AVERROR(ENOMEM); + goto end; + } + + init_put_bits(&pbc, buf, size); + put_bits(&pbc, 13, info->data_rate); + put_bits(&pbc, 3, info->num_ind_sub); + for (i = 0; i <= info->num_ind_sub; i++) { + put_bits(&pbc, 2, info->substream[i].fscod); + put_bits(&pbc, 5, info->substream[i].bsid); + put_bits(&pbc, 1, 0); /* reserved */ + put_bits(&pbc, 1, 0); /* asvc */ + put_bits(&pbc, 3, info->substream[i].bsmod); + put_bits(&pbc, 3, info->substream[i].acmod); + put_bits(&pbc, 1, info->substream[i].lfeon); + put_bits(&pbc, 5, 0); /* reserved */ + put_bits(&pbc, 4, info->substream[i].num_dep_sub); + if (!info->substream[i].num_dep_sub) { + put_bits(&pbc, 1, 0); /* reserved */ + size--; + } else { + put_bits(&pbc, 9, info->substream[i].chan_loc); + } + } + flush_put_bits(&pbc); + + avio_wb32(pb, size + 8); + ffio_wfourcc(pb, "dec3"); + avio_write(pb, buf, size); + + av_free(buf); + +end: + av_packet_unref(&info->pkt); + av_freep(&track->eac3_priv); + + return size; +} + /** * This function writes extradata "as is". * Extradata must be formatted like a valid atom (with size and tag). @@ -293,6 +529,22 @@ static int mov_write_extradata_tag(AVIOContext *pb, MOVTrack *track) return track->par->extradata_size; } +static int mov_write_enda_tag(AVIOContext *pb) +{ + avio_wb32(pb, 10); + ffio_wfourcc(pb, "enda"); + avio_wb16(pb, 1); /* little endian */ + return 10; +} + +static int mov_write_enda_tag_be(AVIOContext *pb) +{ + avio_wb32(pb, 10); + ffio_wfourcc(pb, "enda"); + avio_wb16(pb, 0); /* big endian */ + return 10; +} + static void put_descr(AVIOContext *pb, int tag, unsigned int size) { int i = 3; @@ -302,11 +554,23 @@ 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 { AVCPBProperties *props; 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"); @@ -342,11 +606,9 @@ static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic avio_wb24(pb, props ? props->buffer_size / 8 : 0); // Buffersize DB - avio_wb32(pb, props ? FFMAX(props->max_bitrate, props->avg_bitrate) : track->par->bit_rate); // maxbitrate (FIXME should be max rate in any 1 sec window) - if (!props || !props->min_bitrate || props->max_bitrate != props->min_bitrate) - avio_wb32(pb, 0); // vbr - else - avio_wb32(pb, props->max_bitrate); // avg bitrate + avg_bitrate = compute_avg_bitrate(track); + avio_wb32(pb, props ? FFMAX3(props->max_bitrate, props->avg_bitrate, avg_bitrate) : FFMAX(track->par->bit_rate, avg_bitrate)); // maxbitrate (FIXME should be max rate in any 1 sec window) + avio_wb32(pb, avg_bitrate); if (track->vos_len) { // DecoderSpecific info descriptor @@ -360,22 +622,85 @@ static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic return update_size(pb, pos); } +static int mov_pcm_le_gt16(enum AVCodecID codec_id) +{ + return codec_id == AV_CODEC_ID_PCM_S24LE || + codec_id == AV_CODEC_ID_PCM_S32LE || + codec_id == AV_CODEC_ID_PCM_F32LE || + codec_id == AV_CODEC_ID_PCM_F64LE; +} + +static int mov_pcm_be_gt16(enum AVCodecID codec_id) +{ + return codec_id == AV_CODEC_ID_PCM_S24BE || + codec_id == AV_CODEC_ID_PCM_S32BE || + codec_id == AV_CODEC_ID_PCM_F32BE || + codec_id == AV_CODEC_ID_PCM_F64BE; +} + static int mov_write_ms_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { + int ret; int64_t pos = avio_tell(pb); avio_wb32(pb, 0); avio_wl32(pb, track->tag); // store it byteswapped track->par->codec_tag = av_bswap16(track->tag >> 16); - ff_put_wav_header(s, pb, track->st->codecpar); + if ((ret = ff_put_wav_header(s, pb, track->par, 0)) < 0) + return ret; return update_size(pb, pos); } static int mov_write_wfex_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { + int ret; int64_t pos = avio_tell(pb); avio_wb32(pb, 0); ffio_wfourcc(pb, "wfex"); - ff_put_wav_header(s, pb, track->st->codecpar); + if ((ret = ff_put_wav_header(s, pb, track->st->codecpar, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX)) < 0) + return ret; + return update_size(pb, pos); +} + +static int mov_write_dfla_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); + ffio_wfourcc(pb, "dfLa"); + avio_w8(pb, 0); /* version */ + avio_wb24(pb, 0); /* flags */ + + /* Expect the encoder to pass a METADATA_BLOCK_TYPE_STREAMINFO. */ + if (track->par->extradata_size != FLAC_STREAMINFO_SIZE) + return AVERROR_INVALIDDATA; + + /* TODO: Write other METADATA_BLOCK_TYPEs if the encoder makes them available. */ + avio_w8(pb, 1 << 7 | FLAC_METADATA_TYPE_STREAMINFO); /* LastMetadataBlockFlag << 7 | BlockType */ + avio_wb24(pb, track->par->extradata_size); /* Length */ + avio_write(pb, track->par->extradata, track->par->extradata_size); /* BlockData[Length] */ + + return update_size(pb, pos); +} + +static int mov_write_dops_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); + ffio_wfourcc(pb, "dOps"); + avio_w8(pb, 0); /* Version */ + if (track->par->extradata_size < 19) { + av_log(pb, AV_LOG_ERROR, "invalid extradata size\n"); + return AVERROR_INVALIDDATA; + } + /* extradata contains an Ogg OpusHead, other than byte-ordering and + OpusHead's preceeding magic/version, OpusSpecificBox is currently + identical. */ + avio_w8(pb, AV_RB8(track->par->extradata + 9)); /* OuputChannelCount */ + avio_wb16(pb, AV_RL16(track->par->extradata + 10)); /* PreSkip */ + avio_wb32(pb, AV_RL32(track->par->extradata + 12)); /* InputSampleRate */ + avio_wb16(pb, AV_RL16(track->par->extradata + 16)); /* OutputGain */ + /* Write the rest of the header out without byte-swapping. */ + avio_write(pb, track->par->extradata + 18, track->par->extradata_size - 18); + return update_size(pb, pos); } @@ -393,6 +718,9 @@ static int mov_write_chan_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra return 0; } + if (track->multichannel_as_mono) + return 0; + avio_wb32(pb, 0); // Size ffio_wfourcc(pb, "chan"); // Type avio_w8(pb, 0); // Version @@ -411,9 +739,11 @@ static int mov_write_wave_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "wave"); + if (track->par->codec_id != AV_CODEC_ID_QDM2) { avio_wb32(pb, 12); /* size */ ffio_wfourcc(pb, "frma"); avio_wl32(pb, track->tag); + } if (track->par->codec_id == AV_CODEC_ID_AAC) { /* useless atom needed by mplayer, ipod, not needed by quicktime */ @@ -421,11 +751,18 @@ static int mov_write_wave_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra ffio_wfourcc(pb, "mp4a"); avio_wb32(pb, 0); mov_write_esds_tag(pb, track); + } else if (mov_pcm_le_gt16(track->par->codec_id)) { + mov_write_enda_tag(pb); + } else if (mov_pcm_be_gt16(track->par->codec_id)) { + mov_write_enda_tag_be(pb); } else if (track->par->codec_id == AV_CODEC_ID_AMR_NB) { mov_write_amr_tag(pb, track); } else if (track->par->codec_id == AV_CODEC_ID_AC3) { mov_write_ac3_tag(pb, track); - } else if (track->par->codec_id == AV_CODEC_ID_ALAC) { + } else if (track->par->codec_id == AV_CODEC_ID_EAC3) { + mov_write_eac3_tag(pb, track); + } else if (track->par->codec_id == AV_CODEC_ID_ALAC || + track->par->codec_id == AV_CODEC_ID_QDM2) { mov_write_extradata_tag(pb, track); } else if (track->par->codec_id == AV_CODEC_ID_ADPCM_MS || track->par->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { @@ -463,28 +800,28 @@ static int mov_write_dvc1_structs(MOVTrack *track, uint8_t *buf) return AVERROR(ENOMEM); start = find_next_marker(track->vos_data, end); for (next = start; next < end; start = next) { - BitstreamContext bc; + GetBitContext gb; int size; next = find_next_marker(start + 4, end); size = next - start - 4; if (size <= 0) continue; unescaped_size = vc1_unescape_buffer(start + 4, size, unescaped); - bitstream_init8(&bc, unescaped, unescaped_size); + init_get_bits(&gb, unescaped, 8 * unescaped_size); if (AV_RB32(start) == VC1_CODE_SEQHDR) { - int profile = bitstream_read(&bc, 2); + int profile = get_bits(&gb, 2); if (profile != PROFILE_ADVANCED) { av_free(unescaped); return AVERROR(ENOSYS); } seq_found = 1; - level = bitstream_read(&bc, 3); + level = get_bits(&gb, 3); /* chromaformat, frmrtq_postproc, bitrtq_postproc, postprocflag, * width, height */ - bitstream_skip(&bc, 2 + 3 + 5 + 1 + 2 * 12); - bitstream_skip(&bc, 1); /* broadcast */ - interlace = bitstream_read_bit(&bc); - bitstream_skip(&bc, 4); /* tfcntrflag, finterpflag, reserved, psf */ + skip_bits_long(&gb, 2 + 3 + 5 + 1 + 2*12); + skip_bits(&gb, 1); /* broadcast */ + interlace = get_bits1(&gb); + skip_bits(&gb, 4); /* tfcntrflag, finterpflag, reserved, psf */ } } if (!seq_found) { @@ -586,13 +923,20 @@ static int get_cluster_duration(MOVTrack *track, int cluster_idx) else next_dts = track->cluster[cluster_idx + 1].dts; - return next_dts - track->cluster[cluster_idx].dts; + next_dts -= track->cluster[cluster_idx].dts; + + av_assert0(next_dts >= 0); + av_assert0(next_dts <= INT_MAX); + + return next_dts; } static int get_samples_per_packet(MOVTrack *track) { int i, first_duration; +// return track->par->frame_size; + /* use 1 for raw PCM */ if (!track->audio_vbr) return 1; @@ -608,20 +952,32 @@ static int get_samples_per_packet(MOVTrack *track) return first_duration; } -static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); int version = 0; uint32_t tag = track->tag; if (track->mode == MODE_MOV) { - if (mov_get_lpcm_flags(track->par->codec_id)) - tag = AV_RL32("lpcm"); - version = 2; + if (track->timescale > UINT16_MAX) { + if (mov_get_lpcm_flags(track->par->codec_id)) + tag = AV_RL32("lpcm"); + version = 2; + } else if (track->audio_vbr || mov_pcm_le_gt16(track->par->codec_id) || + mov_pcm_be_gt16(track->par->codec_id) || + track->par->codec_id == AV_CODEC_ID_ADPCM_MS || + track->par->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + track->par->codec_id == AV_CODEC_ID_QDM2) { + version = 1; + } } avio_wb32(pb, 0); /* size */ - avio_wl32(pb, tag); // store it byteswapped + if (mov->encryption_scheme != MOV_ENC_NONE) { + ffio_wfourcc(pb, "enca"); + } else { + avio_wl32(pb, tag); // store it byteswapped + } avio_wb32(pb, 0); /* Reserved */ avio_wb16(pb, 0); /* Reserved */ avio_wb16(pb, 1); /* Data-reference index, XXX == 1 */ @@ -646,24 +1002,62 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tr 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->par->channels); + if (track->par->codec_id == AV_CODEC_ID_PCM_U8 || + track->par->codec_id == AV_CODEC_ID_PCM_S8) + avio_wb16(pb, 8); /* bits per sample */ + else if (track->par->codec_id == AV_CODEC_ID_ADPCM_G726) + avio_wb16(pb, track->par->bits_per_coded_sample); + else + avio_wb16(pb, 16); + avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ + } else { /* reserved for mp4/3gp */ + if (track->par->codec_id == AV_CODEC_ID_FLAC || + track->par->codec_id == AV_CODEC_ID_OPUS) { + avio_wb16(pb, track->par->channels); + } else { + avio_wb16(pb, 2); + } + if (track->par->codec_id == AV_CODEC_ID_FLAC) { + avio_wb16(pb, track->par->bits_per_raw_sample); + } else { + avio_wb16(pb, 16); + } + avio_wb16(pb, 0); + } avio_wb16(pb, 0); /* packet size (= 0) */ - avio_wb16(pb, track->par->sample_rate <= UINT16_MAX ? - track->par->sample_rate : 0); + if (track->par->codec_id == AV_CODEC_ID_OPUS) + avio_wb16(pb, 48000); + else + avio_wb16(pb, track->par->sample_rate <= UINT16_MAX ? + track->par->sample_rate : 0); avio_wb16(pb, 0); /* Reserved */ } + if (version == 1) { /* SoundDescription V1 extended info */ + if (mov_pcm_le_gt16(track->par->codec_id) || + mov_pcm_be_gt16(track->par->codec_id)) + avio_wb32(pb, 1); /* must be 1 for uncompressed formats */ + else + avio_wb32(pb, track->par->frame_size); /* Samples per packet */ + avio_wb32(pb, track->sample_size / track->par->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->par->codec_id == AV_CODEC_ID_AAC || track->par->codec_id == AV_CODEC_ID_AC3 || + track->par->codec_id == AV_CODEC_ID_EAC3 || track->par->codec_id == AV_CODEC_ID_AMR_NB || track->par->codec_id == AV_CODEC_ID_ALAC || track->par->codec_id == AV_CODEC_ID_ADPCM_MS || - track->par->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV)) + track->par->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + track->par->codec_id == AV_CODEC_ID_QDM2 || + (mov_pcm_le_gt16(track->par->codec_id) && version==1) || + (mov_pcm_be_gt16(track->par->codec_id) && version==1))) mov_write_wave_tag(s, pb, track); else if (track->tag == MKTAG('m','p','4','a')) mov_write_esds_tag(pb, track); @@ -671,16 +1065,26 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tr mov_write_amr_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_AC3) mov_write_ac3_tag(pb, track); + else if (track->par->codec_id == AV_CODEC_ID_EAC3) + mov_write_eac3_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_ALAC) mov_write_extradata_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_WMAPRO) mov_write_wfex_tag(s, pb, track); + else if (track->par->codec_id == AV_CODEC_ID_FLAC) + mov_write_dfla_tag(pb, track); + else if (track->par->codec_id == AV_CODEC_ID_OPUS) + mov_write_dops_tag(pb, track); else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); if (track->mode == MODE_MOV && track->par->codec_type == AVMEDIA_TYPE_AUDIO) mov_write_chan_tag(s, pb, track); + if (mov->encryption_scheme != MOV_ENC_NONE) { + ff_mov_cenc_write_sinf_tag(track, pb, mov->encryption_kid); + } + return update_size(pb, pos); } @@ -696,19 +1100,6 @@ static int mov_write_d263_tag(AVIOContext *pb) return 0xf; } -/* TODO: No idea about these values */ -static int mov_write_svq3_tag(AVIOContext *pb) -{ - avio_wb32(pb, 0x15); - ffio_wfourcc(pb, "SMI "); - ffio_wfourcc(pb, "SEQH"); - avio_wb32(pb, 0x5); - avio_wb32(pb, 0xe2c0211d); - avio_wb32(pb, 0xc0000000); - avio_w8(pb, 0); - return 0x15; -} - static int mov_write_avcc_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); @@ -719,13 +1110,28 @@ static int mov_write_avcc_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_vpcc_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + + avio_wb32(pb, 0); + ffio_wfourcc(pb, "vpcC"); + avio_w8(pb, 1); /* version */ + avio_wb24(pb, 0); /* flags */ + ff_isom_write_vpcc(s, pb, track->par); + return update_size(pb, pos); +} + static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); avio_wb32(pb, 0); ffio_wfourcc(pb, "hvcC"); - ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0); + if (track->tag == MKTAG('h','v','c','1')) + ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 1); + else + ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0); return update_size(pb, pos); } @@ -733,13 +1139,48 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track) static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) { int i; + int interlaced; + int cid; + int display_width = track->par->width; + + if (track->vos_data && track->vos_len > 0x29) { + if (ff_dnxhd_parse_header_prefix(track->vos_data) != 0) { + /* looks like a DNxHD bit stream */ + interlaced = (track->vos_data[5] & 2); + cid = AV_RB32(track->vos_data + 0x28); + } else { + av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream in vos_data\n"); + return 0; + } + } else { + av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream, vos_data too small\n"); + return 0; + } + avio_wb32(pb, 24); /* size */ ffio_wfourcc(pb, "ACLR"); ffio_wfourcc(pb, "ACLR"); ffio_wfourcc(pb, "0001"); - avio_wb32(pb, 2); /* yuv range: full 1 / normal 2 */ + if (track->par->color_range == AVCOL_RANGE_MPEG || /* Legal range (16-235) */ + track->par->color_range == AVCOL_RANGE_UNSPECIFIED) { + avio_wb32(pb, 1); /* Corresponds to 709 in official encoder */ + } else { /* Full range (0-255) */ + avio_wb32(pb, 2); /* Corresponds to RGB in official encoder */ + } avio_wb32(pb, 0); /* unknown */ + if (track->tag == MKTAG('A','V','d','h')) { + avio_wb32(pb, 32); + ffio_wfourcc(pb, "ADHR"); + ffio_wfourcc(pb, "0001"); + avio_wb32(pb, cid); + avio_wb32(pb, 0); /* unknown */ + avio_wb32(pb, 1); /* unknown */ + avio_wb32(pb, 0); /* unknown */ + avio_wb32(pb, 0); /* unknown */ + return 0; + } + avio_wb32(pb, 24); /* size */ ffio_wfourcc(pb, "APRG"); ffio_wfourcc(pb, "APRG"); @@ -751,10 +1192,13 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) ffio_wfourcc(pb, "ARES"); ffio_wfourcc(pb, "ARES"); ffio_wfourcc(pb, "0001"); - avio_wb32(pb, AV_RB32(track->vos_data + 0x28)); /* dnxhd cid, some id ? */ - avio_wb32(pb, track->par->width); + avio_wb32(pb, cid); /* dnxhd cid, some id ? */ + if ( track->par->sample_aspect_ratio.num > 0 + && track->par->sample_aspect_ratio.den > 0) + display_width = display_width * track->par->sample_aspect_ratio.num / track->par->sample_aspect_ratio.den; + avio_wb32(pb, display_width); /* values below are based on samples created with quicktime and avid codecs */ - if (track->vos_data[5] & 2) { // interlaced + if (interlaced) { avio_wb32(pb, track->par->height / 2); avio_wb32(pb, 2); /* unknown */ avio_wb32(pb, 0); /* unknown */ @@ -772,77 +1216,40 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) for (i = 0; i < 10; i++) avio_wb64(pb, 0); - /* extra padding for stsd needed */ - avio_wb32(pb, 0); return 0; } -static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) -{ - int tag = track->par->codec_tag; - - if (!ff_codec_get_tag(ff_mp4_obj_type, track->par->codec_id)) - return 0; - - if (track->par->codec_id == AV_CODEC_ID_H264) tag = MKTAG('a','v','c','1'); - else if (track->par->codec_id == AV_CODEC_ID_HEVC) tag = MKTAG('h','e','v','1'); - else if (track->par->codec_id == AV_CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); - else if (track->par->codec_id == AV_CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); - else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); - else if (track->par->codec_id == AV_CODEC_ID_VC1) tag = MKTAG('v','c','-','1'); - else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); - else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); - else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) tag = MKTAG('m','p','4','s'); - - return tag; -} - -static const AVCodecTag codec_ipod_tags[] = { - { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, - { AV_CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, - { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, - { AV_CODEC_ID_ALAC, MKTAG('a','l','a','c') }, - { AV_CODEC_ID_AC3, MKTAG('a','c','-','3') }, - { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, - { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') }, - { AV_CODEC_ID_NONE, 0 }, -}; - -static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track) +static int mov_write_dpxe_tag(AVIOContext *pb, MOVTrack *track) { - int tag = track->par->codec_tag; - - // keep original tag for subs, ipod supports both formats - if (!(track->par->codec_type == AVMEDIA_TYPE_SUBTITLE && - (tag == MKTAG('t', 'x', '3', 'g') || - tag == MKTAG('t', 'e', 'x', 't')))) - tag = ff_codec_get_tag(codec_ipod_tags, track->par->codec_id); - - if (!av_match_ext(s->filename, "m4a") && !av_match_ext(s->filename, "m4v")) - av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " - "Quicktime/Ipod might not play the file\n"); - - return tag; + avio_wb32(pb, 12); + ffio_wfourcc(pb, "DpxE"); + if (track->par->extradata_size >= 12 && + !memcmp(&track->par->extradata[4], "DpxE", 4)) { + avio_wb32(pb, track->par->extradata[11]); + } else { + avio_wb32(pb, 1); + } + return 0; } static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag; - if (track->par->width == 720) /* SD */ - if (track->par->height == 480) /* NTSC */ - if (track->par->format == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); + if (track->par->width == 720) { /* SD */ + if (track->par->height == 480) { /* NTSC */ + if (track->par->format == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); else tag = MKTAG('d','v','c',' '); - else if (track->par->format == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); - else if (track->par->format == AV_PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); + }else if (track->par->format == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); + else if (track->par->format == AV_PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); else tag = MKTAG('d','v','p','p'); - else if (track->par->height == 720) /* HD 720 line */ + } else if (track->par->height == 720) { /* HD 720 line */ if (track->st->time_base.den == 50) tag = MKTAG('d','v','h','q'); else tag = MKTAG('d','v','h','p'); - else if (track->par->height == 1080) /* HD 1080 line */ + } else if (track->par->height == 1080) { /* HD 1080 line */ if (track->st->time_base.den == 25) tag = MKTAG('d','v','h','5'); else tag = MKTAG('d','v','h','6'); - else { + } else { av_log(s, AV_LOG_ERROR, "unsupported height for dv codec\n"); return 0; } @@ -850,11 +1257,159 @@ static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) return tag; } +static AVRational find_fps(AVFormatContext *s, AVStream *st) +{ + AVRational rate = st->avg_frame_rate; + +#if FF_API_LAVF_AVCTX + FF_DISABLE_DEPRECATION_WARNINGS + rate = av_inv_q(st->codec->time_base); + if (av_timecode_check_frame_rate(rate) < 0) { + av_log(s, AV_LOG_DEBUG, "timecode: tbc=%d/%d invalid, fallback on %d/%d\n", + rate.num, rate.den, st->avg_frame_rate.num, st->avg_frame_rate.den); + rate = st->avg_frame_rate; + } + FF_ENABLE_DEPRECATION_WARNINGS +#endif + + return rate; +} + +static int defined_frame_rate(AVFormatContext *s, AVStream *st) +{ + AVRational rational_framerate = find_fps(s, st); + int rate = 0; + if (rational_framerate.den != 0) + rate = av_q2d(rational_framerate); + return rate; +} + +static int mov_get_mpeg2_xdcam_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->par->codec_tag; + int interlaced = track->par->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = defined_frame_rate(s, st); + + if (!tag) + tag = MKTAG('m', '2', 'v', '1'); //fallback tag + + if (track->par->format == AV_PIX_FMT_YUV420P) { + if (track->par->width == 1280 && track->par->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','4'); + else if (rate == 25) tag = MKTAG('x','d','v','5'); + else if (rate == 30) tag = MKTAG('x','d','v','1'); + else if (rate == 50) tag = MKTAG('x','d','v','a'); + else if (rate == 60) tag = MKTAG('x','d','v','9'); + } + } else if (track->par->width == 1440 && track->par->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','6'); + else if (rate == 25) tag = MKTAG('x','d','v','7'); + else if (rate == 30) tag = MKTAG('x','d','v','8'); + } else { + if (rate == 25) tag = MKTAG('x','d','v','3'); + else if (rate == 30) tag = MKTAG('x','d','v','2'); + } + } else if (track->par->width == 1920 && track->par->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','v','d'); + else if (rate == 25) tag = MKTAG('x','d','v','e'); + else if (rate == 30) tag = MKTAG('x','d','v','f'); + } else { + if (rate == 25) tag = MKTAG('x','d','v','c'); + else if (rate == 30) tag = MKTAG('x','d','v','b'); + } + } + } else if (track->par->format == AV_PIX_FMT_YUV422P) { + if (track->par->width == 1280 && track->par->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','5','4'); + else if (rate == 25) tag = MKTAG('x','d','5','5'); + else if (rate == 30) tag = MKTAG('x','d','5','1'); + else if (rate == 50) tag = MKTAG('x','d','5','a'); + else if (rate == 60) tag = MKTAG('x','d','5','9'); + } + } else if (track->par->width == 1920 && track->par->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('x','d','5','d'); + else if (rate == 25) tag = MKTAG('x','d','5','e'); + else if (rate == 30) tag = MKTAG('x','d','5','f'); + } else { + if (rate == 25) tag = MKTAG('x','d','5','c'); + else if (rate == 30) tag = MKTAG('x','d','5','b'); + } + } + } + + return tag; +} + +static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->par->codec_tag; + int interlaced = track->par->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = defined_frame_rate(s, st); + + if (!tag) + tag = MKTAG('a', 'v', 'c', 'i'); //fallback tag + + if (track->par->format == AV_PIX_FMT_YUV420P10) { + if (track->par->width == 960 && track->par->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','5','p'); + else if (rate == 25) tag = MKTAG('a','i','5','q'); + else if (rate == 30) tag = MKTAG('a','i','5','p'); + else if (rate == 50) tag = MKTAG('a','i','5','q'); + else if (rate == 60) tag = MKTAG('a','i','5','p'); + } + } else if (track->par->width == 1440 && track->par->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','5','3'); + else if (rate == 25) tag = MKTAG('a','i','5','2'); + else if (rate == 30) tag = MKTAG('a','i','5','3'); + } else { + if (rate == 50) tag = MKTAG('a','i','5','5'); + else if (rate == 60) tag = MKTAG('a','i','5','6'); + } + } + } else if (track->par->format == AV_PIX_FMT_YUV422P10) { + if (track->par->width == 1280 && track->par->height == 720) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','1','p'); + else if (rate == 25) tag = MKTAG('a','i','1','q'); + else if (rate == 30) tag = MKTAG('a','i','1','p'); + else if (rate == 50) tag = MKTAG('a','i','1','q'); + else if (rate == 60) tag = MKTAG('a','i','1','p'); + } + } else if (track->par->width == 1920 && track->par->height == 1080) { + if (!interlaced) { + if (rate == 24) tag = MKTAG('a','i','1','3'); + else if (rate == 25) tag = MKTAG('a','i','1','2'); + else if (rate == 30) tag = MKTAG('a','i','1','3'); + } else { + if (rate == 25) tag = MKTAG('a','i','1','5'); + else if (rate == 50) tag = MKTAG('a','i','1','5'); + else if (rate == 60) tag = MKTAG('a','i','1','6'); + } + } else if ( track->par->width == 4096 && track->par->height == 2160 + || track->par->width == 3840 && track->par->height == 2160 + || track->par->width == 2048 && track->par->height == 1080) { + tag = MKTAG('a','i','v','x'); + } + } + + return tag; +} + static const struct { enum AVPixelFormat pix_fmt; uint32_t tag; unsigned bps; } mov_pix_fmt_tags[] = { + { AV_PIX_FMT_YUYV422, MKTAG('y','u','v','2'), 0 }, { AV_PIX_FMT_YUYV422, MKTAG('y','u','v','s'), 0 }, { AV_PIX_FMT_UYVY422, MKTAG('2','v','u','y'), 0 }, { AV_PIX_FMT_RGB555BE,MKTAG('r','a','w',' '), 16 }, @@ -871,19 +1426,38 @@ static const struct { { AV_PIX_FMT_RGB48BE, MKTAG('b','4','8','r'), 48 }, }; +static int mov_get_dnxhd_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = MKTAG('A','V','d','n'); + if (track->par->profile != FF_PROFILE_UNKNOWN && + track->par->profile != FF_PROFILE_DNXHD) + tag = MKTAG('A','V','d','h'); + return tag; +} + static int mov_get_rawvideo_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag = track->par->codec_tag; int i; + enum AVPixelFormat pix_fmt; for (i = 0; i < FF_ARRAY_ELEMS(mov_pix_fmt_tags); i++) { if (track->par->format == mov_pix_fmt_tags[i].pix_fmt) { tag = mov_pix_fmt_tags[i].tag; track->par->bits_per_coded_sample = mov_pix_fmt_tags[i].bps; - break; + if (track->par->codec_tag == mov_pix_fmt_tags[i].tag) + break; } } + pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_mov, + track->par->bits_per_coded_sample); + if (tag == MKTAG('r','a','w',' ') && + track->par->format != pix_fmt && + track->par->format != AV_PIX_FMT_GRAY8 && + track->par->format != AV_PIX_FMT_NONE) + av_log(s, AV_LOG_ERROR, "%s rawvideo cannot be written to mov, output file will be unreadable\n", + av_get_pix_fmt_name(track->par->format)); return tag; } @@ -895,11 +1469,20 @@ static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) (track->par->codec_id == AV_CODEC_ID_DVVIDEO || track->par->codec_id == AV_CODEC_ID_RAWVIDEO || track->par->codec_id == AV_CODEC_ID_H263 || + track->par->codec_id == AV_CODEC_ID_H264 || + track->par->codec_id == AV_CODEC_ID_DNXHD || + track->par->codec_id == AV_CODEC_ID_MPEG2VIDEO || av_get_bits_per_sample(track->par->codec_id)))) { // pcm audio if (track->par->codec_id == AV_CODEC_ID_DVVIDEO) tag = mov_get_dv_codec_tag(s, track); else if (track->par->codec_id == AV_CODEC_ID_RAWVIDEO) tag = mov_get_rawvideo_codec_tag(s, track); + else if (track->par->codec_id == AV_CODEC_ID_MPEG2VIDEO) + tag = mov_get_mpeg2_xdcam_codec_tag(s, track); + else if (track->par->codec_id == AV_CODEC_ID_H264) + tag = mov_get_h264_codec_tag(s, track); + else if (track->par->codec_id == AV_CODEC_ID_DNXHD) + tag = mov_get_dnxhd_codec_tag(s, track); else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { tag = ff_codec_get_tag(ff_codec_movvideo_tags, track->par->codec_id); if (!tag) { // if no mac fcc found, try with Microsoft tags @@ -925,42 +1508,25 @@ static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) return tag; } -static const AVCodecTag codec_3gp_tags[] = { - { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, - { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, - { AV_CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, - { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, - { AV_CODEC_ID_AMR_NB, MKTAG('s','a','m','r') }, - { AV_CODEC_ID_AMR_WB, MKTAG('s','a','w','b') }, - { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, - { AV_CODEC_ID_NONE, 0 }, -}; - -static const AVCodecTag codec_f4v_tags[] = { - { AV_CODEC_ID_MP3, MKTAG('.','m','p','3') }, - { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, - { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, - { AV_CODEC_ID_VP6A, MKTAG('V','P','6','A') }, - { AV_CODEC_ID_VP6F, MKTAG('V','P','6','F') }, - { AV_CODEC_ID_NONE, 0 }, -}; - static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag; if (track->mode == MODE_MP4 || track->mode == MODE_PSP) - tag = mp4_get_codec_tag(s, track); - else if (track->mode == MODE_ISM) { - tag = mp4_get_codec_tag(s, track); - if (!tag && track->par->codec_id == AV_CODEC_ID_WMAPRO) - tag = MKTAG('w', 'm', 'a', ' '); - } else if (track->mode == MODE_IPOD) - tag = ipod_get_codec_tag(s, track); - else if (track->mode & MODE_3GP) - tag = ff_codec_get_tag(codec_3gp_tags, track->par->codec_id); + tag = track->par->codec_tag; + else if (track->mode == MODE_ISM) + tag = track->par->codec_tag; + else if (track->mode == MODE_IPOD) { + if (!av_match_ext(s->filename, "m4a") && + !av_match_ext(s->filename, "m4v") && + !av_match_ext(s->filename, "m4b")) + av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " + "Quicktime/Ipod might not play the file\n"); + tag = track->par->codec_tag; + } else if (track->mode & MODE_3GP) + tag = track->par->codec_tag; else if (track->mode == MODE_F4V) - tag = ff_codec_get_tag(codec_f4v_tags, track->par->codec_id); + tag = track->par->codec_tag; else tag = mov_get_codec_tag(s, track); @@ -987,11 +1553,11 @@ static const uint16_t fiel_data[] = { 0x0000, 0x0100, 0x0201, 0x0206, 0x0209, 0x020e }; -static int mov_write_fiel_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_fiel_tag(AVIOContext *pb, MOVTrack *track, int field_order) { unsigned mov_field_order = 0; - if (track->par->field_order < FF_ARRAY_ELEMS(fiel_data)) - mov_field_order = fiel_data[track->par->field_order]; + if (field_order < FF_ARRAY_ELEMS(fiel_data)) + mov_field_order = fiel_data[field_order]; else return 0; avio_wb32(pb, 10); @@ -1017,6 +1583,94 @@ static int mov_write_subtitle_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_st3d_tag(AVIOContext *pb, AVStereo3D *stereo_3d) +{ + int8_t stereo_mode; + + if (stereo_3d->flags != 0) { + av_log(pb, AV_LOG_WARNING, "Unsupported stereo_3d flags %x. st3d not written.\n", stereo_3d->flags); + return 0; + } + + switch (stereo_3d->type) { + case AV_STEREO3D_2D: + stereo_mode = 0; + break; + case AV_STEREO3D_TOPBOTTOM: + stereo_mode = 1; + break; + case AV_STEREO3D_SIDEBYSIDE: + stereo_mode = 2; + break; + default: + av_log(pb, AV_LOG_WARNING, "Unsupported stereo_3d type %s. st3d not written.\n", av_stereo3d_type_name(stereo_3d->type)); + return 0; + } + avio_wb32(pb, 13); /* size */ + ffio_wfourcc(pb, "st3d"); + avio_wb32(pb, 0); /* version = 0 & flags = 0 */ + avio_w8(pb, stereo_mode); + return 13; +} + +static int mov_write_sv3d_tag(AVFormatContext *s, AVIOContext *pb, AVSphericalMapping *spherical_mapping) +{ + int64_t sv3d_pos, svhd_pos, proj_pos; + const char* metadata_source = s->flags & AVFMT_FLAG_BITEXACT ? "Lavf" : LIBAVFORMAT_IDENT; + + if (spherical_mapping->projection != AV_SPHERICAL_EQUIRECTANGULAR && + spherical_mapping->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE && + spherical_mapping->projection != AV_SPHERICAL_CUBEMAP) { + av_log(pb, AV_LOG_WARNING, "Unsupported projection %d. sv3d not written.\n", spherical_mapping->projection); + return 0; + } + + sv3d_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "sv3d"); + + svhd_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "svhd"); + avio_wb32(pb, 0); /* version = 0 & flags = 0 */ + avio_put_str(pb, metadata_source); + update_size(pb, svhd_pos); + + proj_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "proj"); + + avio_wb32(pb, 24); /* size */ + ffio_wfourcc(pb, "prhd"); + avio_wb32(pb, 0); /* version = 0 & flags = 0 */ + avio_wb32(pb, spherical_mapping->yaw); + avio_wb32(pb, spherical_mapping->pitch); + avio_wb32(pb, spherical_mapping->roll); + + switch (spherical_mapping->projection) { + case AV_SPHERICAL_EQUIRECTANGULAR: + case AV_SPHERICAL_EQUIRECTANGULAR_TILE: + avio_wb32(pb, 28); /* size */ + ffio_wfourcc(pb, "equi"); + avio_wb32(pb, 0); /* version = 0 & flags = 0 */ + avio_wb32(pb, spherical_mapping->bound_top); + avio_wb32(pb, spherical_mapping->bound_bottom); + avio_wb32(pb, spherical_mapping->bound_left); + avio_wb32(pb, spherical_mapping->bound_right); + break; + case AV_SPHERICAL_CUBEMAP: + avio_wb32(pb, 20); /* size */ + ffio_wfourcc(pb, "cbmp"); + avio_wb32(pb, 0); /* version = 0 & flags = 0 */ + avio_wb32(pb, 0); /* layout */ + avio_wb32(pb, spherical_mapping->padding); /* padding */ + break; + } + update_size(pb, proj_pos); + + return update_size(pb, sv3d_pos); +} + static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) { AVRational sar; @@ -1030,14 +1684,145 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) return 16; } -static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_gama_tag(AVIOContext *pb, MOVTrack *track, double gamma) +{ + uint32_t gama = 0; + if (gamma <= 0.0) + { + gamma = avpriv_get_gamma_from_trc(track->par->color_trc); + } + av_log(pb, AV_LOG_DEBUG, "gamma value %g\n", gamma); + + if (gamma > 1e-6) { + gama = (uint32_t)lrint((double)(1<<16) * gamma); + av_log(pb, AV_LOG_DEBUG, "writing gama value %"PRId32"\n", gama); + + av_assert0(track->mode == MODE_MOV); + avio_wb32(pb, 12); + ffio_wfourcc(pb, "gama"); + avio_wb32(pb, gama); + return 12; + } + else { + av_log(pb, AV_LOG_WARNING, "gamma value unknown, unable to write gama atom\n"); + } + return 0; +} + +static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track) +{ + // Ref (MOV): https://developer.apple.com/library/mac/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG9 + // Ref (MP4): ISO/IEC 14496-12:2012 + + if (track->par->color_primaries == AVCOL_PRI_UNSPECIFIED && + track->par->color_trc == AVCOL_TRC_UNSPECIFIED && + track->par->color_space == AVCOL_SPC_UNSPECIFIED) { + if ((track->par->width >= 1920 && track->par->height >= 1080) + || (track->par->width == 1280 && track->par->height == 720)) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt709\n"); + track->par->color_primaries = AVCOL_PRI_BT709; + } else if (track->par->width == 720 && track->height == 576) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt470bg\n"); + track->par->color_primaries = AVCOL_PRI_BT470BG; + } else if (track->par->width == 720 && + (track->height == 486 || track->height == 480)) { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming smpte170\n"); + track->par->color_primaries = AVCOL_PRI_SMPTE170M; + } else { + av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, unable to assume anything\n"); + } + switch (track->par->color_primaries) { + case AVCOL_PRI_BT709: + track->par->color_trc = AVCOL_TRC_BT709; + track->par->color_space = AVCOL_SPC_BT709; + break; + case AVCOL_PRI_SMPTE170M: + case AVCOL_PRI_BT470BG: + track->par->color_trc = AVCOL_TRC_BT709; + track->par->color_space = AVCOL_SPC_SMPTE170M; + break; + } + } + + /* We should only ever be called by MOV or MP4. */ + av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4); + + avio_wb32(pb, 18 + (track->mode == MODE_MP4)); + ffio_wfourcc(pb, "colr"); + if (track->mode == MODE_MP4) + ffio_wfourcc(pb, "nclx"); + else + ffio_wfourcc(pb, "nclc"); + switch (track->par->color_primaries) { + case AVCOL_PRI_BT709: avio_wb16(pb, 1); break; + case AVCOL_PRI_SMPTE170M: + case AVCOL_PRI_SMPTE240M: avio_wb16(pb, 6); break; + case AVCOL_PRI_BT470BG: avio_wb16(pb, 5); break; + default: avio_wb16(pb, 2); + } + switch (track->par->color_trc) { + case AVCOL_TRC_BT709: avio_wb16(pb, 1); break; + case AVCOL_TRC_SMPTE170M: avio_wb16(pb, 1); break; // remapped + case AVCOL_TRC_SMPTE240M: avio_wb16(pb, 7); break; + default: avio_wb16(pb, 2); + } + switch (track->par->color_space) { + case AVCOL_SPC_BT709: avio_wb16(pb, 1); break; + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: avio_wb16(pb, 6); break; + case AVCOL_SPC_SMPTE240M: avio_wb16(pb, 7); break; + default: avio_wb16(pb, 2); + } + + if (track->mode == MODE_MP4) { + int full_range = track->par->color_range == AVCOL_RANGE_JPEG; + avio_w8(pb, full_range << 7); + return 19; + } else { + return 18; + } +} + +static void find_compressor(char * compressor_name, int len, MOVTrack *track) { AVDictionaryEntry *encoder; + int xdcam_res = (track->par->width == 1280 && track->par->height == 720) + || (track->par->width == 1440 && track->par->height == 1080) + || (track->par->width == 1920 && track->par->height == 1080); + + if (track->mode == MODE_MOV && + (encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) { + av_strlcpy(compressor_name, encoder->value, 32); + } else if (track->par->codec_id == AV_CODEC_ID_MPEG2VIDEO && xdcam_res) { + int interlaced = track->par->field_order > AV_FIELD_PROGRESSIVE; + AVStream *st = track->st; + int rate = defined_frame_rate(NULL, st); + av_strlcatf(compressor_name, len, "XDCAM"); + if (track->par->format == AV_PIX_FMT_YUV422P) { + av_strlcatf(compressor_name, len, " HD422"); + } else if(track->par->width == 1440) { + av_strlcatf(compressor_name, len, " HD"); + } else + av_strlcatf(compressor_name, len, " EX"); + + av_strlcatf(compressor_name, len, " %d%c", track->par->height, interlaced ? 'i' : 'p'); + + av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); + } +} + +static int mov_write_video_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) +{ int64_t pos = avio_tell(pb); char compressor_name[32] = { 0 }; + int avid = 0; avio_wb32(pb, 0); /* size */ - avio_wl32(pb, track->tag); // store it byteswapped + if (mov->encryption_scheme != MOV_ENC_NONE) { + ffio_wfourcc(pb, "encv"); + } else { + avio_wl32(pb, track->tag); // store it byteswapped + } avio_wb32(pb, 0); /* Reserved */ avio_wb16(pb, 0); /* Reserved */ avio_wb16(pb, 1); /* Data-reference index */ @@ -1066,47 +1851,119 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) avio_wb16(pb, 1); /* Frame count (= 1) */ /* FIXME not sure, ISO 14496-1 draft where it shall be set to 0 */ - if (track->mode == MODE_MOV && - (encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) - av_strlcpy(compressor_name, encoder->value, 32); + find_compressor(compressor_name, 32, track); avio_w8(pb, strlen(compressor_name)); avio_write(pb, compressor_name, 31); if (track->mode == MODE_MOV && track->par->bits_per_coded_sample) - avio_wb16(pb, track->par->bits_per_coded_sample); + avio_wb16(pb, track->par->bits_per_coded_sample | + (track->par->format == AV_PIX_FMT_GRAY8 ? 0x20 : 0)); else avio_wb16(pb, 0x18); /* Reserved */ - avio_wb16(pb, 0xffff); /* Reserved */ + + if (track->mode == MODE_MOV && track->par->format == AV_PIX_FMT_PAL8) { + int pal_size = 1 << track->par->bits_per_coded_sample; + int i; + avio_wb16(pb, 0); /* Color table ID */ + avio_wb32(pb, 0); /* Color table seed */ + avio_wb16(pb, 0x8000); /* Color table flags */ + avio_wb16(pb, pal_size - 1); /* Color table size (zero-relative) */ + for (i = 0; i < pal_size; i++) { + uint32_t rgb = track->palette[i]; + uint16_t r = (rgb >> 16) & 0xff; + uint16_t g = (rgb >> 8) & 0xff; + uint16_t b = rgb & 0xff; + avio_wb16(pb, 0); + avio_wb16(pb, (r << 8) | r); + avio_wb16(pb, (g << 8) | g); + avio_wb16(pb, (b << 8) | b); + } + } else + avio_wb16(pb, 0xffff); /* Reserved */ + if (track->tag == MKTAG('m','p','4','v')) mov_write_esds_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_H263) mov_write_d263_tag(pb); - else if (track->par->codec_id == AV_CODEC_ID_SVQ3) - mov_write_svq3_tag(pb); - else if (track->par->codec_id == AV_CODEC_ID_DNXHD) + else if (track->par->codec_id == AV_CODEC_ID_AVUI || + track->par->codec_id == AV_CODEC_ID_SVQ3) { + mov_write_extradata_tag(pb, track); + avio_wb32(pb, 0); + } else if (track->par->codec_id == AV_CODEC_ID_DNXHD) { mov_write_avid_tag(pb, track); - else if (track->par->codec_id == AV_CODEC_ID_HEVC) + avid = 1; + } else if (track->par->codec_id == AV_CODEC_ID_HEVC) mov_write_hvcc_tag(pb, track); - else if (track->par->codec_id == AV_CODEC_ID_H264) { + else if (track->par->codec_id == AV_CODEC_ID_H264 && !TAG_IS_AVCI(track->tag)) { mov_write_avcc_tag(pb, track); if (track->mode == MODE_IPOD) mov_write_uuid_tag_ipod(pb); - } else if (track->par->field_order != AV_FIELD_UNKNOWN) - mov_write_fiel_tag(pb, track); - else if (track->par->codec_id == AV_CODEC_ID_VC1 && track->vos_len > 0) + } else if (track->par->codec_id == AV_CODEC_ID_VP9) { + mov_write_vpcc_tag(mov->fc, pb, track); + } else if (track->par->codec_id == AV_CODEC_ID_VC1 && track->vos_len > 0) mov_write_dvc1_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_VP6F || track->par->codec_id == AV_CODEC_ID_VP6A) { /* Don't write any potential extradata here - the cropping * is signalled via the normal width/height fields. */ + } else if (track->par->codec_id == AV_CODEC_ID_R10K) { + if (track->par->codec_tag == MKTAG('R','1','0','k')) + mov_write_dpxe_tag(pb, track); } else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); - if (track->par->sample_aspect_ratio.den && track->par->sample_aspect_ratio.num && - track->par->sample_aspect_ratio.den != track->par->sample_aspect_ratio.num) { + if (track->par->codec_id != AV_CODEC_ID_H264 && + track->par->codec_id != AV_CODEC_ID_MPEG4 && + track->par->codec_id != AV_CODEC_ID_DNXHD) { + int field_order = track->par->field_order; + +#if FF_API_LAVF_AVCTX + FF_DISABLE_DEPRECATION_WARNINGS + if (field_order != track->st->codec->field_order && track->st->codec->field_order != AV_FIELD_UNKNOWN) + field_order = track->st->codec->field_order; + FF_ENABLE_DEPRECATION_WARNINGS +#endif + + if (field_order != AV_FIELD_UNKNOWN) + mov_write_fiel_tag(pb, track, field_order); + } + + if (mov->flags & FF_MOV_FLAG_WRITE_GAMA) { + if (track->mode == MODE_MOV) + mov_write_gama_tag(pb, track, mov->gamma); + else + av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n"); + } + if (mov->flags & FF_MOV_FLAG_WRITE_COLR) { + if (track->mode == MODE_MOV || track->mode == MODE_MP4) + mov_write_colr_tag(pb, track); + else + av_log(mov->fc, AV_LOG_WARNING, "Not writing 'colr' atom. Format is not MOV or MP4.\n"); + } + + if (track->mode == MODE_MP4 && mov->fc->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL) { + AVStereo3D* stereo_3d = (AVStereo3D*) av_stream_get_side_data(track->st, AV_PKT_DATA_STEREO3D, NULL); + AVSphericalMapping* spherical_mapping = (AVSphericalMapping*)av_stream_get_side_data(track->st, AV_PKT_DATA_SPHERICAL, NULL); + + if (stereo_3d) + mov_write_st3d_tag(pb, stereo_3d); + if (spherical_mapping) + mov_write_sv3d_tag(mov->fc, pb, spherical_mapping); + } + + if (track->par->sample_aspect_ratio.den && track->par->sample_aspect_ratio.num) { mov_write_pasp_tag(pb, track); } + if (mov->encryption_scheme != MOV_ENC_NONE) { + ff_mov_cenc_write_sinf_tag(track, pb, mov->encryption_kid); + } + + /* extra padding for avid stsd */ + /* https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-61112 */ + if (avid) + avio_wb32(pb, 0); + return update_size(pb, pos); } @@ -1130,9 +1987,70 @@ static int mov_write_rtp_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_source_reference_tag(AVIOContext *pb, MOVTrack *track, const char *reel_name) +{ + uint64_t str_size =strlen(reel_name); + int64_t pos = avio_tell(pb); + + if (str_size >= UINT16_MAX){ + av_log(NULL, AV_LOG_ERROR, "reel_name length %"PRIu64" is too large\n", str_size); + avio_wb16(pb, 0); + return AVERROR(EINVAL); + } + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "name"); /* Data format */ + avio_wb16(pb, str_size); /* string size */ + avio_wb16(pb, track->language); /* langcode */ + avio_write(pb, reel_name, str_size); /* reel name */ + return update_size(pb,pos); +} + static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); +#if 1 + int frame_duration; + int nb_frames; + AVDictionaryEntry *t = NULL; + + if (!track->st->avg_frame_rate.num || !track->st->avg_frame_rate.den) { +#if FF_API_LAVF_AVCTX + FF_DISABLE_DEPRECATION_WARNINGS + frame_duration = av_rescale(track->timescale, track->st->codec->time_base.num, track->st->codec->time_base.den); + nb_frames = ROUNDED_DIV(track->st->codec->time_base.den, track->st->codec->time_base.num); + FF_ENABLE_DEPRECATION_WARNINGS +#else + av_log(NULL, AV_LOG_ERROR, "avg_frame_rate not set for tmcd track.\n"); + return AVERROR(EINVAL); +#endif + } else { + frame_duration = av_rescale(track->timescale, track->st->avg_frame_rate.num, track->st->avg_frame_rate.den); + nb_frames = ROUNDED_DIV(track->st->avg_frame_rate.den, track->st->avg_frame_rate.num); + } + + if (nb_frames > 255) { + av_log(NULL, AV_LOG_ERROR, "fps %d is too large\n", nb_frames); + return AVERROR(EINVAL); + } + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); /* Data format */ + avio_wb32(pb, 0); /* Reserved */ + avio_wb32(pb, 1); /* Data reference index */ + avio_wb32(pb, 0); /* Flags */ + avio_wb32(pb, track->timecode_flags); /* Flags (timecode) */ + avio_wb32(pb, track->timescale); /* Timescale */ + avio_wb32(pb, frame_duration); /* Frame duration */ + avio_w8(pb, nb_frames); /* Number of frames */ + avio_w8(pb, 0); /* Reserved */ + + t = av_dict_get(track->st->metadata, "reel_name", NULL, 0); + if (t && utf8len(t->value) && track->mode != MODE_MP4) + mov_write_source_reference_tag(pb, track, t->value); + else + avio_wb16(pb, 0); /* zero size */ +#else avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "tmcd"); /* Data format */ @@ -1140,10 +2058,23 @@ static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 1); /* Data reference index */ if (track->par->extradata_size) avio_write(pb, track->par->extradata, track->par->extradata_size); +#endif + return update_size(pb, pos); +} + +static int mov_write_gpmd_tag(AVIOContext *pb, const MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "gpmd"); + avio_wb32(pb, 0); /* Reserved */ + avio_wb16(pb, 0); /* Reserved */ + avio_wb16(pb, 1); /* Data-reference index */ + avio_wb32(pb, 0); /* Reserved */ return update_size(pb, pos); } -static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); avio_wb32(pb, 0); /* size */ @@ -1151,26 +2082,29 @@ static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra avio_wb32(pb, 0); /* version & flags */ avio_wb32(pb, 1); /* entry count */ if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) - mov_write_video_tag(pb, track); + mov_write_video_tag(pb, mov, track); else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) - mov_write_audio_tag(s, pb, track); + mov_write_audio_tag(s, pb, mov, track); else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) mov_write_subtitle_tag(pb, track); else if (track->par->codec_tag == MKTAG('r','t','p',' ')) mov_write_rtp_tag(pb, track); else if (track->par->codec_tag == MKTAG('t','m','c','d')) mov_write_tmcd_tag(pb, track); + else if (track->par->codec_tag == MKTAG('g','p','m','d')) + mov_write_gpmd_tag(pb, track); return update_size(pb, pos); } -static int mov_write_ctts_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_ctts_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { + MOVMuxContext *mov = s->priv_data; MOVStts *ctts_entries; uint32_t entries = 0; uint32_t atom_size; int i; - ctts_entries = av_malloc((track->entry + 1) * sizeof(*ctts_entries)); /* worst case */ + ctts_entries = av_malloc_array((track->entry + 1), sizeof(*ctts_entries)); /* worst case */ if (!ctts_entries) return AVERROR(ENOMEM); ctts_entries[0].count = 1; @@ -1188,7 +2122,11 @@ static int mov_write_ctts_tag(AVIOContext *pb, MOVTrack *track) atom_size = 16 + (entries * 8); avio_wb32(pb, atom_size); /* size */ ffio_wfourcc(pb, "ctts"); - avio_wb32(pb, 0); /* version & flags */ + if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + avio_w8(pb, 1); /* version */ + else + avio_w8(pb, 0); /* version */ + avio_wb24(pb, 0); /* flags */ avio_wb32(pb, entries); /* entry count */ for (i = 0; i < entries; i++) { avio_wb32(pb, ctts_entries[i].count); @@ -1215,7 +2153,7 @@ static int mov_write_stts_tag(AVIOContext *pb, MOVTrack *track) entries = 1; } else { if (track->entry) { - stts_entries = av_malloc(track->entry * sizeof(*stts_entries)); /* worst case */ + stts_entries = av_malloc_array(track->entry, sizeof(*stts_entries)); /* worst case */ if (!stts_entries) return AVERROR(ENOMEM); } @@ -1252,18 +2190,116 @@ 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 */ return 28; } -static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +static int mov_preroll_write_stbl_atoms(AVIOContext *pb, MOVTrack *track) +{ + struct sgpd_entry { + int count; + int16_t roll_distance; + int group_description_index; + }; + + struct sgpd_entry *sgpd_entries = NULL; + int entries = -1; + int group = 0; + int i, j; + + const int OPUS_SEEK_PREROLL_MS = 80; + int roll_samples = av_rescale_q(OPUS_SEEK_PREROLL_MS, + (AVRational){1, 1000}, + (AVRational){1, 48000}); + + if (!track->entry) + return 0; + + sgpd_entries = av_malloc_array(track->entry, sizeof(*sgpd_entries)); + if (!sgpd_entries) + return AVERROR(ENOMEM); + + av_assert0(track->par->codec_id == AV_CODEC_ID_OPUS || track->par->codec_id == AV_CODEC_ID_AAC); + + if (track->par->codec_id == AV_CODEC_ID_OPUS) { + for (i = 0; i < track->entry; i++) { + int roll_samples_remaining = roll_samples; + int distance = 0; + for (j = i - 1; j >= 0; j--) { + roll_samples_remaining -= get_cluster_duration(track, j); + distance++; + if (roll_samples_remaining <= 0) + break; + } + /* We don't have enough preceeding samples to compute a valid + roll_distance here, so this sample can't be independently + decoded. */ + if (roll_samples_remaining > 0) + distance = 0; + /* Verify distance is a minimum of 2 (60ms) packets and a maximum of + 32 (2.5ms) packets. */ + av_assert0(distance == 0 || (distance >= 2 && distance <= 32)); + if (i && distance == sgpd_entries[entries].roll_distance) { + sgpd_entries[entries].count++; + } else { + entries++; + sgpd_entries[entries].count = 1; + sgpd_entries[entries].roll_distance = distance; + sgpd_entries[entries].group_description_index = distance ? ++group : 0; + } + } + } else { + entries++; + sgpd_entries[entries].count = track->sample_count; + sgpd_entries[entries].roll_distance = 1; + sgpd_entries[entries].group_description_index = ++group; + } + entries++; + + if (!group) { + av_free(sgpd_entries); + return 0; + } + + /* Write sgpd tag */ + avio_wb32(pb, 24 + (group * 2)); /* size */ + ffio_wfourcc(pb, "sgpd"); + avio_wb32(pb, 1 << 24); /* fullbox */ + ffio_wfourcc(pb, "roll"); + avio_wb32(pb, 2); /* default_length */ + avio_wb32(pb, group); /* entry_count */ + for (i = 0; i < entries; i++) { + if (sgpd_entries[i].group_description_index) { + avio_wb16(pb, -sgpd_entries[i].roll_distance); /* roll_distance */ + } + } + + /* Write sbgp tag */ + avio_wb32(pb, 20 + (entries * 8)); /* size */ + ffio_wfourcc(pb, "sbgp"); + avio_wb32(pb, 0); /* fullbox */ + ffio_wfourcc(pb, "roll"); + avio_wb32(pb, entries); /* entry_count */ + for (i = 0; i < entries; i++) { + avio_wb32(pb, sgpd_entries[i].count); /* sample_count */ + avio_wb32(pb, sgpd_entries[i].group_description_index); /* group_description_index */ + } + + av_free(sgpd_entries); + return 0; +} + +static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "stbl"); - mov_write_stsd_tag(s, pb, track); + mov_write_stsd_tag(s, pb, mov, track); mov_write_stts_tag(pb, track); if ((track->par->codec_type == AVMEDIA_TYPE_VIDEO || track->par->codec_tag == MKTAG('r','t','p',' ')) && @@ -1272,11 +2308,20 @@ static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra if (track->mode == MODE_MOV && track->flags & MOV_TRACK_STPS) mov_write_stss_tag(pb, track, MOV_PARTIAL_SYNC_SAMPLE); if (track->par->codec_type == AVMEDIA_TYPE_VIDEO && - track->flags & MOV_TRACK_CTTS && track->entry) - mov_write_ctts_tag(pb, track); + track->flags & MOV_TRACK_CTTS && track->entry) { + + if ((ret = mov_write_ctts_tag(s, pb, track)) < 0) + return ret; + } mov_write_stsc_tag(pb, track); mov_write_stsz_tag(pb, track); mov_write_stco_tag(pb, track); + if (track->cenc.aes_ctr) { + ff_mov_cenc_write_stbl_atoms(&track->cenc, pb); + } + if (track->par->codec_id == AV_CODEC_ID_OPUS || track->par->codec_id == AV_CODEC_ID_AAC) { + mov_preroll_write_stbl_atoms(pb, track); + } return update_size(pb, pos); } @@ -1297,9 +2342,32 @@ static int mov_write_nmhd_tag(AVIOContext *pb) return 12; } -static int mov_write_gmhd_tag(AVIOContext *pb) +static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track) { - avio_wb32(pb, 0x20); /* size */ + int64_t pos = avio_tell(pb); + const char *font = "Lucida Grande"; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tcmi"); /* timecode media information atom */ + avio_wb32(pb, 0); /* version & flags */ + avio_wb16(pb, 0); /* text font */ + avio_wb16(pb, 0); /* text face */ + avio_wb16(pb, 12); /* text size */ + avio_wb16(pb, 0); /* (unknown, not in the QT specs...) */ + avio_wb16(pb, 0x0000); /* text color (red) */ + avio_wb16(pb, 0x0000); /* text color (green) */ + avio_wb16(pb, 0x0000); /* text color (blue) */ + avio_wb16(pb, 0xffff); /* background color (red) */ + avio_wb16(pb, 0xffff); /* background color (green) */ + avio_wb16(pb, 0xffff); /* background color (blue) */ + avio_w8(pb, strlen(font)); /* font len (part of the pascal string) */ + avio_write(pb, font, strlen(font)); /* font name */ + return update_size(pb, pos); +} + +static int mov_write_gmhd_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "gmhd"); avio_wb32(pb, 0x18); /* gmin size */ ffio_wfourcc(pb, "gmin");/* generic media info */ @@ -1310,7 +2378,42 @@ static int mov_write_gmhd_tag(AVIOContext *pb) avio_wb16(pb, 0x8000); /* opColor (b?) */ avio_wb16(pb, 0); /* balance */ avio_wb16(pb, 0); /* reserved */ - return 0x20; + + /* + * This special text atom is required for + * Apple Quicktime chapters. The contents + * don't appear to be documented, so the + * bytes are copied verbatim. + */ + if (track->tag != MKTAG('c','6','0','8')) { + avio_wb32(pb, 0x2C); /* size */ + ffio_wfourcc(pb, "text"); + avio_wb16(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00004000); + avio_wb16(pb, 0x0000); + } + + if (track->par->codec_tag == MKTAG('t','m','c','d')) { + int64_t tmcd_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); + mov_write_tcmi_tag(pb, track); + update_size(pb, tmcd_pos); + } else if (track->par->codec_tag == MKTAG('g','p','m','d')) { + int64_t gpmd_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "gpmd"); + avio_wb32(pb, 0); /* version */ + update_size(pb, gpmd_pos); + } + return update_size(pb, pos); } static int mov_write_smhd_tag(AVIOContext *pb) @@ -1356,30 +2459,32 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra hdlr_type = "soun"; descr = "SoundHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) { - if (track->tag == MKTAG('t','x','3','g')) { - hdlr_type = "sbtl"; - } else if (track->tag == MKTAG('m','p','4','s')) { - hdlr_type = "subp"; - } else if (is_clcp_track(track)) { + if (is_clcp_track(track)) { hdlr_type = "clcp"; + descr = "ClosedCaptionHandler"; } else { - hdlr_type = "text"; - } + if (track->tag == MKTAG('t','x','3','g')) { + hdlr_type = "sbtl"; + } else if (track->tag == MKTAG('m','p','4','s')) { + hdlr_type = "subp"; + } else { + hdlr_type = "text"; + } descr = "SubtitleHandler"; + } } else if (track->par->codec_tag == MKTAG('r','t','p',' ')) { hdlr_type = "hint"; descr = "HintHandler"; } else if (track->par->codec_tag == MKTAG('t','m','c','d')) { hdlr_type = "tmcd"; descr = "TimeCodeHandler"; + } else if (track->par->codec_tag == MKTAG('g','p','m','d')) { + hdlr_type = "meta"; + descr = "GoPro MET"; // GoPro Metadata } else { - char tag_buf[32]; - av_get_codec_tag_string(tag_buf, sizeof(tag_buf), - track->par->codec_tag); - av_log(s, AV_LOG_WARNING, - "Unknown hldr_type for %s / 0x%04"PRIX32", writing dummy values\n", - tag_buf, track->par->codec_tag); + "Unknown hldr_type for %s, writing dummy values\n", + av_fourcc2str(track->par->codec_tag)); } if (track->st) { // hdlr.name is used by some players to identify the content title @@ -1423,9 +2528,11 @@ static int mov_write_hmhd_tag(AVIOContext *pb) return 28; } -static int mov_write_minf_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +static int mov_write_minf_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "minf"); if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) @@ -1434,19 +2541,25 @@ static int mov_write_minf_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra mov_write_smhd_tag(pb); else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) { if (track->tag == MKTAG('t','e','x','t') || is_clcp_track(track)) { - mov_write_gmhd_tag(pb); + mov_write_gmhd_tag(pb, track); } else { mov_write_nmhd_tag(pb); } } else if (track->tag == MKTAG('r','t','p',' ')) { mov_write_hmhd_tag(pb); } else if (track->tag == MKTAG('t','m','c','d')) { - mov_write_gmhd_tag(pb); + if (track->mode != MODE_MOV) + mov_write_nmhd_tag(pb); + else + mov_write_gmhd_tag(pb, track); + } else if (track->tag == MKTAG('g','p','m','d')) { + mov_write_gmhd_tag(pb, track); } if (track->mode == MODE_MOV) /* FIXME: Why do it for MODE_MOV only ? */ mov_write_hdlr_tag(s, pb, NULL); mov_write_dinf_tag(pb); - mov_write_stbl_tag(s, pb, track); + if ((ret = mov_write_stbl_tag(s, pb, mov, track)) < 0) + return ret; return update_size(pb, pos); } @@ -1493,14 +2606,35 @@ static int mov_write_mdia_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); + int ret; + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "mdia"); mov_write_mdhd_tag(pb, mov, track); mov_write_hdlr_tag(s, pb, track); - mov_write_minf_tag(s, pb, track); + if ((ret = mov_write_minf_tag(s, pb, mov, track)) < 0) + return ret; return update_size(pb, pos); } +/* transformation matrix + |a b u| + |c d v| + |tx ty w| */ +static void write_matrix(AVIOContext *pb, int16_t a, int16_t b, int16_t c, + int16_t d, int16_t tx, int16_t ty) +{ + avio_wb32(pb, a << 16); /* 16.16 format */ + avio_wb32(pb, b << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* u in 2.30 format */ + avio_wb32(pb, c << 16); /* 16.16 format */ + avio_wb32(pb, d << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* v in 2.30 format */ + avio_wb32(pb, tx << 16); /* 16.16 format */ + avio_wb32(pb, ty << 16); /* 16.16 format */ + avio_wb32(pb, 1 << 30); /* w in 2.30 format */ +} + static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVStream *st) { @@ -1508,6 +2642,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, track->timescale, AV_ROUND_UP); int version = duration < INT32_MAX ? 0 : 1; int flags = MOV_TKHD_FLAG_IN_MOVIE; + int rotation = 0; int group = 0; uint32_t *display_matrix = NULL; @@ -1563,34 +2698,51 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb16(pb, 0); /* reserved */ /* Matrix structure */ +#if FF_API_OLD_ROTATE_API + if (st && st->metadata) { + AVDictionaryEntry *rot = av_dict_get(st->metadata, "rotate", NULL, 0); + rotation = (rot && rot->value) ? atoi(rot->value) : 0; + } +#endif if (display_matrix) { for (i = 0; i < 9; i++) avio_wb32(pb, display_matrix[i]); +#if FF_API_OLD_ROTATE_API + } else if (rotation == 90) { + write_matrix(pb, 0, 1, -1, 0, track->par->height, 0); + } else if (rotation == 180) { + write_matrix(pb, -1, 0, 0, -1, track->par->width, track->par->height); + } else if (rotation == 270) { + write_matrix(pb, 0, -1, 1, 0, 0, track->par->width); +#endif } else { - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ + write_matrix(pb, 1, 0, 0, 1, 0, 0); } - /* Track width and height, for visual only */ if (st && (track->par->codec_type == AVMEDIA_TYPE_VIDEO || track->par->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + int64_t track_width_1616; if (track->mode == MODE_MOV) { - avio_wb32(pb, track->par->width << 16); - avio_wb32(pb, track->height << 16); + track_width_1616 = track->par->width * 0x10000ULL; } else { - double sample_aspect_ratio = av_q2d(st->sample_aspect_ratio); - if (!sample_aspect_ratio || track->height != track->par->height) - sample_aspect_ratio = 1; - avio_wb32(pb, sample_aspect_ratio * track->par->width * 0x10000); - avio_wb32(pb, track->height * 0x10000); + track_width_1616 = av_rescale(st->sample_aspect_ratio.num, + track->par->width * 0x10000LL, + st->sample_aspect_ratio.den); + if (!track_width_1616 || + track->height != track->par->height || + track_width_1616 > UINT32_MAX) + track_width_1616 = track->par->width * 0x10000ULL; + } + if (track_width_1616 > UINT32_MAX) { + av_log(mov->fc, AV_LOG_WARNING, "track width is too large\n"); + track_width_1616 = 0; } + avio_wb32(pb, track_width_1616); + if (track->height > 0xFFFF) { + av_log(mov->fc, AV_LOG_WARNING, "track height is too large\n"); + avio_wb32(pb, 0); + } else + avio_wb32(pb, track->height * 0x10000U); } else { avio_wb32(pb, 0); avio_wb32(pb, 0); @@ -1638,7 +2790,21 @@ static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov, int version = duration < INT32_MAX ? 0 : 1; int entry_size, entry_count, size; int64_t delay, start_ct = track->start_cts; - delay = av_rescale_rnd(track->start_dts + start_ct, MOV_TIMESCALE, + int64_t start_dts = track->start_dts; + + if (track->entry) { + if (start_dts != track->cluster[0].dts || start_ct != track->cluster[0].cts) { + + av_log(mov->fc, AV_LOG_DEBUG, + "EDTS using dts:%"PRId64" cts:%d instead of dts:%"PRId64" cts:%"PRId64" tid:%d\n", + track->cluster[0].dts, track->cluster[0].cts, + start_dts, start_ct, track->track_id); + start_dts = track->cluster[0].dts; + start_ct = track->cluster[0].cts; + } + } + + delay = av_rescale_rnd(start_dts + start_ct, MOV_TIMESCALE, track->timescale, AV_ROUND_DOWN); version |= delay < INT32_MAX ? 0 : 1; @@ -1672,9 +2838,10 @@ static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov, } else { /* Avoid accidentally ending up with start_ct = -1 which has got a * special meaning. Normally start_ct should end up positive or zero - * here, but use FFMIN in case dts is a a small positive integer + * here, but use FFMIN in case dts is a small positive integer * rounded to 0 when represented in MOV_TIMESCALE units. */ - start_ct = -FFMIN(track->start_dts, 0); + av_assert0(av_rescale_rnd(start_dts, MOV_TIMESCALE, track->timescale, AV_ROUND_DOWN) <= 0); + start_ct = -FFMIN(start_dts, 0); /* Note, this delay is calculated from the pts of the first sample, * ensuring that we don't reduce the duration for cases with * dts<0 pts=0. */ @@ -1791,31 +2958,38 @@ static int mov_write_track_udta_tag(AVIOContext *pb, MOVMuxContext *mov, return 0; } -static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov, +static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVStream *st) { int64_t pos = avio_tell(pb); int entry_backup = track->entry; + int chunk_backup = track->chunkCount; + int ret; + /* If we want to have an empty moov, but some samples already have been * buffered (delay_moov), pretend that no samples have been written yet. */ if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV) - track->entry = 0; + track->chunkCount = track->entry = 0; avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "trak"); mov_write_tkhd_tag(pb, mov, track, st); - if (track->start_dts != AV_NOPTS_VALUE && - (track->mode == MODE_PSP || track->flags & MOV_TRACK_CTTS || - track->start_dts || is_clcp_track(track))) { + + av_assert2(mov->use_editlist >= 0); + + if (track->start_dts != AV_NOPTS_VALUE) { if (mov->use_editlist) - mov_write_edts_tag(pb, mov, track); // PSP Movies require edts box + mov_write_edts_tag(pb, mov, track); // PSP Movies and several other cases require edts box else if ((track->entry && track->cluster[0].dts) || track->mode == MODE_PSP || is_clcp_track(track)) av_log(mov->fc, AV_LOG_WARNING, "Not writing any edit list even though one would have been required\n"); } + if (track->tref_tag) mov_write_tref_tag(pb, track); - mov_write_mdia_tag(mov->fc, pb, mov, track); + + if ((ret = mov_write_mdia_tag(s, pb, mov, track)) < 0) + return ret; if (track->mode == MODE_PSP) mov_write_uuid_tag_psp(pb, track); // PSP Movies require this uuid box if (track->tag == MKTAG('r','t','p',' ')) @@ -1823,16 +2997,17 @@ static int mov_write_trak_tag(AVIOContext *pb, MOVMuxContext *mov, if (track->mode == MODE_MOV) { if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { 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); } } - if (is_clcp_track(track)) { + if (is_clcp_track(track) && st->sample_aspect_ratio.num) { mov_write_tapt_tag(pb, track); } } mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; + track->chunkCount = chunk_backup; return update_size(pb, pos); } @@ -1892,12 +3067,12 @@ static int mov_write_mvex_tag(AVIOContext *pb, MOVMuxContext *mov) static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) { int max_track_id = 1, i; - int64_t max_track_len_temp, max_track_len = 0; + int64_t max_track_len = 0; int version; for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry > 0 && mov->tracks[i].timescale) { - max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration, + int64_t max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration, MOV_TIMESCALE, mov->tracks[i].timescale, AV_ROUND_UP); @@ -1915,7 +3090,8 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) } version = max_track_len < UINT32_MAX ? 0 : 1; - (version == 1) ? avio_wb32(pb, 120) : avio_wb32(pb, 108); /* size */ + avio_wb32(pb, version == 1 ? 120 : 108); /* size */ + ffio_wfourcc(pb, "mvhd"); avio_w8(pb, version); avio_wb24(pb, 0); /* flags */ @@ -1936,15 +3112,7 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) avio_wb32(pb, 0); /* reserved */ /* Matrix structure */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ + write_matrix(pb, 1, 0, 0, 1, 0, 0); avio_wb32(pb, 0); /* reserved (preview time) */ avio_wb32(pb, 0); /* reserved (preview duration) */ @@ -2042,6 +3210,24 @@ static int mov_write_string_metadata(AVFormatContext *s, AVIOContext *pb, return mov_write_string_tag(pb, name, t->value, lang, long_style); } +/* iTunes bpm number */ +static int mov_write_tmpo_tag(AVIOContext *pb, AVFormatContext *s) +{ + AVDictionaryEntry *t = av_dict_get(s->metadata, "tmpo", NULL, 0); + int size = 0, tmpo = t ? atoi(t->value) : 0; + if (tmpo) { + size = 26; + avio_wb32(pb, size); + ffio_wfourcc(pb, "tmpo"); + avio_wb32(pb, size-8); /* size */ + ffio_wfourcc(pb, "data"); + avio_wb32(pb, 0x15); //type specifier + avio_wb32(pb, 0); + avio_wb16(pb, tmpo); // data + } + return size; +} + /* 3GPP TS 26.244 */ static int mov_write_loci_tag(AVFormatContext *s, AVIOContext *pb) { @@ -2052,7 +3238,7 @@ static int mov_write_loci_tag(AVFormatContext *s, AVIOContext *pb) AVDictionaryEntry *t = get_metadata_lang(s, "location", &lang); const char *ptr, *place = ""; char *end; - const char *astronomical_body = "earth"; + static const char *astronomical_body = "earth"; if (!t) return 0; @@ -2093,28 +3279,61 @@ static int mov_write_loci_tag(AVFormatContext *s, AVIOContext *pb) return update_size(pb, pos); } -/* iTunes track number */ +/* iTunes track or disc number */ static int mov_write_trkn_tag(AVIOContext *pb, MOVMuxContext *mov, - AVFormatContext *s) + AVFormatContext *s, int disc) { - AVDictionaryEntry *t = av_dict_get(s->metadata, "track", NULL, 0); + AVDictionaryEntry *t = av_dict_get(s->metadata, + disc ? "disc" : "track", + NULL, 0); int size = 0, track = t ? atoi(t->value) : 0; if (track) { + int tracks = 0; + char *slash = strchr(t->value, '/'); + if (slash) + tracks = atoi(slash + 1); avio_wb32(pb, 32); /* size */ - ffio_wfourcc(pb, "trkn"); + ffio_wfourcc(pb, disc ? "disk" : "trkn"); avio_wb32(pb, 24); /* size */ ffio_wfourcc(pb, "data"); avio_wb32(pb, 0); // 8 bytes empty avio_wb32(pb, 0); avio_wb16(pb, 0); // empty - avio_wb16(pb, track); // track number - avio_wb16(pb, 0); // total track number + avio_wb16(pb, track); // track / disc number + avio_wb16(pb, tracks); // total track / disc number avio_wb16(pb, 0); // empty size = 32; } return size; } +static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb, + const char *name, const char *tag, + int len) +{ + AVDictionaryEntry *t = NULL; + uint8_t num; + int size = 24 + len; + + if (len != 1 && len != 4) + return -1; + + if (!(t = av_dict_get(s->metadata, tag, NULL, 0))) + return 0; + num = atoi(t->value); + + avio_wb32(pb, size); + ffio_wfourcc(pb, name); + avio_wb32(pb, size - 8); + ffio_wfourcc(pb, "data"); + avio_wb32(pb, 0x15); + avio_wb32(pb, 0); + if (len==4) avio_wb32(pb, num); + else avio_w8 (pb, num); + + return size; +} + /* iTunes meta data list */ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) @@ -2142,11 +3361,84 @@ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_string_metadata(s, pb, "tvsh", "show" , 1); mov_write_string_metadata(s, pb, "tven", "episode_id",1); mov_write_string_metadata(s, pb, "tvnn", "network" , 1); - mov_write_trkn_tag(pb, mov, s); + mov_write_string_metadata(s, pb, "keyw", "keywords" , 1); + mov_write_int8_metadata (s, pb, "tves", "episode_sort",4); + mov_write_int8_metadata (s, pb, "tvsn", "season_number",4); + mov_write_int8_metadata (s, pb, "stik", "media_type",1); + mov_write_int8_metadata (s, pb, "hdvd", "hd_video", 1); + mov_write_int8_metadata (s, pb, "pgap", "gapless_playback",1); + mov_write_int8_metadata (s, pb, "cpil", "compilation", 1); + mov_write_trkn_tag(pb, mov, s, 0); // track number + mov_write_trkn_tag(pb, mov, s, 1); // disc number + mov_write_tmpo_tag(pb, s); return update_size(pb, pos); } -/* iTunes meta data tag */ +static int mov_write_mdta_hdlr_tag(AVIOContext *pb, MOVMuxContext *mov, + AVFormatContext *s) +{ + avio_wb32(pb, 33); /* size */ + ffio_wfourcc(pb, "hdlr"); + avio_wb32(pb, 0); + avio_wb32(pb, 0); + ffio_wfourcc(pb, "mdta"); + avio_wb32(pb, 0); + avio_wb32(pb, 0); + avio_wb32(pb, 0); + avio_w8(pb, 0); + return 33; +} + +static int mov_write_mdta_keys_tag(AVIOContext *pb, MOVMuxContext *mov, + AVFormatContext *s) +{ + AVDictionaryEntry *t = NULL; + int64_t pos = avio_tell(pb); + int64_t curpos, entry_pos; + int count = 0; + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "keys"); + avio_wb32(pb, 0); + entry_pos = avio_tell(pb); + avio_wb32(pb, 0); /* entry count */ + + while (t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX)) { + avio_wb32(pb, strlen(t->key) + 8); + ffio_wfourcc(pb, "mdta"); + avio_write(pb, t->key, strlen(t->key)); + count += 1; + } + curpos = avio_tell(pb); + avio_seek(pb, entry_pos, SEEK_SET); + avio_wb32(pb, count); // rewrite entry count + avio_seek(pb, curpos, SEEK_SET); + + return update_size(pb, pos); +} + +static int mov_write_mdta_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, + AVFormatContext *s) +{ + AVDictionaryEntry *t = NULL; + int64_t pos = avio_tell(pb); + int count = 1; /* keys are 1-index based */ + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ilst"); + + while (t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX)) { + int64_t entry_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + avio_wb32(pb, count); /* key */ + mov_write_string_data_tag(pb, t->value, 0, 1); + update_size(pb, entry_pos); + count += 1; + } + return update_size(pb, pos); +} + +/* meta data tags */ static int mov_write_meta_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) { @@ -2155,12 +3447,40 @@ static int mov_write_meta_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "meta"); avio_wb32(pb, 0); - mov_write_itunes_hdlr_tag(pb, mov, s); - mov_write_ilst_tag(pb, mov, s); + if (mov->flags & FF_MOV_FLAG_USE_MDTA) { + mov_write_mdta_hdlr_tag(pb, mov, s); + mov_write_mdta_keys_tag(pb, mov, s); + mov_write_mdta_ilst_tag(pb, mov, s); + } + else { + /* iTunes metadata tag */ + mov_write_itunes_hdlr_tag(pb, mov, s); + mov_write_ilst_tag(pb, mov, s); + } size = update_size(pb, pos); return size; } +static int mov_write_raw_metadata_tag(AVFormatContext *s, AVIOContext *pb, + const char *name, const char *key) +{ + int len; + AVDictionaryEntry *t; + + if (!(t = av_dict_get(s->metadata, key, NULL, 0))) + return 0; + + len = strlen(t->value); + if (len > 0) { + int size = len + 8; + avio_wb32(pb, size); + ffio_wfourcc(pb, name); + avio_write(pb, t->value, len); + return size; + } + return 0; +} + static int ascii_to_wc(AVIOContext *pb, const uint8_t *b) { int val; @@ -2248,20 +3568,24 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_3gp_udta_tag(pb_buf, s, "cprt", "copyright"); mov_write_3gp_udta_tag(pb_buf, s, "yrrc", "date"); mov_write_loci_tag(s, pb_buf); - } else if (mov->mode == MODE_MOV) { // the title field breaks gtkpod with mp4 and my suspicion is that stuff is not valid in mp4 + } else if (mov->mode == MODE_MOV && !(mov->flags & FF_MOV_FLAG_USE_MDTA)) { // 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); - if (!(s->flags & AVFMT_FLAG_BITEXACT)) - mov_write_string_metadata(s, pb_buf, "\251swr", "encoder", 0); + mov_write_string_metadata(s, pb_buf, "\251swr", "encoder", 0); + // currently ignored by mov.c mov_write_string_metadata(s, pb_buf, "\251des", "comment", 0); + // add support for libquicktime, this atom is also actually read by mov.c + mov_write_string_metadata(s, pb_buf, "\251cmt", "comment", 0); mov_write_string_metadata(s, pb_buf, "\251gen", "genre", 0); mov_write_string_metadata(s, pb_buf, "\251cpy", "copyright", 0); mov_write_string_metadata(s, pb_buf, "\251mak", "make", 0); mov_write_string_metadata(s, pb_buf, "\251mod", "model", 0); mov_write_string_metadata(s, pb_buf, "\251xyz", "location", 0); + mov_write_string_metadata(s, pb_buf, "\251key", "keywords", 0); + mov_write_raw_metadata_tag(s, pb_buf, "XMP_", "xmp"); } else { /* iTunes meta data */ mov_write_meta_tag(pb_buf, mov, s); @@ -2320,7 +3644,8 @@ static int mov_write_uuidusmt_tag(AVIOContext *pb, AVFormatContext *s) avio_wb16(pb, 0x0); /* ? */ avio_wb16(pb, 0x021C); /* data */ - mov_write_psp_udta_tag(pb, LIBAVCODEC_IDENT, "eng", 0x04); + if (!(s->flags & AVFMT_FLAG_BITEXACT)) + mov_write_psp_udta_tag(pb, LIBAVCODEC_IDENT, "eng", 0x04); mov_write_psp_udta_tag(pb, title->value, "eng", 0x01); mov_write_psp_udta_tag(pb, "2006/04/01 11:11:11", "und", 0x03); @@ -2331,6 +3656,75 @@ 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++; + } + } +} + +/** + * Assign track ids. If option "use_stream_ids_as_track_ids" is set, + * the stream ids are used as track ids. + * + * This assumes mov->tracks and s->streams are in the same order and + * there are no gaps in either of them (so mov->tracks[n] refers to + * s->streams[n]). + * + * As an exception, there can be more entries in + * s->streams than in mov->tracks, in which case new track ids are + * generated (starting after the largest found stream id). + */ +static int mov_setup_track_ids(MOVMuxContext *mov, AVFormatContext *s) +{ + int i; + + if (mov->track_ids_ok) + return 0; + + if (mov->use_stream_ids_as_track_ids) { + int next_generated_track_id = 0; + for (i = 0; i < s->nb_streams; i++) { + if (s->streams[i]->id > next_generated_track_id) + next_generated_track_id = s->streams[i]->id; + } + + for (i = 0; i < mov->nb_streams; i++) { + if (mov->tracks[i].entry <= 0 && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) + continue; + + mov->tracks[i].track_id = i >= s->nb_streams ? ++next_generated_track_id : s->streams[i]->id; + } + } else { + for (i = 0; i < mov->nb_streams; i++) { + if (mov->tracks[i].entry <= 0 && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) + continue; + + mov->tracks[i].track_id = i + 1; + } + } + + mov->track_ids_ok = 1; + + return 0; +} + static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) { @@ -2339,12 +3733,16 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, 0); /* size placeholder*/ ffio_wfourcc(pb, "moov"); + mov_setup_track_ids(mov, s); + for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry <= 0 && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) continue; 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) @@ -2370,13 +3768,26 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, } } } + for (i = 0; i < mov->nb_streams; i++) { + if (mov->tracks[i].tag == MKTAG('t','m','c','d')) { + int src_trk = mov->tracks[i].src_track; + mov->tracks[src_trk].tref_tag = mov->tracks[i].tag; + mov->tracks[src_trk].tref_id = mov->tracks[i].track_id; + //src_trk may have a different timescale than the tmcd track + mov->tracks[i].track_duration = av_rescale(mov->tracks[src_trk].track_duration, + mov->tracks[i].timescale, + mov->tracks[src_trk].timescale); + } + } mov_write_mvhd_tag(pb, mov); if (mov->mode != MODE_MOV && !mov->iods_skip) mov_write_iods_tag(pb, mov); for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT) { - mov_write_trak_tag(pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); + int ret = mov_write_trak_tag(s, pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); + if (ret < 0) + return ret; } } if (mov->flags & FF_MOV_FLAG_FRAGMENT) @@ -2409,10 +3820,13 @@ static void param_write_hex(AVIOContext *pb, const char *name, const uint8_t *va avio_printf(pb, "<param name=\"%s\" value=\"%s\" valuetype=\"data\"/>\n", name, buf); } -static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) +static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) { int64_t pos = avio_tell(pb); int i; + int64_t manifest_bit_rate = 0; + AVCPBProperties *props = NULL; + static const uint8_t uuid[] = { 0xa5, 0xd4, 0x0b, 0x30, 0xe8, 0x14, 0x11, 0xdd, 0xba, 0x2f, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 @@ -2426,17 +3840,23 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) avio_printf(pb, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); avio_printf(pb, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n"); avio_printf(pb, "<head>\n"); - avio_printf(pb, "<meta name=\"creator\" content=\"%s\" />\n", + if (!(mov->fc->flags & AVFMT_FLAG_BITEXACT)) + avio_printf(pb, "<meta name=\"creator\" content=\"%s\" />\n", LIBAVFORMAT_IDENT); avio_printf(pb, "</head>\n"); avio_printf(pb, "<body>\n"); avio_printf(pb, "<switch>\n"); + + mov_setup_track_ids(mov, s); + for (i = 0; i < mov->nb_streams; i++) { MOVTrack *track = &mov->tracks[i]; const char *type; - /* track->track_id is initialized in write_moov, and thus isn't known - * here yet */ - int track_id = i + 1; + int track_id = track->track_id; + char track_name_buf[32] = { 0 }; + + AVStream *st = track->st; + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,0); if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { type = "video"; @@ -2445,10 +3865,37 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) } else { continue; } - avio_printf(pb, "<%s systemBitrate=\"%d\">\n", type, - track->par->bit_rate); - param_write_int(pb, "systemBitrate", track->par->bit_rate); + + props = (AVCPBProperties*)av_stream_get_side_data(track->st, AV_PKT_DATA_CPB_PROPERTIES, NULL); + + if (track->par->bit_rate) { + manifest_bit_rate = track->par->bit_rate; + } else if (props) { + manifest_bit_rate = props->max_bitrate; + } + + avio_printf(pb, "<%s systemBitrate=\"%"PRId64"\">\n", type, + manifest_bit_rate); + param_write_int(pb, "systemBitrate", manifest_bit_rate); param_write_int(pb, "trackID", track_id); + param_write_string(pb, "systemLanguage", lang ? lang->value : "und"); + + /* Build track name piece by piece: */ + /* 1. track type */ + av_strlcat(track_name_buf, type, sizeof(track_name_buf)); + /* 2. track language, if available */ + if (lang) + av_strlcatf(track_name_buf, sizeof(track_name_buf), + "_%s", lang->value); + /* 3. special type suffix */ + /* "_cc" = closed captions, "_ad" = audio_description */ + if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) + av_strlcat(track_name_buf, "_cc", sizeof(track_name_buf)); + else if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) + av_strlcat(track_name_buf, "_ad", sizeof(track_name_buf)); + + param_write_string(pb, "trackName", track_name_buf); + if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { if (track->par->codec_id == AV_CODEC_ID_H264) { uint8_t *ptr; @@ -2472,7 +3919,17 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) param_write_int(pb, "DisplayHeight", track->par->height); } else { if (track->par->codec_id == AV_CODEC_ID_AAC) { - param_write_string(pb, "FourCC", "AACL"); + switch (track->par->profile) + { + case FF_PROFILE_AAC_HE_V2: + param_write_string(pb, "FourCC", "AACP"); + break; + case FF_PROFILE_AAC_HE: + param_write_string(pb, "FourCC", "AACH"); + break; + default: + param_write_string(pb, "FourCC", "AACL"); + } } else if (track->par->codec_id == AV_CODEC_ID_WMAPRO) { param_write_string(pb, "FourCC", "WMAP"); } @@ -2594,7 +4051,10 @@ static int mov_write_trun_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, 0); /* size placeholder */ ffio_wfourcc(pb, "trun"); - avio_w8(pb, 0); /* version */ + if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + avio_w8(pb, 1); /* version */ + else + avio_w8(pb, 0); /* version */ avio_wb24(pb, flags); avio_wb32(pb, end - first); /* sample count */ @@ -2673,8 +4133,7 @@ static int mov_write_tfrf_tag(AVIOContext *pb, MOVMuxContext *mov, int free_size = 16 * (mov->ism_lookahead - n); avio_wb32(pb, free_size); ffio_wfourcc(pb, "free"); - for (i = 0; i < free_size - 8; i++) - avio_w8(pb, 0); + ffio_fill(pb, 0, free_size - 8); } return 0; @@ -3040,6 +4499,8 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) ffio_wfourcc(pb, "MSNV"); else if (mov->mode == MODE_MP4 && mov->flags & FF_MOV_FLAG_DEFAULT_BASE_MOOF) ffio_wfourcc(pb, "iso5"); // Required when using default-base-is-moof + else if (mov->mode == MODE_MP4 && mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + ffio_wfourcc(pb, "iso4"); else if (mov->mode == MODE_MP4) ffio_wfourcc(pb, "isom"); else if (mov->mode == MODE_IPOD) @@ -3084,17 +4545,23 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) return update_size(pb, pos); } -static void mov_write_uuidprof_tag(AVIOContext *pb, AVFormatContext *s) +static int mov_write_uuidprof_tag(AVIOContext *pb, AVFormatContext *s) { AVStream *video_st = s->streams[0]; AVCodecParameters *video_par = s->streams[0]->codecpar; AVCodecParameters *audio_par = s->streams[1]->codecpar; int audio_rate = audio_par->sample_rate; - // TODO: should be avg_frame_rate - int frame_rate = ((video_st->time_base.den) * (0x10000)) / (video_st->time_base.num); + int64_t frame_rate = video_st->avg_frame_rate.den ? + (video_st->avg_frame_rate.num * 0x10000LL) / video_st->avg_frame_rate.den : + 0; int audio_kbitrate = audio_par->bit_rate / 1000; int video_kbitrate = FFMIN(video_par->bit_rate / 1000, 800 - audio_kbitrate); + if (frame_rate < 0 || frame_rate > INT32_MAX) { + av_log(s, AV_LOG_ERROR, "Frame rate %f outside supported range\n", frame_rate / (double)0x10000); + return AVERROR(EINVAL); + } + avio_wb32(pb, 0x94); /* size */ ffio_wfourcc(pb, "uuid"); ffio_wfourcc(pb, "PROF"); @@ -3145,18 +4612,33 @@ static void mov_write_uuidprof_tag(AVIOContext *pb, AVFormatContext *s) avio_wb16(pb, video_par->width); avio_wb16(pb, video_par->height); avio_wb32(pb, 0x010001); /* ? */ + + return 0; } static int mov_write_identification(AVIOContext *pb, AVFormatContext *s) { MOVMuxContext *mov = s->priv_data; + int i; + mov_write_ftyp_tag(pb,s); if (mov->mode == MODE_PSP) { - if (s->nb_streams != 2) { + int video_streams_nb = 0, audio_streams_nb = 0, other_streams_nb = 0; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + video_streams_nb++; + else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + audio_streams_nb++; + else + other_streams_nb++; + } + + if (video_streams_nb != 1 || audio_streams_nb != 1 || other_streams_nb) { av_log(s, AV_LOG_ERROR, "PSP mode need one video and one audio stream\n"); return AVERROR(EINVAL); } - mov_write_uuidprof_tag(pb, s); + return mov_write_uuidprof_tag(pb, s); } return 0; } @@ -3273,6 +4755,7 @@ static int mov_flush_fragment(AVFormatContext *s, int force) MOVMuxContext *mov = s->priv_data; int i, first_track = -1; int64_t mdat_size = 0; + int ret; int has_video = 0, starts_with_key = 0, first_video_track = 1; if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) @@ -3288,6 +4771,8 @@ static int mov_flush_fragment(AVFormatContext *s, int force) if (!track->end_reliable) { AVPacket pkt; if (!ff_interleaved_peek(s, i, &pkt, 1)) { + if (track->dts_shift != AV_NOPTS_VALUE) + pkt.dts += track->dts_shift; track->track_duration = pkt.dts - track->start_dts; if (pkt.pts != AV_NOPTS_VALUE) track->end_pts = pkt.pts; @@ -3326,10 +4811,8 @@ static int mov_flush_fragment(AVFormatContext *s, int force) if (!mov->moov_written) { int64_t pos = avio_tell(s->pb); - int ret; - AVIOContext *moov_buf; uint8_t *buf; - int buf_size; + int buf_size, moov_size; for (i = 0; i < mov->nb_streams; i++) if (!mov->tracks[i].entry) @@ -3338,17 +4821,15 @@ static int mov_flush_fragment(AVFormatContext *s, int force) if (i < mov->nb_streams && !force) return 0; - if ((ret = ffio_open_null_buf(&moov_buf)) < 0) - return ret; - mov_write_moov_tag(moov_buf, mov, s); - buf_size = ffio_close_null_buf(moov_buf); + moov_size = get_moov_size(s); for (i = 0; i < mov->nb_streams; i++) - mov->tracks[i].data_offset = pos + buf_size + 8; + mov->tracks[i].data_offset = pos + moov_size + 8; avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER); if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) mov_write_identification(s->pb, s); - mov_write_moov_tag(s->pb, mov, s); + if ((ret = mov_write_moov_tag(s->pb, mov, s)) < 0) + return ret; if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) { if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) @@ -3493,6 +4974,44 @@ static int mov_auto_flush_fragment(AVFormatContext *s, int force) return ret; } +static int check_pkt(AVFormatContext *s, AVPacket *pkt) +{ + MOVMuxContext *mov = s->priv_data; + MOVTrack *trk = &mov->tracks[pkt->stream_index]; + int64_t ref; + uint64_t duration; + + if (trk->entry) { + ref = trk->cluster[trk->entry - 1].dts; + } else if ( trk->start_dts != AV_NOPTS_VALUE + && !trk->frag_discont) { + ref = trk->start_dts + trk->track_duration; + } else + ref = pkt->dts; // Skip tests for the first packet + + if (trk->dts_shift != AV_NOPTS_VALUE) { + /* With negative CTS offsets we have set an offset to the DTS, + * reverse this for the check. */ + ref -= trk->dts_shift; + } + + duration = pkt->dts - ref; + if (pkt->dts < ref || duration >= INT_MAX) { + av_log(s, AV_LOG_ERROR, "Application provided duration: %"PRId64" / timestamp: %"PRId64" is out of range for mov/mp4 format\n", + duration, pkt->dts + ); + + pkt->dts = ref + 1; + pkt->pts = AV_NOPTS_VALUE; + } + + if (pkt->duration < 0 || pkt->duration > INT_MAX) { + av_log(s, AV_LOG_ERROR, "Application provided duration: %"PRId64" is invalid\n", pkt->duration); + return AVERROR(EINVAL); + } + return 0; +} + int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) { MOVMuxContext *mov = s->priv_data; @@ -3503,6 +5022,10 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) int size = pkt->size, ret = 0; uint8_t *reformatted_data = NULL; + ret = check_pkt(s, pkt); + if (ret < 0) + return ret; + if (mov->flags & FF_MOV_FLAG_FRAGMENT) { int ret; if (mov->moov_written || mov->flags & FF_MOV_FLAG_EMPTY_MOOV) { @@ -3541,13 +5064,18 @@ int ff_mov_write_packet(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 (par->codec_id == AV_CODEC_ID_ADPCM_MS || + par->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { + samples_in_chunk = trk->par->frame_size; } else if (trk->sample_size) samples_in_chunk = size / trk->sample_size; else samples_in_chunk = 1; /* copy extradata if it exists */ - if (trk->vos_len == 0 && par->extradata_size > 0) { + if (trk->vos_len == 0 && par->extradata_size > 0 && + !TAG_IS_AVCI(trk->tag) && + (par->codec_id != AV_CODEC_ID_DNXHD)) { trk->vos_len = par->extradata_size; trk->vos_data = av_malloc(trk->vos_len); if (!trk->vos_data) { @@ -3557,7 +5085,17 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) memcpy(trk->vos_data, par->extradata, trk->vos_len); } - if (par->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1) { + if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && + (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { + if (!s->streams[pkt->stream_index]->nb_frames) { + av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: " + "use the audio bitstream filter 'aac_adtstoasc' to fix it " + "('-bsf:a aac_adtstoasc' option with ffmpeg)\n"); + return -1; + } + av_log(s, AV_LOG_WARNING, "aac bitstream error\n"); + } + if (par->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1 && !TAG_IS_AVCI(trk->tag)) { /* from x264 or from bytestream H.264 */ /* NAL reformatting needed */ if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { @@ -3565,7 +5103,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) &size); avio_write(pb, reformatted_data, size); } else { - size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size); + if (trk->cenc.aes_ctr) { + size = ff_mov_cenc_avc_parse_nal_units(&trk->cenc, pb, pkt->data, size); + if (size < 0) { + ret = size; + goto err; + } + } else { + size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size); + } } } else if (par->codec_id == AV_CODEC_ID_HEVC && trk->vos_len > 6 && (AV_RB24(trk->vos_data) == 1 || AV_RB32(trk->vos_data) == 1)) { @@ -3576,8 +5122,30 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } else { size = ff_hevc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL); } - } else { +#if CONFIG_AC3_PARSER + } else if (par->codec_id == AV_CODEC_ID_EAC3) { + size = handle_eac3(mov, pkt, trk); + if (size < 0) + return size; + else if (!size) + goto end; avio_write(pb, pkt->data, size); +#endif + } else { + if (trk->cenc.aes_ctr) { + if (par->codec_id == AV_CODEC_ID_H264 && par->extradata_size > 4) { + int nal_size_length = (par->extradata[4] & 0x3) + 1; + ret = ff_mov_cenc_avc_write_nal_units(s, &trk->cenc, nal_size_length, pb, pkt->data, size); + } else { + ret = ff_mov_cenc_write_packet(&trk->cenc, pb, pkt->data, size); + } + + if (ret) { + goto err; + } + } else { + avio_write(pb, pkt->data, size); + } } if ((par->codec_id == AV_CODEC_ID_DNXHD || @@ -3604,6 +5172,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) 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; @@ -3631,6 +5200,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) trk->frag_discont = 0; } } + if (!trk->entry && trk->start_dts == AV_NOPTS_VALUE && !mov->use_editlist && s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) { /* Not using edit lists and shifting the first track to start from zero. @@ -3664,6 +5234,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) pkt->stream_index, pkt->dts); } trk->track_duration = pkt->dts - trk->start_dts + pkt->duration; + trk->last_sample_is_subtitle_end = 0; if (pkt->pts == AV_NOPTS_VALUE) { av_log(s, AV_LOG_WARNING, "pts has no value\n"); @@ -3705,23 +5276,25 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) ff_mov_add_hinted_packet(s, pkt, trk->hint_track, trk->entry, reformatted_data, size); +end: err: + av_free(reformatted_data); return ret; } -static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) +static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt) { - if (!pkt) { - mov_flush_fragment(s, 1); - return 1; - } else { MOVMuxContext *mov = s->priv_data; MOVTrack *trk = &mov->tracks[pkt->stream_index]; AVCodecParameters *par = trk->par; int64_t frag_duration = 0; int size = pkt->size; + int ret = check_pkt(s, pkt); + if (ret < 0) + return ret; + if (mov->flags & FF_MOV_FLAG_FRAG_DISCONT) { int i; for (i = 0; i < s->nb_streams; i++) @@ -3729,6 +5302,30 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) mov->flags &= ~FF_MOV_FLAG_FRAG_DISCONT; } + if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) { + if (trk->dts_shift == AV_NOPTS_VALUE) + trk->dts_shift = pkt->pts - pkt->dts; + pkt->dts += trk->dts_shift; + } + + if (trk->par->codec_id == AV_CODEC_ID_MP4ALS || + trk->par->codec_id == AV_CODEC_ID_AAC || + trk->par->codec_id == AV_CODEC_ID_FLAC) { + int side_size = 0; + uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); + if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { + void *newextra = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!newextra) + return AVERROR(ENOMEM); + av_free(par->extradata); + par->extradata = newextra; + memcpy(par->extradata, side, side_size); + par->extradata_size = side_size; + if (!pkt->size) // Flush packet + mov->need_rewrite_extradata = 1; + } + } + if (!pkt->size) { if (trk->start_dts == AV_NOPTS_VALUE && trk->frag_discont) { trk->start_dts = pkt->dts; @@ -3741,7 +5338,7 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) return 0; /* Discard 0 sized packets */ } - if (trk->entry) + if (trk->entry && pkt->stream_index < s->nb_streams) frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts, s->streams[pkt->stream_index]->time_base, AV_TIME_BASE_Q); @@ -3767,6 +5364,107 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) } return ff_mov_write_packet(s, pkt); +} + +static int mov_write_subtitle_end_packet(AVFormatContext *s, + int stream_index, + int64_t dts) { + AVPacket end; + uint8_t data[2] = {0}; + int ret; + + av_init_packet(&end); + end.size = sizeof(data); + end.data = data; + end.pts = dts; + end.dts = dts; + end.duration = 0; + end.stream_index = stream_index; + + ret = mov_write_single_packet(s, &end); + av_packet_unref(&end); + + return ret; +} + +static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + if (!pkt) { + mov_flush_fragment(s, 1); + return 1; + } else { + int i; + MOVMuxContext *mov = s->priv_data; + MOVTrack *trk = &mov->tracks[pkt->stream_index]; + + if (!pkt->size) + return mov_write_single_packet(s, pkt); /* Passthrough. */ + + /* + * Subtitles require special handling. + * + * 1) For full complaince, every track must have a sample at + * dts == 0, which is rarely true for subtitles. So, as soon + * as we see any packet with dts > 0, write an empty subtitle + * at dts == 0 for any subtitle track with no samples in it. + * + * 2) For each subtitle track, check if the current packet's + * dts is past the duration of the last subtitle sample. If + * so, we now need to write an end sample for that subtitle. + * + * This must be done conditionally to allow for subtitles that + * immediately replace each other, in which case an end sample + * is not needed, and is, in fact, actively harmful. + * + * 3) See mov_write_trailer for how the final end sample is + * handled. + */ + for (i = 0; i < mov->nb_streams; i++) { + MOVTrack *trk = &mov->tracks[i]; + int ret; + + if (trk->par->codec_id == AV_CODEC_ID_MOV_TEXT && + trk->track_duration < pkt->dts && + (trk->entry == 0 || !trk->last_sample_is_subtitle_end)) { + ret = mov_write_subtitle_end_packet(s, i, trk->track_duration); + if (ret < 0) return ret; + trk->last_sample_is_subtitle_end = 1; + } + } + + if (trk->mode == MODE_MOV && trk->par->codec_type == AVMEDIA_TYPE_VIDEO) { + AVPacket *opkt = pkt; + int reshuffle_ret, ret; + if (trk->is_unaligned_qt_rgb) { + int64_t bpc = trk->par->bits_per_coded_sample != 15 ? trk->par->bits_per_coded_sample : 16; + int expected_stride = ((trk->par->width * bpc + 15) >> 4)*2; + reshuffle_ret = ff_reshuffle_raw_rgb(s, &pkt, trk->par, expected_stride); + if (reshuffle_ret < 0) + return reshuffle_ret; + } else + reshuffle_ret = 0; + if (trk->par->format == AV_PIX_FMT_PAL8 && !trk->pal_done) { + ret = ff_get_packet_palette(s, opkt, reshuffle_ret, trk->palette); + if (ret < 0) + goto fail; + if (ret) + trk->pal_done++; + } else if (trk->par->codec_id == AV_CODEC_ID_RAWVIDEO && + (trk->par->format == AV_PIX_FMT_GRAY8 || + trk->par->format == AV_PIX_FMT_MONOBLACK)) { + for (i = 0; i < pkt->size; i++) + pkt->data[i] = ~pkt->data[i]; + } + if (reshuffle_ret) { + ret = mov_write_single_packet(s, pkt); +fail: + if (reshuffle_ret) + av_packet_free(&pkt); + return ret; + } + } + + return mov_write_single_packet(s, pkt); } } @@ -3774,12 +5472,12 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) // as samples, and a tref pointing from the other tracks to the chapter one. static int mov_create_chapter_track(AVFormatContext *s, int tracknum) { + AVIOContext *pb; + MOVMuxContext *mov = s->priv_data; MOVTrack *track = &mov->tracks[tracknum]; AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; int i, len; - // These properties are required to make QT recognize the chapter track - uint8_t chapter_properties[43] = { 0, 0, 0, 0, 0, 0, 0, 1, }; track->mode = mov->mode; track->tag = MKTAG('t','e','x','t'); @@ -3788,11 +5486,57 @@ static int mov_create_chapter_track(AVFormatContext *s, int tracknum) if (!track->par) return AVERROR(ENOMEM); track->par->codec_type = AVMEDIA_TYPE_SUBTITLE; - track->par->extradata = av_malloc(sizeof(chapter_properties)); - if (!track->par->extradata) +#if 0 + // These properties are required to make QT recognize the chapter track + uint8_t chapter_properties[43] = { 0, 0, 0, 0, 0, 0, 0, 1, }; + if (ff_alloc_extradata(track->par, sizeof(chapter_properties))) return AVERROR(ENOMEM); - track->par->extradata_size = sizeof(chapter_properties); memcpy(track->par->extradata, chapter_properties, sizeof(chapter_properties)); +#else + if (avio_open_dyn_buf(&pb) >= 0) { + int size; + uint8_t *buf; + + /* Stub header (usually for Quicktime chapter track) */ + // TextSampleEntry + avio_wb32(pb, 0x01); // displayFlags + avio_w8(pb, 0x00); // horizontal justification + avio_w8(pb, 0x00); // vertical justification + avio_w8(pb, 0x00); // bgColourRed + avio_w8(pb, 0x00); // bgColourGreen + avio_w8(pb, 0x00); // bgColourBlue + avio_w8(pb, 0x00); // bgColourAlpha + // BoxRecord + avio_wb16(pb, 0x00); // defTextBoxTop + avio_wb16(pb, 0x00); // defTextBoxLeft + avio_wb16(pb, 0x00); // defTextBoxBottom + avio_wb16(pb, 0x00); // defTextBoxRight + // StyleRecord + avio_wb16(pb, 0x00); // startChar + avio_wb16(pb, 0x00); // endChar + avio_wb16(pb, 0x01); // fontID + avio_w8(pb, 0x00); // fontStyleFlags + avio_w8(pb, 0x00); // fontSize + avio_w8(pb, 0x00); // fgColourRed + avio_w8(pb, 0x00); // fgColourGreen + avio_w8(pb, 0x00); // fgColourBlue + avio_w8(pb, 0x00); // fgColourAlpha + // FontTableBox + avio_wb32(pb, 0x0D); // box size + ffio_wfourcc(pb, "ftab"); // box atom name + avio_wb16(pb, 0x01); // entry count + // FontRecord + avio_wb16(pb, 0x01); // font ID + avio_w8(pb, 0x00); // font name length + + if ((size = avio_close_dyn_buf(pb, &buf)) > 0) { + track->par->extradata = buf; + track->par->extradata_size = size; + } else { + av_freep(&buf); + } + } +#endif for (i = 0; i < s->nb_chapters; i++) { AVChapter *c = s->chapters[i]; @@ -3823,6 +5567,54 @@ static int mov_create_chapter_track(AVFormatContext *s, int tracknum) return 0; } + +static int mov_check_timecode_track(AVFormatContext *s, AVTimecode *tc, int src_index, const char *tcstr) +{ + int ret; + + /* compute the frame number */ + ret = av_timecode_init_from_string(tc, find_fps(s, s->streams[src_index]), tcstr, s); + return ret; +} + +static int mov_create_timecode_track(AVFormatContext *s, int index, int src_index, AVTimecode tc) +{ + int ret; + MOVMuxContext *mov = s->priv_data; + MOVTrack *track = &mov->tracks[index]; + AVStream *src_st = s->streams[src_index]; + AVPacket pkt = {.stream_index = index, .flags = AV_PKT_FLAG_KEY, .size = 4}; + AVRational rate = find_fps(s, src_st); + + /* tmcd track based on video stream */ + track->mode = mov->mode; + track->tag = MKTAG('t','m','c','d'); + track->src_track = src_index; + track->timescale = mov->tracks[src_index].timescale; + if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) + track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME; + + /* set st to src_st for metadata access*/ + track->st = src_st; + + /* encode context: tmcd data stream */ + track->par = avcodec_parameters_alloc(); + if (!track->par) + return AVERROR(ENOMEM); + track->par->codec_type = AVMEDIA_TYPE_DATA; + track->par->codec_tag = track->tag; + track->st->avg_frame_rate = av_inv_q(rate); + + /* the tmcd track just contains one packet with the frame number */ + pkt.data = av_malloc(pkt.size); + if (!pkt.data) + return AVERROR(ENOMEM); + AV_WB32(pkt.data, tc.start); + ret = ff_mov_write_packet(s, &pkt); + av_free(pkt.data); + return ret; +} + /* * st->disposition controls the "enabled" flag in the tkhd tag. * QuickTime will not play a track if it is not enabled. So make sure @@ -3880,17 +5672,24 @@ static void mov_free(AVFormatContext *s) MOVMuxContext *mov = s->priv_data; int i; - if (mov->chapter_track) - avcodec_parameters_free(&mov->tracks[mov->chapter_track].par); + if (mov->chapter_track) { + if (mov->tracks[mov->chapter_track].par) + av_freep(&mov->tracks[mov->chapter_track].par->extradata); + av_freep(&mov->tracks[mov->chapter_track].par); + } for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].tag == MKTAG('r','t','p',' ')) ff_mov_close_hinting(&mov->tracks[i]); + else if (mov->tracks[i].tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd) + av_freep(&mov->tracks[i].par); av_freep(&mov->tracks[i].cluster); av_freep(&mov->tracks[i].frag_info); if (mov->tracks[i].vos_len) - av_free(mov->tracks[i].vos_data); + av_freep(&mov->tracks[i].vos_data); + + ff_mov_cenc_free(&mov->tracks[i].cenc); } av_freep(&mov->tracks); @@ -3905,9 +5704,9 @@ static uint32_t rgb_to_yuv(uint32_t rgb) g = (rgb >> 8) & 0xFF; b = (rgb ) & 0xFF; - y = av_clip_uint8( 16. + 0.257 * r + 0.504 * g + 0.098 * b); - cb = av_clip_uint8(128. - 0.148 * r - 0.291 * g + 0.439 * b); - cr = av_clip_uint8(128. + 0.439 * r - 0.368 * g - 0.071 * b); + y = av_clip_uint8(( 16000 + 257 * r + 504 * g + 98 * b)/1000); + cb = av_clip_uint8((128000 - 148 * r - 291 * g + 439 * b)/1000); + cr = av_clip_uint8((128000 + 439 * r - 368 * g - 71 * b)/1000); return (y << 16) | (cr << 8) | cb; } @@ -3961,12 +5760,11 @@ static int mov_create_dvd_sub_decoder_specific_info(MOVTrack *track, return 0; } -static int mov_write_header(AVFormatContext *s) +static int mov_init(AVFormatContext *s) { - AVIOContext *pb = s->pb; MOVMuxContext *mov = s->priv_data; - AVDictionaryEntry *t; - int i, ret, hint_track = 0; + AVDictionaryEntry *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + int i, ret; mov->fc = s; @@ -4002,6 +5800,15 @@ static int mov_write_header(AVFormatContext *s) mov->flags |= FF_MOV_FLAG_FRAGMENT | FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_DEFAULT_BASE_MOOF; + if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && s->flags & AVFMT_FLAG_AUTO_BSF) { + av_log(s, AV_LOG_VERBOSE, "Empty MOOV enabled; disabling automatic bitstream filtering\n"); + s->flags &= ~AVFMT_FLAG_AUTO_BSF; + } + + if (mov->flags & FF_MOV_FLAG_FASTSTART) { + mov->reserved_moov_size = -1; + } + if (mov->use_editlist < 0) { mov->use_editlist = 1; if (mov->flags & FF_MOV_FLAG_FRAGMENT && @@ -4044,19 +5851,12 @@ static int mov_write_header(AVFormatContext *s) return AVERROR(EINVAL); } - - if (!(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { - if ((ret = mov_write_identification(pb, s)) < 0) - return ret; - } - mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; if (mov->flags & FF_MOV_FLAG_RTP_HINT) { /* Add hint tracks for each audio and video stream */ - hint_track = mov->nb_streams; for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || @@ -4066,12 +5866,64 @@ static int mov_write_header(AVFormatContext *s) } } + if ( mov->write_tmcd == -1 && (mov->mode == MODE_MOV || mov->mode == MODE_MP4) + || mov->write_tmcd == 1) { + /* +1 tmcd track for each video stream with a timecode */ + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + AVDictionaryEntry *t = global_tcr; + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + (t || (t=av_dict_get(st->metadata, "timecode", NULL, 0)))) { + AVTimecode tc; + ret = mov_check_timecode_track(s, &tc, i, t->value); + if (ret >= 0) + mov->nb_meta_tmcd++; + } + } + + /* check if there is already a tmcd track to remux */ + if (mov->nb_meta_tmcd) { + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codecpar->codec_tag == MKTAG('t','m','c','d')) { + av_log(s, AV_LOG_WARNING, "You requested a copy of the original timecode track " + "so timecode metadata are now ignored\n"); + mov->nb_meta_tmcd = 0; + } + } + } + + mov->nb_streams += mov->nb_meta_tmcd; + } + // Reserve an extra stream for chapters for the case where chapters // are written in the trailer - mov->tracks = av_mallocz((mov->nb_streams + 1) * sizeof(*mov->tracks)); + mov->tracks = av_mallocz_array((mov->nb_streams + 1), sizeof(*mov->tracks)); if (!mov->tracks) return AVERROR(ENOMEM); + if (mov->encryption_scheme_str != NULL && strcmp(mov->encryption_scheme_str, "none") != 0) { + if (strcmp(mov->encryption_scheme_str, "cenc-aes-ctr") == 0) { + mov->encryption_scheme = MOV_ENC_CENC_AES_CTR; + + if (mov->encryption_key_len != AES_CTR_KEY_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid encryption key len %d expected %d\n", + mov->encryption_key_len, AES_CTR_KEY_SIZE); + return AVERROR(EINVAL); + } + + if (mov->encryption_kid_len != CENC_KID_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid encryption kid len %d expected %d\n", + mov->encryption_kid_len, CENC_KID_SIZE); + return AVERROR(EINVAL); + } + } else { + av_log(s, AV_LOG_ERROR, "unsupported encryption scheme %s\n", + mov->encryption_scheme_str); + return AVERROR(EINVAL); + } + } + for (i = 0; i < s->nb_streams; i++) { AVStream *st= s->streams[i]; MOVTrack *track= &mov->tracks[i]; @@ -4085,9 +5937,10 @@ static int mov_write_header(AVFormatContext *s) track->mode = mov->mode; track->tag = mov_find_codec_tag(s, track); if (!track->tag) { - av_log(s, AV_LOG_ERROR, "track %d: could not find tag, " - "codec not currently supported in container\n", i); - goto error; + av_log(s, AV_LOG_ERROR, "Could not find tag for codec %s in stream #%d, " + "codec not currently supported in container\n", + avcodec_get_name(st->codecpar->codec_id), i); + return AVERROR(EINVAL); } /* If hinting of this track is enabled by a later hint track, * this is updated. */ @@ -4095,47 +5948,106 @@ static int mov_write_header(AVFormatContext *s) track->start_dts = AV_NOPTS_VALUE; track->start_cts = AV_NOPTS_VALUE; track->end_pts = AV_NOPTS_VALUE; + track->dts_shift = AV_NOPTS_VALUE; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (track->tag == MKTAG('m','x','3','p') || track->tag == MKTAG('m','x','3','n') || track->tag == MKTAG('m','x','4','p') || track->tag == MKTAG('m','x','4','n') || track->tag == MKTAG('m','x','5','p') || track->tag == MKTAG('m','x','5','n')) { if (st->codecpar->width != 720 || (st->codecpar->height != 608 && st->codecpar->height != 512)) { av_log(s, AV_LOG_ERROR, "D-10/IMX must use 720x608 or 720x512 video resolution\n"); - goto error; + return AVERROR(EINVAL); } track->height = track->tag >> 24 == 'n' ? 486 : 576; } - track->timescale = st->time_base.den; + if (mov->video_track_timescale) { + track->timescale = mov->video_track_timescale; + } else { + track->timescale = st->time_base.den; + while(track->timescale < 10000) + track->timescale *= 2; + } + if (st->codecpar->width > 65535 || st->codecpar->height > 65535) { + av_log(s, AV_LOG_ERROR, "Resolution %dx%d too large for mov/mp4\n", st->codecpar->width, st->codecpar->height); + return AVERROR(EINVAL); + } if (track->mode == MODE_MOV && track->timescale > 100000) av_log(s, 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"); + if (track->mode == MODE_MOV && + track->par->codec_id == AV_CODEC_ID_RAWVIDEO && + track->tag == MKTAG('r','a','w',' ')) { + enum AVPixelFormat pix_fmt = track->par->format; + if (pix_fmt == AV_PIX_FMT_NONE && track->par->bits_per_coded_sample == 1) + pix_fmt = AV_PIX_FMT_MONOWHITE; + track->is_unaligned_qt_rgb = + pix_fmt == AV_PIX_FMT_RGB24 || + pix_fmt == AV_PIX_FMT_BGR24 || + pix_fmt == AV_PIX_FMT_PAL8 || + pix_fmt == AV_PIX_FMT_GRAY8 || + pix_fmt == AV_PIX_FMT_MONOWHITE || + pix_fmt == AV_PIX_FMT_MONOBLACK; + } + if (track->par->codec_id == AV_CODEC_ID_VP9) { + if (track->mode != MODE_MP4) { + av_log(s, AV_LOG_ERROR, "VP9 only supported in MP4.\n"); + return AVERROR(EINVAL); + } + } } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { track->timescale = st->codecpar->sample_rate; - /* set sample_size for PCM and ADPCM */ - if (av_get_bits_per_sample(st->codecpar->codec_id) || - st->codecpar->codec_id == AV_CODEC_ID_ILBC) { + if (!st->codecpar->frame_size && !av_get_bits_per_sample(st->codecpar->codec_id)) { + av_log(s, AV_LOG_WARNING, "track %d: codec frame size is not set\n", i); + track->audio_vbr = 1; + }else if (st->codecpar->codec_id == AV_CODEC_ID_ADPCM_MS || + st->codecpar->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || + st->codecpar->codec_id == AV_CODEC_ID_ILBC){ if (!st->codecpar->block_align) { - av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set\n", i); - goto error; + av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set for adpcm\n", i); + return AVERROR(EINVAL); } track->sample_size = st->codecpar->block_align; + }else if (st->codecpar->frame_size > 1){ /* assume compressed audio */ + track->audio_vbr = 1; + }else{ + track->sample_size = (av_get_bits_per_sample(st->codecpar->codec_id) >> 3) * st->codecpar->channels; } - /* set audio_vbr for compressed audio */ - if (av_get_bits_per_sample(st->codecpar->codec_id) < 8) { + if (st->codecpar->codec_id == AV_CODEC_ID_ILBC || + st->codecpar->codec_id == AV_CODEC_ID_ADPCM_IMA_QT) { track->audio_vbr = 1; } if (track->mode != MODE_MOV && track->par->codec_id == AV_CODEC_ID_MP3 && track->timescale < 16000) { - av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not supported\n", - i, track->par->sample_rate); - goto error; + if (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL) { + av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not standard, to mux anyway set strict to -1\n", + i, track->par->sample_rate); + return AVERROR(EINVAL); + } else { + av_log(s, AV_LOG_WARNING, "track %d: muxing mp3 at %dhz is not standard in MP4\n", + i, track->par->sample_rate); + } + } + if (track->par->codec_id == AV_CODEC_ID_FLAC || + track->par->codec_id == AV_CODEC_ID_OPUS) { + if (track->mode != MODE_MP4) { + av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id)); + return AVERROR(EINVAL); + } + if (s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { + av_log(s, AV_LOG_ERROR, + "%s in MP4 support is experimental, add " + "'-strict %d' if you want to use it.\n", + avcodec_get_name(track->par->codec_id), FF_COMPLIANCE_EXPERIMENTAL); + return AVERROR_EXPERIMENTAL; + } } } else if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { track->timescale = st->time_base.den; } else if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA) { track->timescale = st->time_base.den; + } else { + track->timescale = MOV_TIMESCALE; } if (!track->height) track->height = st->codecpar->height; @@ -4146,21 +6058,92 @@ static int mov_write_header(AVFormatContext *s) avpriv_set_pts_info(st, 64, 1, track->timescale); + if (mov->encryption_scheme == MOV_ENC_CENC_AES_CTR) { + ret = ff_mov_cenc_init(&track->cenc, mov->encryption_key, + track->par->codec_id == AV_CODEC_ID_H264, s->flags & AVFMT_FLAG_BITEXACT); + if (ret) + return ret; + } + } + + enable_tracks(s); + return 0; +} + +static int mov_write_header(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + MOVMuxContext *mov = s->priv_data; + AVDictionaryEntry *t, *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + int i, ret, hint_track = 0, tmcd_track = 0, nb_tracks = s->nb_streams; + + if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) + nb_tracks++; + + if (mov->flags & FF_MOV_FLAG_RTP_HINT) { + /* Add hint tracks for each audio and video stream */ + hint_track = nb_tracks; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + nb_tracks++; + } + } + } + + if (mov->mode == MODE_MOV || mov->mode == MODE_MP4) + tmcd_track = nb_tracks; + + for (i = 0; i < s->nb_streams; i++) { + int j; + AVStream *st= s->streams[i]; + MOVTrack *track= &mov->tracks[i]; + /* copy extradata if it exists */ if (st->codecpar->extradata_size) { if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) mov_create_dvd_sub_decoder_specific_info(track, st); - else { + else if (!TAG_IS_AVCI(track->tag) && st->codecpar->codec_id != AV_CODEC_ID_DNXHD) { track->vos_len = st->codecpar->extradata_size; track->vos_data = av_malloc(track->vos_len); - if (!track->vos_data) - goto error; + if (!track->vos_data) { + return AVERROR(ENOMEM); + } memcpy(track->vos_data, st->codecpar->extradata, track->vos_len); } } + + if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || + track->par->channel_layout != AV_CH_LAYOUT_MONO) + continue; + + for (j = 0; j < s->nb_streams; j++) { + AVStream *stj= s->streams[j]; + MOVTrack *trackj= &mov->tracks[j]; + if (j == i) + continue; + + if (stj->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || + trackj->par->channel_layout != AV_CH_LAYOUT_MONO || + trackj->language != track->language || + trackj->tag != track->tag + ) + continue; + track->multichannel_as_mono++; + } } - enable_tracks(s); + if (!(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { + if ((ret = mov_write_identification(pb, s)) < 0) + return ret; + } + + if (mov->reserved_moov_size){ + mov->reserved_header_pos = avio_tell(pb); + if (mov->reserved_moov_size > 0) + avio_skip(pb, mov->reserved_moov_size); + } if (mov->flags & FF_MOV_FLAG_FRAGMENT) { /* If no fragmentation options have been set, set a default. */ @@ -4174,14 +6157,13 @@ static int mov_write_header(AVFormatContext *s) mov_write_mdat_tag(pb, mov); } - if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) - mov->time = ff_iso8601_to_unix_time(t->value); + ff_parse_creation_time_metadata(s, &mov->time, 1); if (mov->time) mov->time += 0x7C25B080; // 1970 based -> 1904 based if (mov->chapter_track) - if (mov_create_chapter_track(s, mov->chapter_track) < 0) - goto error; + if ((ret = mov_create_chapter_track(s, mov->chapter_track)) < 0) + return ret; if (mov->flags & FF_MOV_FLAG_RTP_HINT) { /* Initialize the hint tracks for each audio and video stream */ @@ -4189,20 +6171,43 @@ static int mov_write_header(AVFormatContext *s) AVStream *st = s->streams[i]; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - ff_mov_init_hinting(s, hint_track, i); + if ((ret = ff_mov_init_hinting(s, hint_track, i)) < 0) + return ret; hint_track++; } } } + if (mov->nb_meta_tmcd) { + /* Initialize the tmcd tracks */ + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + t = global_tcr; + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + AVTimecode tc; + if (!t) + t = av_dict_get(st->metadata, "timecode", NULL, 0); + if (!t) + continue; + if (mov_check_timecode_track(s, &tc, i, t->value) < 0) + continue; + if ((ret = mov_create_timecode_track(s, tmcd_track, i, tc)) < 0) + return ret; + tmcd_track++; + } + } + } + avio_flush(pb); if (mov->flags & FF_MOV_FLAG_ISML) - mov_write_isml_manifest(pb, mov); + mov_write_isml_manifest(pb, mov, s); if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && !(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { - mov_write_moov_tag(pb, mov, s); + if ((ret = mov_write_moov_tag(pb, mov, s)) < 0) + return ret; avio_flush(pb); mov->moov_written = 1; if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) @@ -4210,9 +6215,6 @@ static int mov_write_header(AVFormatContext *s) } return 0; - error: - mov_free(s); - return -1; } static int get_moov_size(AVFormatContext *s) @@ -4223,7 +6225,8 @@ static int get_moov_size(AVFormatContext *s) if ((ret = ffio_open_null_buf(&moov_buf)) < 0) return ret; - mov_write_moov_tag(moov_buf, mov, s); + if ((ret = mov_write_moov_tag(moov_buf, mov, s)) < 0) + return ret; return ffio_close_null_buf(moov_buf); } @@ -4359,6 +6362,33 @@ static int mov_write_trailer(AVFormatContext *s) int i; int64_t moov_pos; + if (mov->need_rewrite_extradata) { + for (i = 0; i < s->nb_streams; i++) { + MOVTrack *track = &mov->tracks[i]; + AVCodecParameters *par = track->par; + + track->vos_len = par->extradata_size; + track->vos_data = av_malloc(track->vos_len); + if (!track->vos_data) + return AVERROR(ENOMEM); + memcpy(track->vos_data, par->extradata, track->vos_len); + } + mov->need_rewrite_extradata = 0; + } + + /* + * Before actually writing the trailer, make sure that there are no + * dangling subtitles, that need a terminating sample. + */ + for (i = 0; i < mov->nb_streams; i++) { + MOVTrack *trk = &mov->tracks[i]; + if (trk->par->codec_id == AV_CODEC_ID_MOV_TEXT && + !trk->last_sample_is_subtitle_end) { + mov_write_subtitle_end_packet(s, i, trk->track_duration); + trk->last_sample_is_subtitle_end = 1; + } + } + // If there were no chapters when the header was written, but there // are chapters now, write them in the trailer. This only works // when we are not doing fragments. @@ -4366,7 +6396,7 @@ static int mov_write_trailer(AVFormatContext *s) if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) { mov->chapter_track = mov->nb_streams++; if ((res = mov_create_chapter_track(s, mov->chapter_track)) < 0) - goto error; + return res; } } @@ -4386,45 +6416,141 @@ 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 > 0 ? mov->reserved_header_pos : moov_pos, SEEK_SET); if (mov->flags & FF_MOV_FLAG_FASTSTART) { av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n"); res = shift_data(s); - if (res == 0) { - avio_seek(pb, mov->reserved_header_pos, SEEK_SET); - mov_write_moov_tag(pb, mov, s); + if (res < 0) + return res; + avio_seek(pb, mov->reserved_header_pos, SEEK_SET); + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + return res; + } else if (mov->reserved_moov_size > 0) { + int64_t size; + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + return res; + size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos); + if (size < 8){ + av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size); + return AVERROR(EINVAL); } + avio_wb32(pb, size); + ffio_wfourcc(pb, "free"); + ffio_fill(pb, 0, size - 8); + avio_seek(pb, moov_pos, SEEK_SET); } else { - mov_write_moov_tag(pb, mov, s); + if ((res = mov_write_moov_tag(pb, mov, s)) < 0) + return res; } + res = 0; } else { mov_auto_flush_fragment(s, 1); for (i = 0; i < mov->nb_streams; i++) mov->tracks[i].data_offset = 0; if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) { + int64_t end; av_log(s, AV_LOG_INFO, "Starting second pass: inserting sidx atoms\n"); res = shift_data(s); - if (res == 0) { - int64_t end = avio_tell(pb); - avio_seek(pb, mov->reserved_header_pos, SEEK_SET); - mov_write_sidx_tags(pb, mov, -1, 0); - avio_seek(pb, end, SEEK_SET); - avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); - mov_write_mfra_tag(pb, mov); - } + if (res < 0) + return res; + end = avio_tell(pb); + avio_seek(pb, mov->reserved_header_pos, SEEK_SET); + mov_write_sidx_tags(pb, mov, -1, 0); + avio_seek(pb, end, SEEK_SET); + avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); + mov_write_mfra_tag(pb, mov); } else if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) { avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); mov_write_mfra_tag(pb, mov); } } -error: - mov_free(s); - return res; } +static int mov_check_bitstream(struct AVFormatContext *s, const AVPacket *pkt) +{ + int ret = 1; + AVStream *st = s->streams[pkt->stream_index]; + + if (st->codecpar->codec_id == AV_CODEC_ID_AAC) { + if (pkt->size > 2 && (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) + ret = ff_stream_add_bitstream_filter(st, "aac_adtstoasc", NULL); + } else if (st->codecpar->codec_id == AV_CODEC_ID_VP9) { + ret = ff_stream_add_bitstream_filter(st, "vp9_superframe", NULL); + } + + return ret; +} + +static const AVCodecTag codec_3gp_tags[] = { + { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, + { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, + { AV_CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, + { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, + { AV_CODEC_ID_AMR_NB, MKTAG('s','a','m','r') }, + { AV_CODEC_ID_AMR_WB, MKTAG('s','a','w','b') }, + { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, + { AV_CODEC_ID_NONE, 0 }, +}; + +const AVCodecTag codec_mp4_tags[] = { + { AV_CODEC_ID_MPEG4 , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_H264 , MKTAG('a', 'v', 'c', '1') }, + { AV_CODEC_ID_HEVC , MKTAG('h', 'e', 'v', '1') }, + { AV_CODEC_ID_HEVC , MKTAG('h', 'v', 'c', '1') }, + { AV_CODEC_ID_MPEG2VIDEO , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_MPEG1VIDEO , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_MJPEG , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_PNG , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_JPEG2000 , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_VC1 , MKTAG('v', 'c', '-', '1') }, + { AV_CODEC_ID_DIRAC , MKTAG('d', 'r', 'a', 'c') }, + { AV_CODEC_ID_TSCC2 , MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_VP9 , MKTAG('v', 'p', '0', '9') }, + { AV_CODEC_ID_AAC , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP4ALS , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP3 , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP2 , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_AC3 , MKTAG('a', 'c', '-', '3') }, + { AV_CODEC_ID_EAC3 , MKTAG('e', 'c', '-', '3') }, + { AV_CODEC_ID_DTS , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_FLAC , MKTAG('f', 'L', 'a', 'C') }, + { AV_CODEC_ID_OPUS , MKTAG('O', 'p', 'u', 's') }, + { AV_CODEC_ID_VORBIS , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_QCELP , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_EVRC , MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') }, + { AV_CODEC_ID_MOV_TEXT , MKTAG('t', 'x', '3', 'g') }, + { AV_CODEC_ID_NONE , 0 }, +}; + +const AVCodecTag codec_ism_tags[] = { + { AV_CODEC_ID_WMAPRO , MKTAG('w', 'm', 'a', ' ') }, + { AV_CODEC_ID_NONE , 0 }, +}; + +static const AVCodecTag codec_ipod_tags[] = { + { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, + { AV_CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, + { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, + { AV_CODEC_ID_ALAC, MKTAG('a','l','a','c') }, + { AV_CODEC_ID_AC3, MKTAG('a','c','-','3') }, + { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, + { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') }, + { AV_CODEC_ID_NONE, 0 }, +}; + +static const AVCodecTag codec_f4v_tags[] = { + { AV_CODEC_ID_MP3, MKTAG('.','m','p','3') }, + { AV_CODEC_ID_AAC, MKTAG('m','p','4','a') }, + { AV_CODEC_ID_H264, MKTAG('a','v','c','1') }, + { AV_CODEC_ID_VP6A, MKTAG('V','P','6','A') }, + { AV_CODEC_ID_VP6F, MKTAG('V','P','6','F') }, + { AV_CODEC_ID_NONE, 0 }, +}; + #if CONFIG_MOV_MUXER MOV_CLASS(mov) AVOutputFormat ff_mov_muxer = { @@ -4435,13 +6561,16 @@ AVOutputFormat ff_mov_muxer = { .audio_codec = AV_CODEC_ID_AAC, .video_codec = CONFIG_LIBX264_ENCODER ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, .codec_tag = (const AVCodecTag* const []){ ff_codec_movvideo_tags, ff_codec_movaudio_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &mov_muxer_class, }; #endif @@ -4454,11 +6583,14 @@ AVOutputFormat ff_tgp_muxer = { .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AMR_NB, .video_codec = AV_CODEC_ID_H263, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, .codec_tag = (const AVCodecTag* const []){ codec_3gp_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &tgp_muxer_class, }; #endif @@ -4467,17 +6599,20 @@ MOV_CLASS(mp4) AVOutputFormat ff_mp4_muxer = { .name = "mp4", .long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "mp4", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = CONFIG_LIBX264_ENCODER ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, - .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, + .codec_tag = (const AVCodecTag* const []){ codec_mp4_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &mp4_muxer_class, }; #endif @@ -4491,11 +6626,14 @@ AVOutputFormat ff_psp_muxer = { .audio_codec = AV_CODEC_ID_AAC, .video_codec = CONFIG_LIBX264_ENCODER ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, - .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, + .codec_tag = (const AVCodecTag* const []){ codec_mp4_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &psp_muxer_class, }; #endif @@ -4508,11 +6646,14 @@ AVOutputFormat ff_tg2_muxer = { .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AMR_NB, .video_codec = AV_CODEC_ID_H263, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, .codec_tag = (const AVCodecTag* const []){ codec_3gp_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &tg2_muxer_class, }; #endif @@ -4521,16 +6662,19 @@ MOV_CLASS(ipod) AVOutputFormat ff_ipod_muxer = { .name = "ipod", .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 (MPEG-4 Part 14)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "m4v,m4a", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_H264, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, .codec_tag = (const AVCodecTag* const []){ codec_ipod_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &ipod_muxer_class, }; #endif @@ -4539,16 +6683,20 @@ MOV_CLASS(ismv) AVOutputFormat ff_ismv_muxer = { .name = "ismv", .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming)"), - .mime_type = "application/mp4", + .mime_type = "video/mp4", .extensions = "ismv,isma", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_H264, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, + .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, - .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, + .codec_tag = (const AVCodecTag* const []){ + codec_mp4_tags, codec_ism_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &ismv_muxer_class, }; #endif @@ -4562,11 +6710,14 @@ AVOutputFormat ff_f4v_muxer = { .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_H264, + .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, + .deinit = mov_free, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, .codec_tag = (const AVCodecTag* const []){ codec_f4v_tags, 0 }, + .check_bitstream = mov_check_bitstream, .priv_class = &f4v_muxer_class, }; #endif |