| /* |
| * MOV, 3GP, MP4 muxer |
| * Copyright (c) 2003 Thomas Raivio |
| * 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 FFmpeg. |
| * |
| * FFmpeg is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * 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 FFmpeg; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdint.h> |
| #include <inttypes.h> |
| |
| #include "movenc.h" |
| #include "avformat.h" |
| #include "avio_internal.h" |
| #include "riff.h" |
| #include "avio.h" |
| #include "isom.h" |
| #include "avc.h" |
| #include "libavcodec/ac3_parser.h" |
| #include "libavcodec/get_bits.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/timecode.h" |
| #include "libavutil/color_utils.h" |
| #include "hevc.h" |
| #include "rtpenc.h" |
| #include "mov_chan.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" }, |
| { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "faststart", "Run a second pass to put the index (moov atom) at the beginning of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FASTSTART}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "omit_tfhd_offset", "Omit the base data offset in tfhd atoms", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_OMIT_TFHD_OFFSET}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "disable_chpl", "Disable Nero chapter atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DISABLE_CHPL}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "default_base_moof", "Set the default-base-is-moof flag in tfhd atoms", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DEFAULT_BASE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "dash", "Write DASH compatible fragmented MP4", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DASH}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "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" }, |
| FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), |
| { "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_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, .unit = "movflags" }, |
| { "encryption_key", "The media encryption key (hex)", offsetof(MOVMuxContext, encryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM, .unit = "movflags" }, |
| { "encryption_kid", "The media encryption key identifier (hex)", offsetof(MOVMuxContext, encryption_kid), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_ENCODING_PARAM, .unit = "movflags" }, |
| { NULL }, |
| }; |
| |
| #define MOV_CLASS(flavor)\ |
| static const AVClass flavor ## _muxer_class = {\ |
| .class_name = #flavor " muxer",\ |
| .item_name = av_default_item_name,\ |
| .option = options,\ |
| .version = LIBAVUTIL_VERSION_INT,\ |
| }; |
| |
| static int get_moov_size(AVFormatContext *s); |
| |
| static int utf8len(const uint8_t *b) |
| { |
| int len = 0; |
| int val; |
| while (*b) { |
| GET_UTF8(val, *b++, return -1;) |
| len++; |
| } |
| return len; |
| } |
| |
| //FIXME support 64 bit variant with wide placeholders |
| static int64_t update_size(AVIOContext *pb, int64_t pos) |
| { |
| int64_t curpos = avio_tell(pb); |
| avio_seek(pb, pos, SEEK_SET); |
| avio_wb32(pb, curpos - pos); /* rewrite size */ |
| avio_seek(pb, curpos, SEEK_SET); |
| |
| return curpos - pos; |
| } |
| |
| static int co64_required(const MOVTrack *track) |
| { |
| if (track->entry > 0 && track->cluster[track->entry - 1].pos + track->data_offset > UINT32_MAX) |
| return 1; |
| return 0; |
| } |
| |
| /* Chunk offset atom */ |
| static int mov_write_stco_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int i; |
| 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) |
| ffio_wfourcc(pb, "co64"); |
| else |
| ffio_wfourcc(pb, "stco"); |
| avio_wb32(pb, 0); /* version & flags */ |
| 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 |
| avio_wb32(pb, track->cluster[i].pos + track->data_offset); |
| } |
| return update_size(pb, pos); |
| } |
| |
| /* Sample size atom */ |
| static int mov_write_stsz_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int equalChunks = 1; |
| int i, j, entries = 0, tst = -1, oldtst = -1; |
| |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "stsz"); |
| avio_wb32(pb, 0); /* version & flags */ |
| |
| for (i = 0; i < track->entry; i++) { |
| tst = track->cluster[i].size / track->cluster[i].entries; |
| if (oldtst != -1 && tst != oldtst) |
| equalChunks = 0; |
| oldtst = tst; |
| entries += track->cluster[i].entries; |
| } |
| if (equalChunks && track->entry) { |
| int sSize = track->entry ? track->cluster[0].size / track->cluster[0].entries : 0; |
| sSize = FFMAX(1, sSize); // adpcm mono case could make sSize == 0 |
| avio_wb32(pb, sSize); // sample size |
| avio_wb32(pb, entries); // sample count |
| } else { |
| avio_wb32(pb, 0); // sample size |
| avio_wb32(pb, entries); // sample count |
| for (i = 0; i < track->entry; i++) { |
| for (j = 0; j < track->cluster[i].entries; j++) { |
| avio_wb32(pb, track->cluster[i].size / |
| track->cluster[i].entries); |
| } |
| } |
| } |
| return update_size(pb, pos); |
| } |
| |
| /* Sample to chunk atom */ |
| static int mov_write_stsc_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int index = 0, oldval = -1, i; |
| int64_t entryPos, curpos; |
| |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "stsc"); |
| avio_wb32(pb, 0); // version & flags |
| entryPos = avio_tell(pb); |
| avio_wb32(pb, track->chunkCount); // entry count |
| for (i = 0; i < track->entry; i++) { |
| 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; |
| index++; |
| } |
| } |
| curpos = avio_tell(pb); |
| avio_seek(pb, entryPos, SEEK_SET); |
| avio_wb32(pb, index); // rewrite size |
| avio_seek(pb, curpos, SEEK_SET); |
| |
| return update_size(pb, pos); |
| } |
| |
| /* Sync sample atom */ |
| static int mov_write_stss_tag(AVIOContext *pb, MOVTrack *track, uint32_t flag) |
| { |
| int64_t curpos, entryPos; |
| int i, index = 0; |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); // size |
| ffio_wfourcc(pb, flag == MOV_SYNC_SAMPLE ? "stss" : "stps"); |
| avio_wb32(pb, 0); // version & flags |
| entryPos = avio_tell(pb); |
| avio_wb32(pb, track->entry); // entry count |
| for (i = 0; i < track->entry; i++) { |
| if (track->cluster[i].flags & flag) { |
| avio_wb32(pb, i + 1); |
| index++; |
| } |
| } |
| curpos = avio_tell(pb); |
| avio_seek(pb, entryPos, SEEK_SET); |
| avio_wb32(pb, index); // rewrite size |
| avio_seek(pb, curpos, SEEK_SET); |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_amr_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| avio_wb32(pb, 0x11); /* size */ |
| if (track->mode == MODE_MOV) ffio_wfourcc(pb, "samr"); |
| else ffio_wfourcc(pb, "damr"); |
| ffio_wfourcc(pb, "FFMP"); |
| avio_w8(pb, 0); /* decoder version */ |
| |
| avio_wb16(pb, 0x81FF); /* Mode set (all modes for AMR_NB) */ |
| avio_w8(pb, 0x00); /* Mode change period (no restriction) */ |
| avio_w8(pb, 0x01); /* Frames per sample */ |
| return 0x11; |
| } |
| |
| static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| GetBitContext gbc; |
| PutBitContext pbc; |
| uint8_t buf[3]; |
| int fscod, bsid, bsmod, acmod, lfeon, frmsizecod; |
| |
| if (track->vos_len < 7) |
| return -1; |
| |
| avio_wb32(pb, 11); |
| ffio_wfourcc(pb, "dac3"); |
| |
| 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) { |
| skip_bits(&gbc, 2); // dsurmod |
| } else { |
| if ((acmod & 1) && acmod != 1) |
| skip_bits(&gbc, 2); // cmixlev |
| if (acmod & 4) |
| skip_bits(&gbc, 2); // surmixlev |
| } |
| lfeon = get_bits1(&gbc); |
| |
| init_put_bits(&pbc, buf, sizeof(buf)); |
| put_bits(&pbc, 2, fscod); |
| put_bits(&pbc, 5, bsid); |
| put_bits(&pbc, 3, bsmod); |
| put_bits(&pbc, 3, acmod); |
| put_bits(&pbc, 1, lfeon); |
| put_bits(&pbc, 5, frmsizecod >> 1); // bit_rate_code |
| put_bits(&pbc, 5, 0); // reserved |
| |
| flush_put_bits(&pbc); |
| avio_write(pb, buf, sizeof(buf)); |
| |
| 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->enc, "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; |
| if ((ret = av_copy_packet(&info->pkt, pkt)) < 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); |
| if ((ret = av_copy_packet(pkt, &info->pkt)) < 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). |
| */ |
| static int mov_write_extradata_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| avio_write(pb, track->enc->extradata, track->enc->extradata_size); |
| return track->enc->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; |
| avio_w8(pb, tag); |
| for (; i > 0; i--) |
| avio_w8(pb, (size >> (7 * i)) | 0x80); |
| 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"); |
| avio_wb32(pb, 0); // Version |
| |
| // ES descriptor |
| put_descr(pb, 0x03, 3 + 5+13 + decoder_specific_info_len + 5+1); |
| avio_wb16(pb, track->track_id); |
| avio_w8(pb, 0x00); // flags (= no flags) |
| |
| // DecoderConfig descriptor |
| put_descr(pb, 0x04, 13 + decoder_specific_info_len); |
| |
| // Object type indication |
| if ((track->enc->codec_id == AV_CODEC_ID_MP2 || |
| track->enc->codec_id == AV_CODEC_ID_MP3) && |
| track->enc->sample_rate > 24000) |
| avio_w8(pb, 0x6B); // 11172-3 |
| else |
| avio_w8(pb, ff_codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)); |
| |
| // the following fields is made of 6 bits to identify the streamtype (4 for video, 5 for audio) |
| // plus 1 bit to indicate upstream and 1 bit set to 1 (reserved) |
| if (track->enc->codec_id == AV_CODEC_ID_DVD_SUBTITLE) |
| avio_w8(pb, (0x38 << 2) | 1); // flags (= NeroSubpicStream) |
| else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) |
| avio_w8(pb, 0x15); // flags (= Audiostream) |
| else |
| avio_w8(pb, 0x11); // flags (= Visualstream) |
| |
| props = (AVCPBProperties*)av_stream_get_side_data(track->st, AV_PKT_DATA_CPB_PROPERTIES, |
| NULL); |
| |
| avio_wb24(pb, props ? props->buffer_size / 8 : 0); // Buffersize DB |
| |
| avg_bitrate = compute_avg_bitrate(track); |
| avio_wb32(pb, props ? FFMAX3(props->max_bitrate, props->avg_bitrate, avg_bitrate) : FFMAX(track->enc->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 |
| put_descr(pb, 0x05, track->vos_len); |
| avio_write(pb, track->vos_data, track->vos_len); |
| } |
| |
| // SL descriptor |
| put_descr(pb, 0x06, 1); |
| avio_w8(pb, 0x02); |
| 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(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->enc->codec_tag = av_bswap16(track->tag >> 16); |
| if ((ret = ff_put_wav_header(pb, track->enc, 0)) < 0) |
| return ret; |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_wfex_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int ret; |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); |
| ffio_wfourcc(pb, "wfex"); |
| if ((ret = ff_put_wav_header(pb, track->enc, FF_PUT_WAV_HEADER_FORCE_WAVEFORMATEX)) < 0) |
| return ret; |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_chan_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| uint32_t layout_tag, bitmap; |
| int64_t pos = avio_tell(pb); |
| |
| layout_tag = ff_mov_get_channel_layout_tag(track->enc->codec_id, |
| track->enc->channel_layout, |
| &bitmap); |
| if (!layout_tag) { |
| av_log(track->enc, AV_LOG_WARNING, "not writing 'chan' tag due to " |
| "lack of channel information\n"); |
| return 0; |
| } |
| |
| if (track->multichannel_as_mono) |
| return 0; |
| |
| avio_wb32(pb, 0); // Size |
| ffio_wfourcc(pb, "chan"); // Type |
| avio_w8(pb, 0); // Version |
| avio_wb24(pb, 0); // Flags |
| avio_wb32(pb, layout_tag); // mChannelLayoutTag |
| avio_wb32(pb, bitmap); // mChannelBitmap |
| avio_wb32(pb, 0); // mNumberChannelDescriptions |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_wave_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "wave"); |
| |
| if (track->enc->codec_id != AV_CODEC_ID_QDM2) { |
| avio_wb32(pb, 12); /* size */ |
| ffio_wfourcc(pb, "frma"); |
| avio_wl32(pb, track->tag); |
| } |
| |
| if (track->enc->codec_id == AV_CODEC_ID_AAC) { |
| /* useless atom needed by mplayer, ipod, not needed by quicktime */ |
| avio_wb32(pb, 12); /* size */ |
| ffio_wfourcc(pb, "mp4a"); |
| avio_wb32(pb, 0); |
| mov_write_esds_tag(pb, track); |
| } else if (mov_pcm_le_gt16(track->enc->codec_id)) { |
| mov_write_enda_tag(pb); |
| } else if (mov_pcm_be_gt16(track->enc->codec_id)) { |
| mov_write_enda_tag_be(pb); |
| } else if (track->enc->codec_id == AV_CODEC_ID_AMR_NB) { |
| mov_write_amr_tag(pb, track); |
| } else if (track->enc->codec_id == AV_CODEC_ID_AC3) { |
| mov_write_ac3_tag(pb, track); |
| } else if (track->enc->codec_id == AV_CODEC_ID_EAC3) { |
| mov_write_eac3_tag(pb, track); |
| } else if (track->enc->codec_id == AV_CODEC_ID_ALAC || |
| track->enc->codec_id == AV_CODEC_ID_QDM2) { |
| mov_write_extradata_tag(pb, track); |
| } else if (track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || |
| track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) { |
| mov_write_ms_tag(pb, track); |
| } |
| |
| avio_wb32(pb, 8); /* size */ |
| avio_wb32(pb, 0); /* null tag */ |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_dvc1_structs(MOVTrack *track, uint8_t *buf) |
| { |
| uint8_t *unescaped; |
| const uint8_t *start, *next, *end = track->vos_data + track->vos_len; |
| int unescaped_size, seq_found = 0; |
| int level = 0, interlace = 0; |
| int packet_seq = track->vc1_info.packet_seq; |
| int packet_entry = track->vc1_info.packet_entry; |
| int slices = track->vc1_info.slices; |
| PutBitContext pbc; |
| |
| if (track->start_dts == AV_NOPTS_VALUE) { |
| /* No packets written yet, vc1_info isn't authoritative yet. */ |
| /* Assume inline sequence and entry headers. */ |
| packet_seq = packet_entry = 1; |
| av_log(NULL, AV_LOG_WARNING, |
| "moov atom written before any packets, unable to write correct " |
| "dvc1 atom. Set the delay_moov flag to fix this.\n"); |
| } |
| |
| unescaped = av_mallocz(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE); |
| if (!unescaped) |
| return AVERROR(ENOMEM); |
| start = find_next_marker(track->vos_data, end); |
| for (next = start; next < end; start = next) { |
| 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); |
| init_get_bits(&gb, unescaped, 8 * unescaped_size); |
| if (AV_RB32(start) == VC1_CODE_SEQHDR) { |
| int profile = get_bits(&gb, 2); |
| if (profile != PROFILE_ADVANCED) { |
| av_free(unescaped); |
| return AVERROR(ENOSYS); |
| } |
| seq_found = 1; |
| level = get_bits(&gb, 3); |
| /* chromaformat, frmrtq_postproc, bitrtq_postproc, postprocflag, |
| * width, height */ |
| 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) { |
| av_free(unescaped); |
| return AVERROR(ENOSYS); |
| } |
| |
| init_put_bits(&pbc, buf, 7); |
| /* VC1DecSpecStruc */ |
| put_bits(&pbc, 4, 12); /* profile - advanced */ |
| put_bits(&pbc, 3, level); |
| put_bits(&pbc, 1, 0); /* reserved */ |
| /* VC1AdvDecSpecStruc */ |
| put_bits(&pbc, 3, level); |
| put_bits(&pbc, 1, 0); /* cbr */ |
| put_bits(&pbc, 6, 0); /* reserved */ |
| put_bits(&pbc, 1, !interlace); /* no interlace */ |
| put_bits(&pbc, 1, !packet_seq); /* no multiple seq */ |
| put_bits(&pbc, 1, !packet_entry); /* no multiple entry */ |
| put_bits(&pbc, 1, !slices); /* no slice code */ |
| put_bits(&pbc, 1, 0); /* no bframe */ |
| put_bits(&pbc, 1, 0); /* reserved */ |
| |
| /* framerate */ |
| if (track->st->avg_frame_rate.num > 0 && track->st->avg_frame_rate.den > 0) |
| put_bits32(&pbc, track->st->avg_frame_rate.num / track->st->avg_frame_rate.den); |
| else |
| put_bits32(&pbc, 0xffffffff); |
| |
| flush_put_bits(&pbc); |
| |
| av_free(unescaped); |
| |
| return 0; |
| } |
| |
| static int mov_write_dvc1_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| uint8_t buf[7] = { 0 }; |
| int ret; |
| |
| if ((ret = mov_write_dvc1_structs(track, buf)) < 0) |
| return ret; |
| |
| avio_wb32(pb, track->vos_len + 8 + sizeof(buf)); |
| ffio_wfourcc(pb, "dvc1"); |
| avio_write(pb, buf, sizeof(buf)); |
| avio_write(pb, track->vos_data, track->vos_len); |
| |
| return 0; |
| } |
| |
| static int mov_write_glbl_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| avio_wb32(pb, track->vos_len + 8); |
| ffio_wfourcc(pb, "glbl"); |
| avio_write(pb, track->vos_data, track->vos_len); |
| return 8 + track->vos_len; |
| } |
| |
| /** |
| * Compute flags for 'lpcm' tag. |
| * See CoreAudioTypes and AudioStreamBasicDescription at Apple. |
| */ |
| static int mov_get_lpcm_flags(enum AVCodecID codec_id) |
| { |
| switch (codec_id) { |
| case AV_CODEC_ID_PCM_F32BE: |
| case AV_CODEC_ID_PCM_F64BE: |
| return 11; |
| case AV_CODEC_ID_PCM_F32LE: |
| case AV_CODEC_ID_PCM_F64LE: |
| return 9; |
| case AV_CODEC_ID_PCM_U8: |
| return 10; |
| case AV_CODEC_ID_PCM_S16BE: |
| case AV_CODEC_ID_PCM_S24BE: |
| case AV_CODEC_ID_PCM_S32BE: |
| return 14; |
| case AV_CODEC_ID_PCM_S8: |
| case AV_CODEC_ID_PCM_S16LE: |
| case AV_CODEC_ID_PCM_S24LE: |
| case AV_CODEC_ID_PCM_S32LE: |
| return 12; |
| default: |
| return 0; |
| } |
| } |
| |
| static int get_cluster_duration(MOVTrack *track, int cluster_idx) |
| { |
| int64_t next_dts; |
| |
| if (cluster_idx >= track->entry) |
| return 0; |
| |
| if (cluster_idx + 1 == track->entry) |
| next_dts = track->track_duration + track->start_dts; |
| else |
| next_dts = track->cluster[cluster_idx + 1].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->enc->frame_size; |
| |
| /* use 1 for raw PCM */ |
| if (!track->audio_vbr) |
| return 1; |
| |
| /* check to see if duration is constant for all clusters */ |
| if (!track->entry) |
| return 0; |
| first_duration = get_cluster_duration(track, 0); |
| for (i = 1; i < track->entry; i++) { |
| if (get_cluster_duration(track, i) != first_duration) |
| return 0; |
| } |
| return first_duration; |
| } |
| |
| static int mov_write_audio_tag(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 (track->timescale > UINT16_MAX) { |
| if (mov_get_lpcm_flags(track->enc->codec_id)) |
| tag = AV_RL32("lpcm"); |
| version = 2; |
| } else if (track->audio_vbr || mov_pcm_le_gt16(track->enc->codec_id) || |
| mov_pcm_be_gt16(track->enc->codec_id) || |
| track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || |
| track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || |
| track->enc->codec_id == AV_CODEC_ID_QDM2) { |
| version = 1; |
| } |
| } |
| |
| avio_wb32(pb, 0); /* size */ |
| 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 */ |
| |
| /* SoundDescription */ |
| avio_wb16(pb, version); /* Version */ |
| avio_wb16(pb, 0); /* Revision level */ |
| avio_wb32(pb, 0); /* Reserved */ |
| |
| if (version == 2) { |
| avio_wb16(pb, 3); |
| avio_wb16(pb, 16); |
| avio_wb16(pb, 0xfffe); |
| avio_wb16(pb, 0); |
| avio_wb32(pb, 0x00010000); |
| avio_wb32(pb, 72); |
| avio_wb64(pb, av_double2int(track->enc->sample_rate)); |
| avio_wb32(pb, track->enc->channels); |
| avio_wb32(pb, 0x7F000000); |
| avio_wb32(pb, av_get_bits_per_sample(track->enc->codec_id)); |
| avio_wb32(pb, mov_get_lpcm_flags(track->enc->codec_id)); |
| avio_wb32(pb, track->sample_size); |
| avio_wb32(pb, get_samples_per_packet(track)); |
| } else { |
| if (track->mode == MODE_MOV) { |
| avio_wb16(pb, track->enc->channels); |
| if (track->enc->codec_id == AV_CODEC_ID_PCM_U8 || |
| track->enc->codec_id == AV_CODEC_ID_PCM_S8) |
| avio_wb16(pb, 8); /* bits per sample */ |
| else if (track->enc->codec_id == AV_CODEC_ID_ADPCM_G726) |
| avio_wb16(pb, track->enc->bits_per_coded_sample); |
| else |
| avio_wb16(pb, 16); |
| avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ |
| } else { /* reserved for mp4/3gp */ |
| avio_wb16(pb, 2); |
| avio_wb16(pb, 16); |
| avio_wb16(pb, 0); |
| } |
| |
| avio_wb16(pb, 0); /* packet size (= 0) */ |
| avio_wb16(pb, track->enc->sample_rate <= UINT16_MAX ? |
| track->enc->sample_rate : 0); |
| avio_wb16(pb, 0); /* Reserved */ |
| } |
| |
| if (version == 1) { /* SoundDescription V1 extended info */ |
| if (mov_pcm_le_gt16(track->enc->codec_id) || |
| mov_pcm_be_gt16(track->enc->codec_id)) |
| avio_wb32(pb, 1); /* must be 1 for uncompressed formats */ |
| else |
| avio_wb32(pb, track->enc->frame_size); /* Samples per packet */ |
| avio_wb32(pb, track->sample_size / track->enc->channels); /* Bytes per packet */ |
| avio_wb32(pb, track->sample_size); /* Bytes per frame */ |
| avio_wb32(pb, 2); /* Bytes per sample */ |
| } |
| |
| if (track->mode == MODE_MOV && |
| (track->enc->codec_id == AV_CODEC_ID_AAC || |
| track->enc->codec_id == AV_CODEC_ID_AC3 || |
| track->enc->codec_id == AV_CODEC_ID_EAC3 || |
| track->enc->codec_id == AV_CODEC_ID_AMR_NB || |
| track->enc->codec_id == AV_CODEC_ID_ALAC || |
| track->enc->codec_id == AV_CODEC_ID_ADPCM_MS || |
| track->enc->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV || |
| track->enc->codec_id == AV_CODEC_ID_QDM2 || |
| (mov_pcm_le_gt16(track->enc->codec_id) && version==1) || |
| (mov_pcm_be_gt16(track->enc->codec_id) && version==1))) |
| mov_write_wave_tag(pb, track); |
| else if (track->tag == MKTAG('m','p','4','a')) |
| mov_write_esds_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_AMR_NB) |
| mov_write_amr_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_AC3) |
| mov_write_ac3_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_EAC3) |
| mov_write_eac3_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_ALAC) |
| mov_write_extradata_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_WMAPRO) |
| mov_write_wfex_tag(pb, track); |
| else if (track->vos_len > 0) |
| mov_write_glbl_tag(pb, track); |
| |
| if (track->mode == MODE_MOV && track->enc->codec_type == AVMEDIA_TYPE_AUDIO) |
| mov_write_chan_tag(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); |
| } |
| |
| static int mov_write_d263_tag(AVIOContext *pb) |
| { |
| avio_wb32(pb, 0xf); /* size */ |
| ffio_wfourcc(pb, "d263"); |
| ffio_wfourcc(pb, "FFMP"); |
| avio_w8(pb, 0); /* decoder version */ |
| /* FIXME use AVCodecContext level/profile, when encoder will set values */ |
| avio_w8(pb, 0xa); /* level */ |
| avio_w8(pb, 0); /* profile */ |
| return 0xf; |
| } |
| |
| static int mov_write_avcc_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| |
| avio_wb32(pb, 0); |
| ffio_wfourcc(pb, "avcC"); |
| ff_isom_write_avcc(pb, track->vos_data, track->vos_len); |
| 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); |
| return update_size(pb, pos); |
| } |
| |
| /* also used by all avid codecs (dv, imx, meridien) and their variants */ |
| static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int i; |
| int interlaced; |
| int cid; |
| |
| if (track->vos_data && track->vos_len > 0x29) { |
| if (track->vos_data[0] == 0x00 && |
| track->vos_data[1] == 0x00 && |
| track->vos_data[2] == 0x02 && |
| track->vos_data[3] == 0x80 && |
| (track->vos_data[4] == 0x01 || track->vos_data[4] == 0x02)) { |
| /* 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"); |
| if (track->enc->color_range == AVCOL_RANGE_MPEG || /* Legal range (16-235) */ |
| track->enc->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 */ |
| |
| avio_wb32(pb, 24); /* size */ |
| ffio_wfourcc(pb, "APRG"); |
| ffio_wfourcc(pb, "APRG"); |
| ffio_wfourcc(pb, "0001"); |
| avio_wb32(pb, 1); /* unknown */ |
| avio_wb32(pb, 0); /* unknown */ |
| |
| avio_wb32(pb, 120); /* size */ |
| ffio_wfourcc(pb, "ARES"); |
| ffio_wfourcc(pb, "ARES"); |
| ffio_wfourcc(pb, "0001"); |
| avio_wb32(pb, cid); /* dnxhd cid, some id ? */ |
| avio_wb32(pb, track->enc->width); |
| /* values below are based on samples created with quicktime and avid codecs */ |
| if (interlaced) { |
| avio_wb32(pb, track->enc->height / 2); |
| avio_wb32(pb, 2); /* unknown */ |
| avio_wb32(pb, 0); /* unknown */ |
| avio_wb32(pb, 4); /* unknown */ |
| } else { |
| avio_wb32(pb, track->enc->height); |
| avio_wb32(pb, 1); /* unknown */ |
| avio_wb32(pb, 0); /* unknown */ |
| if (track->enc->height == 1080) |
| avio_wb32(pb, 5); /* unknown */ |
| else |
| avio_wb32(pb, 6); /* unknown */ |
| } |
| /* padding */ |
| for (i = 0; i < 10; i++) |
| avio_wb64(pb, 0); |
| |
| return 0; |
| } |
| |
| static int mov_write_dpxe_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| avio_wb32(pb, 12); |
| ffio_wfourcc(pb, "DpxE"); |
| if (track->enc->extradata_size >= 12 && |
| !memcmp(&track->enc->extradata[4], "DpxE", 4)) { |
| avio_wb32(pb, track->enc->extradata[11]); |
| } else { |
| avio_wb32(pb, 1); |
| } |
| return 0; |
| } |
| |
| static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| int tag = track->enc->codec_tag; |
| |
| if (!ff_codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)) |
| return 0; |
| |
| if (track->enc->codec_id == AV_CODEC_ID_H264) tag = MKTAG('a','v','c','1'); |
| else if (track->enc->codec_id == AV_CODEC_ID_HEVC) tag = MKTAG('h','e','v','1'); |
| else if (track->enc->codec_id == AV_CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); |
| else if (track->enc->codec_id == AV_CODEC_ID_EAC3) tag = MKTAG('e','c','-','3'); |
| else if (track->enc->codec_id == AV_CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); |
| else if (track->enc->codec_id == AV_CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); |
| else if (track->enc->codec_id == AV_CODEC_ID_VC1) tag = MKTAG('v','c','-','1'); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); |
| else if (track->enc->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) |
| { |
| int tag = track->enc->codec_tag; |
| |
| // keep original tag for subs, ipod supports both formats |
| if (!(track->enc->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->enc->codec_id); |
| |
| if (!av_match_ext(s->filename, "m4a") && |
| !av_match_ext(s->filename, "m4b") && |
| !av_match_ext(s->filename, "m4v")) |
| av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v nor .m4b " |
| "Quicktime/Ipod might not play the file\n"); |
| |
| return tag; |
| } |
| |
| static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| int tag; |
| |
| if (track->enc->width == 720) { /* SD */ |
| if (track->enc->height == 480) { /* NTSC */ |
| if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); |
| else tag = MKTAG('d','v','c',' '); |
| }else if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); |
| else if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); |
| else tag = MKTAG('d','v','p','p'); |
| } else if (track->enc->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->enc->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 { |
| av_log(s, AV_LOG_ERROR, "unsupported height for dv codec\n"); |
| return 0; |
| } |
| |
| return tag; |
| } |
| |
| static AVRational find_fps(AVFormatContext *s, AVStream *st) |
| { |
| AVRational rate = {st->codec->time_base.den, st->codec->time_base.num}; |
| /* if the codec time base makes no sense, try to fallback on stream frame rate */ |
| 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; |
| } |
| |
| return rate; |
| } |
| |
| static int mov_get_mpeg2_xdcam_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| int tag = track->enc->codec_tag; |
| int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; |
| AVStream *st = track->st; |
| int rate = av_q2d(find_fps(s, st)); |
| |
| if (!tag) |
| tag = MKTAG('m', '2', 'v', '1'); //fallback tag |
| |
| if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P) { |
| if (track->enc->width == 1280 && track->enc->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->enc->width == 1440 && track->enc->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->enc->width == 1920 && track->enc->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->enc->pix_fmt == AV_PIX_FMT_YUV422P) { |
| if (track->enc->width == 1280 && track->enc->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->enc->width == 1920 && track->enc->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->enc->codec_tag; |
| int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; |
| AVStream *st = track->st; |
| int rate = av_q2d(find_fps(s, st)); |
| |
| if (!tag) |
| tag = MKTAG('a', 'v', 'c', 'i'); //fallback tag |
| |
| if (track->enc->pix_fmt == AV_PIX_FMT_YUV420P10) { |
| if (track->enc->width == 960 && track->enc->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->enc->width == 1440 && track->enc->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->enc->pix_fmt == AV_PIX_FMT_YUV422P10) { |
| if (track->enc->width == 1280 && track->enc->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->enc->width == 1920 && track->enc->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->enc->width == 4096 && track->enc->height == 2160 |
| || track->enc->width == 3840 && track->enc->height == 2160 |
| || track->enc->width == 2048 && track->enc->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 }, |
| { AV_PIX_FMT_RGB555LE,MKTAG('L','5','5','5'), 16 }, |
| { AV_PIX_FMT_RGB565LE,MKTAG('L','5','6','5'), 16 }, |
| { AV_PIX_FMT_RGB565BE,MKTAG('B','5','6','5'), 16 }, |
| { AV_PIX_FMT_GRAY16BE,MKTAG('b','1','6','g'), 16 }, |
| { AV_PIX_FMT_RGB24, MKTAG('r','a','w',' '), 24 }, |
| { AV_PIX_FMT_BGR24, MKTAG('2','4','B','G'), 24 }, |
| { AV_PIX_FMT_ARGB, MKTAG('r','a','w',' '), 32 }, |
| { AV_PIX_FMT_BGRA, MKTAG('B','G','R','A'), 32 }, |
| { AV_PIX_FMT_RGBA, MKTAG('R','G','B','A'), 32 }, |
| { AV_PIX_FMT_ABGR, MKTAG('A','B','G','R'), 32 }, |
| { AV_PIX_FMT_RGB48BE, MKTAG('b','4','8','r'), 48 }, |
| }; |
| |
| static int mov_get_rawvideo_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| int tag = track->enc->codec_tag; |
| int i; |
| enum AVPixelFormat pix_fmt; |
| |
| for (i = 0; i < FF_ARRAY_ELEMS(mov_pix_fmt_tags); i++) { |
| if (track->enc->pix_fmt == mov_pix_fmt_tags[i].pix_fmt) { |
| tag = mov_pix_fmt_tags[i].tag; |
| track->enc->bits_per_coded_sample = mov_pix_fmt_tags[i].bps; |
| if (track->enc->codec_tag == mov_pix_fmt_tags[i].tag) |
| break; |
| } |
| } |
| |
| pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_mov, |
| track->enc->bits_per_coded_sample); |
| if (tag == MKTAG('r','a','w',' ') && |
| track->enc->pix_fmt != pix_fmt && |
| track->enc->pix_fmt != 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->enc->pix_fmt)); |
| return tag; |
| } |
| |
| static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| int tag = track->enc->codec_tag; |
| |
| if (!tag || (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL && |
| (track->enc->codec_id == AV_CODEC_ID_DVVIDEO || |
| track->enc->codec_id == AV_CODEC_ID_RAWVIDEO || |
| track->enc->codec_id == AV_CODEC_ID_H263 || |
| track->enc->codec_id == AV_CODEC_ID_H264 || |
| track->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO || |
| av_get_bits_per_sample(track->enc->codec_id)))) { // pcm audio |
| if (track->enc->codec_id == AV_CODEC_ID_DVVIDEO) |
| tag = mov_get_dv_codec_tag(s, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_RAWVIDEO) |
| tag = mov_get_rawvideo_codec_tag(s, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO) |
| tag = mov_get_mpeg2_xdcam_codec_tag(s, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_H264) |
| tag = mov_get_h264_codec_tag(s, track); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { |
| tag = ff_codec_get_tag(ff_codec_movvideo_tags, track->enc->codec_id); |
| if (!tag) { // if no mac fcc found, try with Microsoft tags |
| tag = ff_codec_get_tag(ff_codec_bmp_tags, track->enc->codec_id); |
| if (tag) |
| av_log(s, AV_LOG_WARNING, "Using MS style video codec tag, " |
| "the file may be unplayable!\n"); |
| } |
| } else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) { |
| tag = ff_codec_get_tag(ff_codec_movaudio_tags, track->enc->codec_id); |
| if (!tag) { // if no mac fcc found, try with Microsoft tags |
| int ms_tag = ff_codec_get_tag(ff_codec_wav_tags, track->enc->codec_id); |
| if (ms_tag) { |
| tag = MKTAG('m', 's', ((ms_tag >> 8) & 0xff), (ms_tag & 0xff)); |
| av_log(s, AV_LOG_WARNING, "Using MS style audio codec tag, " |
| "the file may be unplayable!\n"); |
| } |
| } |
| } else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) |
| tag = ff_codec_get_tag(ff_codec_movsubtitle_tags, track->enc->codec_id); |
| } |
| |
| 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[] = { // XXX: add GIF/PNG/JPEG? |
| { 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->enc->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->enc->codec_id); |
| else if (track->mode == MODE_F4V) |
| tag = ff_codec_get_tag(codec_f4v_tags, track->enc->codec_id); |
| else |
| tag = mov_get_codec_tag(s, track); |
| |
| return tag; |
| } |
| |
| /** Write uuid atom. |
| * Needed to make file play in iPods running newest firmware |
| * goes after avcC atom in moov.trak.mdia.minf.stbl.stsd.avc1 |
| */ |
| static int mov_write_uuid_tag_ipod(AVIOContext *pb) |
| { |
| avio_wb32(pb, 28); |
| ffio_wfourcc(pb, "uuid"); |
| avio_wb32(pb, 0x6b6840f2); |
| avio_wb32(pb, 0x5f244fc5); |
| avio_wb32(pb, 0xba39a51b); |
| avio_wb32(pb, 0xcf0323f3); |
| avio_wb32(pb, 0x0); |
| return 28; |
| } |
| |
| static const uint16_t fiel_data[] = { |
| 0x0000, 0x0100, 0x0201, 0x0206, 0x0209, 0x020e |
| }; |
| |
| static int mov_write_fiel_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| unsigned mov_field_order = 0; |
| if (track->enc->field_order < FF_ARRAY_ELEMS(fiel_data)) |
| mov_field_order = fiel_data[track->enc->field_order]; |
| else |
| return 0; |
| avio_wb32(pb, 10); |
| ffio_wfourcc(pb, "fiel"); |
| avio_wb16(pb, mov_field_order); |
| return 10; |
| } |
| |
| static int mov_write_subtitle_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| 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 */ |
| |
| if (track->enc->codec_id == AV_CODEC_ID_DVD_SUBTITLE) |
| mov_write_esds_tag(pb, track); |
| else if (track->enc->extradata_size) |
| avio_write(pb, track->enc->extradata, track->enc->extradata_size); |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| AVRational sar; |
| av_reduce(&sar.num, &sar.den, track->enc->sample_aspect_ratio.num, |
| track->enc->sample_aspect_ratio.den, INT_MAX); |
| |
| avio_wb32(pb, 16); |
| ffio_wfourcc(pb, "pasp"); |
| avio_wb32(pb, sar.num); |
| avio_wb32(pb, sar.den); |
| return 16; |
| } |
| |
| 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->enc->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 %d\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->enc->color_primaries == AVCOL_PRI_UNSPECIFIED && |
| track->enc->color_trc == AVCOL_TRC_UNSPECIFIED && |
| track->enc->colorspace == AVCOL_SPC_UNSPECIFIED) { |
| if ((track->enc->width >= 1920 && track->enc->height >= 1080) |
| || (track->enc->width == 1280 && track->enc->height == 720)) { |
| av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt709\n"); |
| track->enc->color_primaries = AVCOL_PRI_BT709; |
| } else if (track->enc->width == 720 && track->height == 576) { |
| av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt470bg\n"); |
| track->enc->color_primaries = AVCOL_PRI_BT470BG; |
| } else if (track->enc->width == 720 && |
| (track->height == 486 || track->height == 480)) { |
| av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming smpte170\n"); |
| track->enc->color_primaries = AVCOL_PRI_SMPTE170M; |
| } else { |
| av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, unable to assume anything\n"); |
| } |
| switch (track->enc->color_primaries) { |
| case AVCOL_PRI_BT709: |
| track->enc->color_trc = AVCOL_TRC_BT709; |
| track->enc->colorspace = AVCOL_SPC_BT709; |
| break; |
| case AVCOL_PRI_SMPTE170M: |
| case AVCOL_PRI_BT470BG: |
| track->enc->color_trc = AVCOL_TRC_BT709; |
| track->enc->colorspace = 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->enc->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->enc->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->enc->colorspace) { |
| 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->enc->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->enc->width == 1280 && track->enc->height == 720) |
| || (track->enc->width == 1440 && track->enc->height == 1080) |
| || (track->enc->width == 1920 && track->enc->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->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO && xdcam_res) { |
| int interlaced = track->enc->field_order > AV_FIELD_PROGRESSIVE; |
| AVStream *st = track->st; |
| int rate = av_q2d(find_fps(NULL, st)); |
| av_strlcatf(compressor_name, len, "XDCAM"); |
| if (track->enc->pix_fmt == AV_PIX_FMT_YUV422P) { |
| av_strlcatf(compressor_name, len, " HD422"); |
| } else if(track->enc->width == 1440) { |
| av_strlcatf(compressor_name, len, " HD"); |
| } else |
| av_strlcatf(compressor_name, len, " EX"); |
| |
| av_strlcatf(compressor_name, len, " %d%c", track->enc->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 */ |
| 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 */ |
| |
| avio_wb16(pb, 0); /* Codec stream version */ |
| avio_wb16(pb, 0); /* Codec stream revision (=0) */ |
| if (track->mode == MODE_MOV) { |
| ffio_wfourcc(pb, "FFMP"); /* Vendor */ |
| if (track->enc->codec_id == AV_CODEC_ID_RAWVIDEO) { |
| avio_wb32(pb, 0); /* Temporal Quality */ |
| avio_wb32(pb, 0x400); /* Spatial Quality = lossless*/ |
| } else { |
| avio_wb32(pb, 0x200); /* Temporal Quality = normal */ |
| avio_wb32(pb, 0x200); /* Spatial Quality = normal */ |
| } |
| } else { |
| avio_wb32(pb, 0); /* Reserved */ |
| avio_wb32(pb, 0); /* Reserved */ |
| avio_wb32(pb, 0); /* Reserved */ |
| } |
| avio_wb16(pb, track->enc->width); /* Video width */ |
| avio_wb16(pb, track->height); /* Video height */ |
| avio_wb32(pb, 0x00480000); /* Horizontal resolution 72dpi */ |
| avio_wb32(pb, 0x00480000); /* Vertical resolution 72dpi */ |
| avio_wb32(pb, 0); /* Data size (= 0) */ |
| avio_wb16(pb, 1); /* Frame count (= 1) */ |
| |
| /* FIXME not sure, ISO 14496-1 draft where it shall be set to 0 */ |
| find_compressor(compressor_name, 32, track); |
| avio_w8(pb, strlen(compressor_name)); |
| avio_write(pb, compressor_name, 31); |
| |
| if (track->mode == MODE_MOV && track->enc->bits_per_coded_sample) |
| avio_wb16(pb, track->enc->bits_per_coded_sample); |
| else |
| avio_wb16(pb, 0x18); /* Reserved */ |
| avio_wb16(pb, 0xffff); /* Reserved */ |
| if (track->tag == MKTAG('m','p','4','v')) |
| mov_write_esds_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_H263) |
| mov_write_d263_tag(pb); |
| else if (track->enc->codec_id == AV_CODEC_ID_AVUI || |
| track->enc->codec_id == AV_CODEC_ID_SVQ3) { |
| mov_write_extradata_tag(pb, track); |
| avio_wb32(pb, 0); |
| } else if (track->enc->codec_id == AV_CODEC_ID_DNXHD) { |
| mov_write_avid_tag(pb, track); |
| avid = 1; |
| } else if (track->enc->codec_id == AV_CODEC_ID_HEVC) |
| mov_write_hvcc_tag(pb, track); |
| else if (track->enc->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->enc->codec_id == AV_CODEC_ID_VC1 && track->vos_len > 0) |
| mov_write_dvc1_tag(pb, track); |
| else if (track->enc->codec_id == AV_CODEC_ID_VP6F || |
| track->enc->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->enc->codec_id == AV_CODEC_ID_R10K) { |
| if (track->enc->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->enc->codec_id != AV_CODEC_ID_H264 && |
| track->enc->codec_id != AV_CODEC_ID_MPEG4 && |
| track->enc->codec_id != AV_CODEC_ID_DNXHD) |
| if (track->enc->field_order != AV_FIELD_UNKNOWN) |
| mov_write_fiel_tag(pb, track); |
| |
| 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->enc->sample_aspect_ratio.den && track->enc->sample_aspect_ratio.num && |
| track->enc->sample_aspect_ratio.den != track->enc->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); |
| } |
| |
| static int mov_write_rtp_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "rtp "); |
| avio_wb32(pb, 0); /* Reserved */ |
| avio_wb16(pb, 0); /* Reserved */ |
| avio_wb16(pb, 1); /* Data-reference index */ |
| |
| avio_wb16(pb, 1); /* Hint track version */ |
| avio_wb16(pb, 1); /* Highest compatible version */ |
| avio_wb32(pb, track->max_packet_size); /* Max packet size */ |
| |
| avio_wb32(pb, 12); /* size */ |
| ffio_wfourcc(pb, "tims"); |
| avio_wb32(pb, track->timescale); |
| |
| 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 = av_rescale(track->timescale, track->enc->time_base.num, track->enc->time_base.den); |
| int nb_frames = ROUNDED_DIV(track->enc->time_base.den, track->enc->time_base.num); |
| AVDictionaryEntry *t = NULL; |
| |
| 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 */ |
| |
| if (track->st) |
| t = av_dict_get(track->st->metadata, "reel_name", NULL, 0); |
| |
| if (t && utf8len(t->value)) |
| 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 */ |
| avio_wb32(pb, 0); /* Reserved */ |
| avio_wb32(pb, 1); /* Data reference index */ |
| if (track->enc->extradata_size) |
| avio_write(pb, track->enc->extradata, track->enc->extradata_size); |
| #endif |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_stsd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "stsd"); |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb32(pb, 1); /* entry count */ |
| if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) |
| mov_write_video_tag(pb, mov, track); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) |
| mov_write_audio_tag(pb, mov, track); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) |
| mov_write_subtitle_tag(pb, track); |
| else if (track->enc->codec_tag == MKTAG('r','t','p',' ')) |
| mov_write_rtp_tag(pb, track); |
| else if (track->enc->codec_tag == MKTAG('t','m','c','d')) |
| mov_write_tmcd_tag(pb, track); |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_ctts_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| MOVStts *ctts_entries; |
| uint32_t entries = 0; |
| uint32_t atom_size; |
| int i; |
| |
| ctts_entries = av_malloc_array((track->entry + 1), sizeof(*ctts_entries)); /* worst case */ |
| if (!ctts_entries) |
| return AVERROR(ENOMEM); |
| ctts_entries[0].count = 1; |
| ctts_entries[0].duration = track->cluster[0].cts; |
| for (i = 1; i < track->entry; i++) { |
| if (track->cluster[i].cts == ctts_entries[entries].duration) { |
| ctts_entries[entries].count++; /* compress */ |
| } else { |
| entries++; |
| ctts_entries[entries].duration = track->cluster[i].cts; |
| ctts_entries[entries].count = 1; |
| } |
| } |
| entries++; /* last one */ |
| atom_size = 16 + (entries * 8); |
| avio_wb32(pb, atom_size); /* size */ |
| ffio_wfourcc(pb, "ctts"); |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb32(pb, entries); /* entry count */ |
| for (i = 0; i < entries; i++) { |
| avio_wb32(pb, ctts_entries[i].count); |
| avio_wb32(pb, ctts_entries[i].duration); |
| } |
| av_free(ctts_entries); |
| return atom_size; |
| } |
| |
| /* Time to sample atom */ |
| static int mov_write_stts_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| MOVStts *stts_entries = NULL; |
| uint32_t entries = -1; |
| uint32_t atom_size; |
| int i; |
| |
| if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO && !track->audio_vbr) { |
| stts_entries = av_malloc(sizeof(*stts_entries)); /* one entry */ |
| if (!stts_entries) |
| return AVERROR(ENOMEM); |
| stts_entries[0].count = track->sample_count; |
| stts_entries[0].duration = 1; |
| entries = 1; |
| } else { |
| if (track->entry) { |
| stts_entries = av_malloc_array(track->entry, sizeof(*stts_entries)); /* worst case */ |
| if (!stts_entries) |
| return AVERROR(ENOMEM); |
| } |
| for (i = 0; i < track->entry; i++) { |
| int duration = get_cluster_duration(track, i); |
| if (i && duration == stts_entries[entries].duration) { |
| stts_entries[entries].count++; /* compress */ |
| } else { |
| entries++; |
| stts_entries[entries].duration = duration; |
| stts_entries[entries].count = 1; |
| } |
| } |
| entries++; /* last one */ |
| } |
| atom_size = 16 + (entries * 8); |
| avio_wb32(pb, atom_size); /* size */ |
| ffio_wfourcc(pb, "stts"); |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb32(pb, entries); /* entry count */ |
| for (i = 0; i < entries; i++) { |
| avio_wb32(pb, stts_entries[i].count); |
| avio_wb32(pb, stts_entries[i].duration); |
| } |
| av_free(stts_entries); |
| return atom_size; |
| } |
| |
| static int mov_write_dref_tag(AVIOContext *pb) |
| { |
| avio_wb32(pb, 28); /* size */ |
| ffio_wfourcc(pb, "dref"); |
| avio_wb32(pb, 0); /* version & flags */ |
| 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(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(pb, mov, track); |
| mov_write_stts_tag(pb, track); |
| if ((track->enc->codec_type == AVMEDIA_TYPE_VIDEO || |
| track->enc->codec_tag == MKTAG('r','t','p',' ')) && |
| track->has_keyframes && track->has_keyframes < track->entry) |
| mov_write_stss_tag(pb, track, MOV_SYNC_SAMPLE); |
| if (track->mode == MODE_MOV && track->flags & MOV_TRACK_STPS) |
| mov_write_stss_tag(pb, track, MOV_PARTIAL_SYNC_SAMPLE); |
| if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO && |
| track->flags & MOV_TRACK_CTTS && track->entry) { |
| |
| if ((ret = mov_write_ctts_tag(pb, track)) < 0) |
| return ret; |
| } |
| mov_write_stsc_tag(pb, track); |
| mov_write_stsz_tag(pb, track); |
| mov_write_stco_tag(pb, track); |
| if (mov->encryption_scheme == MOV_ENC_CENC_AES_CTR) { |
| ff_mov_cenc_write_stbl_atoms(&track->cenc, pb); |
| } |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_dinf_tag(AVIOContext *pb) |
| { |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "dinf"); |
| mov_write_dref_tag(pb); |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_nmhd_tag(AVIOContext *pb) |
| { |
| avio_wb32(pb, 12); |
| ffio_wfourcc(pb, "nmhd"); |
| avio_wb32(pb, 0); |
| return 12; |
| } |
| |
| static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| 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 */ |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb16(pb, 0x40); /* graphics mode = */ |
| avio_wb16(pb, 0x8000); /* opColor (r?) */ |
| avio_wb16(pb, 0x8000); /* opColor (g?) */ |
| avio_wb16(pb, 0x8000); /* opColor (b?) */ |
| avio_wb16(pb, 0); /* balance */ |
| avio_wb16(pb, 0); /* reserved */ |
| |
| /* |
| * 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->enc->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); |
| } |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_smhd_tag(AVIOContext *pb) |
| { |
| avio_wb32(pb, 16); /* size */ |
| ffio_wfourcc(pb, "smhd"); |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb16(pb, 0); /* reserved (balance, normally = 0) */ |
| avio_wb16(pb, 0); /* reserved */ |
| return 16; |
| } |
| |
| static int mov_write_vmhd_tag(AVIOContext *pb) |
| { |
| avio_wb32(pb, 0x14); /* size (always 0x14) */ |
| ffio_wfourcc(pb, "vmhd"); |
| avio_wb32(pb, 0x01); /* version & flags */ |
| avio_wb64(pb, 0); /* reserved (graphics mode = copy) */ |
| return 0x14; |
| } |
| |
| static int is_clcp_track(MOVTrack *track) |
| { |
| return track->tag == MKTAG('c','7','0','8') || |
| track->tag == MKTAG('c','6','0','8'); |
| } |
| |
| static int mov_write_hdlr_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| const char *hdlr, *descr = NULL, *hdlr_type = NULL; |
| int64_t pos = avio_tell(pb); |
| |
| hdlr = "dhlr"; |
| hdlr_type = "url "; |
| descr = "DataHandler"; |
| |
| if (track) { |
| hdlr = (track->mode == MODE_MOV) ? "mhlr" : "\0\0\0\0"; |
| if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { |
| hdlr_type = "vide"; |
| descr = "VideoHandler"; |
| } else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) { |
| hdlr_type = "soun"; |
| descr = "SoundHandler"; |
| } else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { |
| if (is_clcp_track(track)) { |
| hdlr_type = "clcp"; |
| descr = "ClosedCaptionHandler"; |
| } else { |
| 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->enc->codec_tag == MKTAG('r','t','p',' ')) { |
| hdlr_type = "hint"; |
| descr = "HintHandler"; |
| } else if (track->enc->codec_tag == MKTAG('t','m','c','d')) { |
| hdlr_type = "tmcd"; |
| descr = "TimeCodeHandler"; |
| } else { |
| char tag_buf[32]; |
| av_get_codec_tag_string(tag_buf, sizeof(tag_buf), |
| track->enc->codec_tag); |
| |
| av_log(track->enc, AV_LOG_WARNING, |
| "Unknown hldr_type for %s / 0x%04X, writing dummy values\n", |
| tag_buf, track->enc->codec_tag); |
| } |
| if (track->st) { |
| // hdlr.name is used by some players to identify the content title |
| // of the track. So if an alternate handler description is |
| // specified, use it. |
| AVDictionaryEntry *t; |
| t = av_dict_get(track->st->metadata, "handler", NULL, 0); |
| if (t && utf8len(t->value)) |
| descr = t->value; |
| } |
| } |
| |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "hdlr"); |
| avio_wb32(pb, 0); /* Version & flags */ |
| avio_write(pb, hdlr, 4); /* handler */ |
| ffio_wfourcc(pb, hdlr_type); /* handler type */ |
| avio_wb32(pb, 0); /* reserved */ |
| avio_wb32(pb, 0); /* reserved */ |
| avio_wb32(pb, 0); /* reserved */ |
| if (!track || track->mode == MODE_MOV) |
| avio_w8(pb, strlen(descr)); /* pascal string */ |
| avio_write(pb, descr, strlen(descr)); /* handler description */ |
| if (track && track->mode != MODE_MOV) |
| avio_w8(pb, 0); /* c string */ |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_hmhd_tag(AVIOContext *pb) |
| { |
| /* This atom must be present, but leaving the values at zero |
| * seems harmless. */ |
| avio_wb32(pb, 28); /* size */ |
| ffio_wfourcc(pb, "hmhd"); |
| avio_wb32(pb, 0); /* version, flags */ |
| avio_wb16(pb, 0); /* maxPDUsize */ |
| avio_wb16(pb, 0); /* avgPDUsize */ |
| avio_wb32(pb, 0); /* maxbitrate */ |
| avio_wb32(pb, 0); /* avgbitrate */ |
| avio_wb32(pb, 0); /* reserved */ |
| return 28; |
| } |
| |
| static int mov_write_minf_tag(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->enc->codec_type == AVMEDIA_TYPE_VIDEO) |
| mov_write_vmhd_tag(pb); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) |
| mov_write_smhd_tag(pb); |
| else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { |
| if (track |