| /* |
| * 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 "av1.h" |
| #include "avc.h" |
| #include "libavcodec/ac3_parser_internal.h" |
| #include "libavcodec/dnxhddata.h" |
| #include "libavcodec/flac.h" |
| #include "libavcodec/get_bits.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/dovi_meta.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" }, |
| { "frag_every_frame", "Fragment at every frame", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_EVERY_FRAME}, 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" }, |
| { "cmaf", "Write CMAF compatible fragmented MP4", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_CMAF}, 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" }, |
| { "skip_sidx", "Skip writing of sidx atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, |
| { "write_colr", "Write colr atom even if the color info is unspecified (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" }, |
| { "prefer_icc", "If writing colr atom prioritise usage of ICC profile if it exists in stream packet side data", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_PREFER_ICC}, 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_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 }, |
| { "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}, |
| { "write_prft", "Write producer reference time box with specified time source", offsetof(MOVMuxContext, write_prft), AV_OPT_TYPE_INT, {.i64 = MOV_PRFT_NONE}, 0, MOV_PRFT_NB-1, AV_OPT_FLAG_ENCODING_PARAM, "prft"}, |
| { "wallclock", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MOV_PRFT_SRC_WALLCLOCK}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM, "prft"}, |
| { "pts", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MOV_PRFT_SRC_PTS}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM, "prft"}, |
| { "empty_hdlr_name", "write zero-length name string in hdlr atoms within mdia and minf atoms", offsetof(MOVMuxContext, empty_hdlr_name), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, |
| { 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; |
| } |
| |
| static int is_cover_image(const AVStream *st) |
| { |
| /* Eg. AV_DISPOSITION_ATTACHED_PIC | AV_DISPOSITION_TIMED_THUMBNAILS |
| * is encoded as sparse video track */ |
| return st && st->disposition == AV_DISPOSITION_ATTACHED_PIC; |
| } |
| |
| static int rtp_hinting_needed(const AVStream *st) |
| { |
| /* Add hint tracks for each real audio and video stream */ |
| if (is_cover_image(st)) |
| return 0; |
| return st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || |
| st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; |
| } |
| |
| /* 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); |
| } |
| |
| /* Sample dependency atom */ |
| static int mov_write_sdtp_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int i; |
| uint8_t leading, dependent, reference, redundancy; |
| int64_t pos = avio_tell(pb); |
| avio_wb32(pb, 0); // size |
| ffio_wfourcc(pb, "sdtp"); |
| avio_wb32(pb, 0); // version & flags |
| for (i = 0; i < track->entry; i++) { |
| dependent = MOV_SAMPLE_DEPENDENCY_YES; |
| leading = reference = redundancy = MOV_SAMPLE_DEPENDENCY_UNKNOWN; |
| if (track->cluster[i].flags & MOV_DISPOSABLE_SAMPLE) { |
| reference = MOV_SAMPLE_DEPENDENCY_NO; |
| } |
| if (track->cluster[i].flags & MOV_SYNC_SAMPLE) { |
| dependent = MOV_SAMPLE_DEPENDENCY_NO; |
| } |
| avio_w8(pb, (leading << 6) | (dependent << 4) | |
| (reference << 2) | redundancy); |
| } |
| 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(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) |
| { |
| GetBitContext gbc; |
| PutBitContext pbc; |
| uint8_t buf[3]; |
| int fscod, bsid, bsmod, acmod, lfeon, frmsizecod; |
| |
| if (track->vos_len < 7) { |
| av_log(s, AV_LOG_ERROR, |
| "Cannot write moov atom before AC3 packets." |
| " Set the delay_moov flag to fix this.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| 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) |
| { |
| AC3HeaderInfo *hdr = NULL; |
| struct eac3_info *info; |
| int num_blocks, ret; |
| |
| if (!track->eac3_priv && !(track->eac3_priv = av_mallocz(sizeof(*info)))) |
| return AVERROR(ENOMEM); |
| info = track->eac3_priv; |
| |
| if (avpriv_ac3_parse_header(&hdr, pkt->data, pkt->size) < 0) { |
| /* drop the packets until we see a good one */ |
| if (!track->entry) { |
| av_log(mov->fc, AV_LOG_WARNING, "Dropping invalid packet from start of the stream\n"); |
| ret = 0; |
| } else |
| ret = AVERROR_INVALIDDATA; |
| goto end; |
| } |
| |
| 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) { |
| ret = AVERROR(EINVAL); |
| goto end; |
| } |
| |
| /* 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) { |
| ret = AVERROR(EINVAL); |
| goto end; |
| } |
| |
| if (hdr->substreamid == info->num_ind_sub + 1) { |
| //info->num_ind_sub++; |
| avpriv_request_sample(mov->fc, "Multiple independent substreams"); |
| ret = AVERROR_PATCHWELCOME; |
| goto end; |
| } else if (hdr->substreamid < info->num_ind_sub || |
| hdr->substreamid == 0 && info->substream[0].bsid) { |
| info->ec3_done = 1; |
| goto concatenate; |
| } |
| } else { |
| if (hdr->substreamid != 0) { |
| avpriv_request_sample(mov->fc, "Multiple non EAC3 independent substreams"); |
| ret = AVERROR_PATCHWELCOME; |
| goto end; |
| } |
| } |
| |
| /* 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) { |
| GetBitContext gbc; |
| int i; |
| ret = avpriv_ac3_parse_header(&hdr, pkt->data + cumul_size, pkt->size - cumul_size); |
| if (ret < 0) |
| goto end; |
| if (hdr->frame_type != EAC3_FRAME_TYPE_DEPENDENT) { |
| ret = AVERROR(EINVAL); |
| goto end; |
| } |
| info->substream[parent].num_dep_sub++; |
| ret /= 8; |
| |
| /* header is parsed up to lfeon, but custom channel map may be needed */ |
| init_get_bits8(&gbc, pkt->data + cumul_size + ret, pkt->size - cumul_size - ret); |
| /* 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; |
| cumul_size += hdr->frame_size; |
| } |
| } |
| } |
| |
| concatenate: |
| if (!info->num_blocks && num_blocks == 6) { |
| ret = pkt->size; |
| goto end; |
| } |
| else if (info->num_blocks + num_blocks > 6) { |
| ret = AVERROR_INVALIDDATA; |
| goto end; |
| } |
| |
| if (!info->num_blocks) { |
| ret = av_packet_ref(&info->pkt, pkt); |
| if (!ret) |
| info->num_blocks = num_blocks; |
| goto end; |
| } else { |
| if ((ret = av_grow_packet(&info->pkt, pkt->size)) < 0) |
| goto end; |
| memcpy(info->pkt.data + info->pkt.size - pkt->size, pkt->data, pkt->size); |
| info->num_blocks += num_blocks; |
| info->pkt.duration += pkt->duration; |
| if (info->num_blocks != 6) |
| goto end; |
| av_packet_unref(pkt); |
| av_packet_move_ref(pkt, &info->pkt); |
| info->num_blocks = 0; |
| } |
| ret = pkt->size; |
| |
| end: |
| av_free(hdr); |
| |
| return ret; |
| } |
| #endif |
| |
| static int mov_write_eac3_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) |
| { |
| PutBitContext pbc; |
| uint8_t *buf; |
| struct eac3_info *info; |
| int size, i; |
| |
| if (!track->eac3_priv) { |
| av_log(s, AV_LOG_ERROR, |
| "Cannot write moov atom before EAC3 packets parsed.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| info = track->eac3_priv; |
| size = 2 + ((34 * (info->num_ind_sub + 1) + 7) >> 3); |
| buf = av_malloc(size); |
| if (!buf) { |
| return AVERROR(ENOMEM); |
| } |
| |
| 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 */ |
| } else { |
| put_bits(&pbc, 9, info->substream[i].chan_loc); |
| } |
| } |
| flush_put_bits(&pbc); |
| size = put_bits_count(&pbc) >> 3; |
| |
| avio_wb32(pb, size + 8); |
| ffio_wfourcc(pb, "dec3"); |
| avio_write(pb, buf, size); |
| |
| av_free(buf); |
| |
| 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->par->extradata, track->par->extradata_size); |
| 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; |
| 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; |
| } |
| |
| struct mpeg4_bit_rate_values { |
| uint32_t buffer_size; ///< Size of the decoding buffer for the elementary stream in bytes. |
| uint32_t max_bit_rate; ///< Maximum rate in bits/second over any window of one second. |
| uint32_t avg_bit_rate; ///< Average rate in bits/second over the entire presentation. |
| }; |
| |
| static struct mpeg4_bit_rate_values calculate_mpeg4_bit_rates(MOVTrack *track) |
| { |
| AVCPBProperties *props = track->st ? |
| (AVCPBProperties*)av_stream_get_side_data(track->st, |
| AV_PKT_DATA_CPB_PROPERTIES, |
| NULL) : |
| NULL; |
| struct mpeg4_bit_rate_values bit_rates = { 0 }; |
| |
| bit_rates.avg_bit_rate = compute_avg_bitrate(track); |
| if (!bit_rates.avg_bit_rate) { |
| // if the average bit rate cannot be calculated at this point, such as |
| // in the case of fragmented MP4, utilize the following values as |
| // fall-back in priority order: |
| // |
| // 1. average bit rate property |
| // 2. bit rate (usually average over the whole clip) |
| // 3. maximum bit rate property |
| |
| if (props && props->avg_bitrate) { |
| bit_rates.avg_bit_rate = props->avg_bitrate; |
| } else if (track->par->bit_rate) { |
| bit_rates.avg_bit_rate = track->par->bit_rate; |
| } else if (props && props->max_bitrate) { |
| bit_rates.avg_bit_rate = props->max_bitrate; |
| } |
| } |
| |
| // (FIXME should be max rate in any 1 sec window) |
| bit_rates.max_bit_rate = FFMAX(track->par->bit_rate, |
| bit_rates.avg_bit_rate); |
| |
| // utilize values from properties if we have them available |
| if (props) { |
| bit_rates.max_bit_rate = FFMAX(bit_rates.max_bit_rate, |
| props->max_bitrate); |
| bit_rates.buffer_size = props->buffer_size / 8; |
| } |
| |
| return bit_rates; |
| } |
| |
| static int mov_write_esds_tag(AVIOContext *pb, MOVTrack *track) // Basic |
| { |
| struct mpeg4_bit_rate_values bit_rates = calculate_mpeg4_bit_rates(track); |
| int64_t pos = avio_tell(pb); |
| int decoder_specific_info_len = track->vos_len ? 5 + track->vos_len : 0; |
| |
| 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->par->codec_id == AV_CODEC_ID_MP2 || |
| track->par->codec_id == AV_CODEC_ID_MP3) && |
| track->par->sample_rate > 24000) |
| avio_w8(pb, 0x6B); // 11172-3 |
| else |
| avio_w8(pb, ff_codec_get_tag(ff_mp4_obj_type, track->par->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->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) |
| avio_w8(pb, (0x38 << 2) | 1); // flags (= NeroSubpicStream) |
| else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) |
| avio_w8(pb, 0x15); // flags (= Audiostream) |
| else |
| avio_w8(pb, 0x11); // flags (= Visualstream) |
| |
| avio_wb24(pb, bit_rates.buffer_size); // Buffersize DB |
| avio_wb32(pb, bit_rates.max_bit_rate); // maxbitrate |
| avio_wb32(pb, bit_rates.avg_bit_rate); |
| |
| 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(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); |
| 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"); |
| 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(AVFormatContext *s, 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(s, 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); |
| } |
| |
| static int mov_write_dmlp_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| int length; |
| avio_wb32(pb, 0); |
| ffio_wfourcc(pb, "dmlp"); |
| |
| if (track->vos_len < 20) { |
| av_log(s, AV_LOG_ERROR, |
| "Cannot write moov atom before TrueHD packets." |
| " Set the delay_moov flag to fix this.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| length = (AV_RB16(track->vos_data) & 0xFFF) * 2; |
| if (length < 20 || length > track->vos_len) |
| return AVERROR_INVALIDDATA; |
| |
| // Only TrueHD is supported |
| if (AV_RB32(track->vos_data + 4) != 0xF8726FBA) |
| return AVERROR_INVALIDDATA; |
| |
| avio_wb32(pb, AV_RB32(track->vos_data + 8)); /* format_info */ |
| avio_wb16(pb, AV_RB16(track->vos_data + 18) << 1); /* peak_data_rate */ |
| avio_wb32(pb, 0); /* reserved */ |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_chan_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) |
| { |
| uint32_t layout_tag, bitmap; |
| int64_t pos = avio_tell(pb); |
| |
| layout_tag = ff_mov_get_channel_layout_tag(track->par->codec_id, |
| track->par->channel_layout, |
| &bitmap); |
| if (!layout_tag) { |
| av_log(s, 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(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| |
| 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 */ |
| 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->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(s, pb, track); |
| } else if (track->par->codec_id == AV_CODEC_ID_EAC3) { |
| mov_write_eac3_tag(s, 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) { |
| mov_write_ms_tag(s, 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->par->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_btrt_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| struct mpeg4_bit_rate_values bit_rates = calculate_mpeg4_bit_rates(track); |
| if (!bit_rates.max_bit_rate && !bit_rates.avg_bit_rate && |
| !bit_rates.buffer_size) |
| // no useful data to be written, skip |
| return 0; |
| |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "btrt"); |
| |
| avio_wb32(pb, bit_rates.buffer_size); |
| avio_wb32(pb, bit_rates.max_bit_rate); |
| avio_wb32(pb, bit_rates.avg_bit_rate); |
| |
| return update_size(pb, pos); |
| } |
| |
| 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; |
| int ret = 0; |
| |
| if (track->mode == MODE_MOV) { |
| if (track->timescale > UINT16_MAX || !track->par->channels) { |
| 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 */ |
| 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->par->sample_rate)); |
| avio_wb32(pb, track->par->channels); |
| avio_wb32(pb, 0x7F000000); |
| avio_wb32(pb, av_get_bits_per_sample(track->par->codec_id)); |
| avio_wb32(pb, mov_get_lpcm_flags(track->par->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->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_ALAC || |
| 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 || |
| track->par->codec_id == AV_CODEC_ID_ALAC) { |
| 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) */ |
| if (track->par->codec_id == AV_CODEC_ID_OPUS) |
| avio_wb16(pb, 48000); |
| else if (track->par->codec_id == AV_CODEC_ID_TRUEHD) |
| avio_wb32(pb, track->par->sample_rate); |
| else |
| avio_wb16(pb, track->par->sample_rate <= UINT16_MAX ? |
| track->par->sample_rate : 0); |
| |
| if (track->par->codec_id != AV_CODEC_ID_TRUEHD) |
| 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_QDM2 || |
| (mov_pcm_le_gt16(track->par->codec_id) && version==1) || |
| (mov_pcm_be_gt16(track->par->codec_id) && version==1))) |
| ret = mov_write_wave_tag(s, pb, track); |
| else if (track->tag == MKTAG('m','p','4','a')) |
| ret = mov_write_esds_tag(pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_AMR_NB) |
| ret = mov_write_amr_tag(pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_AC3) |
| ret = mov_write_ac3_tag(s, pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_EAC3) |
| ret = mov_write_eac3_tag(s, pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_ALAC) |
| ret = mov_write_extradata_tag(pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_WMAPRO) |
| ret = mov_write_wfex_tag(s, pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_FLAC) |
| ret = mov_write_dfla_tag(pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_OPUS) |
| ret = mov_write_dops_tag(s, pb, track); |
| else if (track->par->codec_id == AV_CODEC_ID_TRUEHD) |
| ret = mov_write_dmlp_tag(s, pb, track); |
| else if (track->vos_len > 0) |
| ret = mov_write_glbl_tag(pb, track); |
| |
| if (ret < 0) |
| return ret; |
| |
| if (track->mode == MODE_MOV && track->par->codec_type == AVMEDIA_TYPE_AUDIO |
| && ((ret = mov_write_chan_tag(s, pb, track)) < 0)) { |
| return ret; |
| } |
| |
| if (mov->encryption_scheme != MOV_ENC_NONE |
| && ((ret = ff_mov_cenc_write_sinf_tag(track, pb, mov->encryption_kid)) < 0)) { |
| return ret; |
| } |
| |
| if (track->mode == MODE_MP4 && |
| ((ret = mov_write_btrt_tag(pb, track)) < 0)) |
| return ret; |
| |
| ret = update_size(pb, pos); |
| return ret; |
| } |
| |
| 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_av1c_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| |
| avio_wb32(pb, 0); |
| ffio_wfourcc(pb, "av1C"); |
| ff_isom_write_av1c(pb, track->vos_data, track->vos_len); |
| return update_size(pb, pos); |
| } |
| |
| 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_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"); |
| 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); |
| } |
| |
| /* 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; |
| 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"); |
| 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"); |
| 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 ? */ |
| 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 (interlaced) { |
| avio_wb32(pb, track->par->height / 2); |
| avio_wb32(pb, 2); /* unknown */ |
| avio_wb32(pb, 0); /* unknown */ |
| avio_wb32(pb, 4); /* unknown */ |
| } else { |
| avio_wb32(pb, track->par->height); |
| avio_wb32(pb, 1); /* unknown */ |
| avio_wb32(pb, 0); /* unknown */ |
| if (track->par->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->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'); |
| 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 tag = MKTAG('d','v','p','p'); |
| } 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 */ |
| 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->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 }, |
| { 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_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; |
| 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; |
| } |
| |
| static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| unsigned int tag = track->par->codec_tag; |
| |
| // "rtp " is used to distinguish internally created RTP-hint tracks |
| // (with rtp_ctx) from other tracks. |
| if (tag == MKTAG('r','t','p',' ')) |
| tag = 0; |
| if (!tag || (s->strict_std_compliance >= FF_COMPLIANCE_NORMAL && |
| (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 |
| tag = ff_codec_get_tag(ff_codec_bmp_tags, track->par->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->par->codec_type == AVMEDIA_TYPE_AUDIO) { |
| tag = ff_codec_get_tag(ff_codec_movaudio_tags, track->par->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->par->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->par->codec_type == AVMEDIA_TYPE_SUBTITLE) |
| tag = ff_codec_get_tag(ff_codec_movsubtitle_tags, track->par->codec_id); |
| } |
| |
| return tag; |
| } |
| |
| static const AVCodecTag codec_cover_image_tags[] = { |
| { AV_CODEC_ID_MJPEG, 0xD }, |
| { AV_CODEC_ID_PNG, 0xE }, |
| { AV_CODEC_ID_BMP, 0x1B }, |
| { AV_CODEC_ID_NONE, 0 }, |
| }; |
| |
| static unsigned int validate_codec_tag(const AVCodecTag *const *tags, |
| unsigned int tag, int codec_id) |
| { |
| int i; |
| |
| /** |
| * Check that tag + id is in the table |
| */ |
| for (i = 0; tags && tags[i]; i++) { |
| const AVCodecTag *codec_tags = tags[i]; |
| while (codec_tags->id != AV_CODEC_ID_NONE) { |
| if (avpriv_toupper4(codec_tags->tag) == avpriv_toupper4(tag) && |
| codec_tags->id == codec_id) |
| return codec_tags->tag; |
| codec_tags++; |
| } |
| } |
| return 0; |
| } |
| |
| static unsigned int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track) |
| { |
| if (is_cover_image(track->st)) |
| return ff_codec_get_tag(codec_cover_image_tags, track->par->codec_id); |
| |
| if (track->mode == MODE_IPOD) |
| if (!av_match_ext(s->url, "m4a") && |
| !av_match_ext(s->url, "m4v") && |
| !av_match_ext(s->url, "m4b")) |
| av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " |
| "Quicktime/Ipod might not play the file\n"); |
| |
| if (track->mode == MODE_MOV) { |
| return mov_get_codec_tag(s, track); |
| } else |
| return validate_codec_tag(s->oformat->codec_tag, track->par->codec_tag, |
| track->par->codec_id); |
| } |
| |
| /** 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, int field_order) |
| { |
| unsigned mov_field_order = 0; |
| if (field_order < FF_ARRAY_ELEMS(fiel_data)) |
| mov_field_order = fiel_data[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) |
| { |
| int ret = AVERROR_BUG; |
| 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->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) |
| mov_write_esds_tag(pb, track); |
| else if (track->par->extradata_size) |
| avio_write(pb, track->par->extradata, track->par->extradata_size); |
| |
| if (track->mode == MODE_MP4 && |
| ((ret = mov_write_btrt_tag(pb, track)) < 0)) |
| return ret; |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_st3d_tag(AVFormatContext *s, AVIOContext *pb, AVStereo3D *stereo_3d) |
| { |
| int8_t stereo_mode; |
| |
| if (stereo_3d->flags != 0) { |
| av_log(s, 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(s, 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(s, 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_dvcc_dvvc_tag(AVFormatContext *s, AVIOContext *pb, AVDOVIDecoderConfigurationRecord *dovi) |
| { |
| avio_wb32(pb, 32); /* size = 8 + 24 */ |
| if (dovi->dv_profile > 7) |
| ffio_wfourcc(pb, "dvvC"); |
| else |
| ffio_wfourcc(pb, "dvcC"); |
| avio_w8(pb, dovi->dv_version_major); |
| avio_w8(pb, dovi->dv_version_minor); |
| avio_wb16(pb, (dovi->dv_profile << 9) | (dovi->dv_level << 3) | |
| (dovi->rpu_present_flag << 2) | (dovi->el_present_flag << 1) | |
| dovi->bl_present_flag); |
| avio_wb32(pb, (dovi->dv_bl_signal_compatibility_id << 28) | 0); |
| |
| avio_wb32(pb, 0); /* reserved */ |
| avio_wb32(pb, 0); /* reserved */ |
| avio_wb32(pb, 0); /* reserved */ |
| avio_wb32(pb, 0); /* reserved */ |
| av_log(s, AV_LOG_DEBUG, "DOVI in %s box, version: %d.%d, profile: %d, level: %d, " |
| "rpu flag: %d, el flag: %d, bl flag: %d, compatibility id: %d\n", |
| dovi->dv_profile > 7 ? "dvvC" : "dvcC", |
| dovi->dv_version_major, dovi->dv_version_minor, |
| dovi->dv_profile, dovi->dv_level, |
| dovi->rpu_present_flag, |
| dovi->el_present_flag, |
| dovi->bl_present_flag, |
| dovi->dv_bl_signal_compatibility_id); |
| return 32; /* 8 + 24 */ |
| } |
| |
| static int mov_write_clap_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| avio_wb32(pb, 40); |
| ffio_wfourcc(pb, "clap"); |
| avio_wb32(pb, track->par->width); /* apertureWidth_N */ |
| avio_wb32(pb, 1); /* apertureWidth_D (= 1) */ |
| avio_wb32(pb, track->height); /* apertureHeight_N */ |
| avio_wb32(pb, 1); /* apertureHeight_D (= 1) */ |
| avio_wb32(pb, 0); /* horizOff_N (= 0) */ |
| avio_wb32(pb, 1); /* horizOff_D (= 1) */ |
| avio_wb32(pb, 0); /* vertOff_N (= 0) */ |
| avio_wb32(pb, 1); /* vertOff_D (= 1) */ |
| return 40; |
| } |
| |
| static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| AVRational sar; |
| av_reduce(&sar.num, &sar.den, track->par->sample_aspect_ratio.num, |
| track->par->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(AVFormatContext *s, 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(s, AV_LOG_DEBUG, "gamma value %g\n", gamma); |
| |
| if (gamma > 1e-6) { |
| gama = (uint32_t)lrint((double)(1<<16) * gamma); |
| av_log(s, 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(s, AV_LOG_WARNING, "gamma value unknown, unable to write gama atom\n"); |
| } |
| return 0; |
| } |
| |
| static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track, int prefer_icc) |
| { |
| int64_t pos = avio_tell(pb); |
| |
| // 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 |
| |
| const uint8_t *icc_profile; |
| int icc_profile_size; |
| |
| if (prefer_icc) { |
| icc_profile = av_stream_get_side_data(track->st, AV_PKT_DATA_ICC_PROFILE, &icc_profile_size); |
| |
| if (icc_profile) { |
| avio_wb32(pb, 12 + icc_profile_size); |
| ffio_wfourcc(pb, "colr"); |
| ffio_wfourcc(pb, "prof"); |
| avio_write(pb, icc_profile, icc_profile_size); |
| return 12 + icc_profile_size; |
| } |
| else { |
| av_log(NULL, AV_LOG_INFO, "no ICC profile found, will write nclx/nclc colour info instead\n"); |
| } |
| } |
| |
| /* We should only ever be called by MOV or MP4. */ |
| av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4); |
| |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "colr"); |
| if (track->mode == MODE_MP4) |
| ffio_wfourcc(pb, "nclx"); |
| else |
| ffio_wfourcc(pb, "nclc"); |
| // Do not try to guess the color info if it is AVCOL_PRI_UNSPECIFIED. |
| // e.g., Dolby Vision for Apple devices should be set to AVCOL_PRI_UNSPECIFIED. See |
| // https://developer.apple.com/av-foundation/High-Dynamic-Range-Metadata-for-Apple-Devices.pdf |
| avio_wb16(pb, track->par->color_primaries); |
| avio_wb16(pb, track->par->color_trc); |
| avio_wb16(pb, track->par->color_space); |
| if (track->mode == MODE_MP4) { |
| int full_range = track->par->color_range == AVCOL_RANGE_JPEG; |
| avio_w8(pb, full_range << 7); |
| } |
| |
| return update_size(pb, pos); |
| } |
| |
| static int mov_write_clli_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| const uint8_t *side_data; |
| const AVContentLightMetadata *content_light_metadata; |
| |
| side_data = av_stream_get_side_data(track->st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, NULL); |
| if (!side_data) { |
| av_log(NULL, AV_LOG_VERBOSE, "Not writing 'clli' atom. No content light level info.\n"); |
| return 0; |
| } |
| content_light_metadata = (const AVContentLightMetadata*)side_data; |
| |
| avio_wb32(pb, 12); // size |
| ffio_wfourcc(pb, "clli"); |
| avio_wb16(pb, content_light_metadata->MaxCLL); |
| avio_wb16(pb, content_light_metadata->MaxFALL); |
| return 12; |
| } |
| |
| static inline int64_t rescale_mdcv(AVRational q, int b) |
| { |
| return av_rescale(q.num, b, q.den); |
| } |
| |
| static int mov_write_mdcv_tag(AVIOContext *pb, MOVTrack *track) |
| { |
| const int chroma_den = 50000; |
| const int luma_den = 10000; |
| const uint8_t *side_data; |
| const AVMasteringDisplayMetadata *metadata; |
| |
| side_data = av_stream_get_side_data(track->st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, NULL); |
| metadata = (const AVMasteringDisplayMetadata*)side_data; |
| if (!metadata || !metadata->has_primaries || !metadata->has_luminance) { |
| av_log(NULL, AV_LOG_VERBOSE, "Not writing 'mdcv' atom. Missing mastering metadata.\n"); |
| return 0; |
| } |
| |
| avio_wb32(pb, 32); // size |
| ffio_wfourcc(pb, "mdcv"); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[1][0], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[1][1], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[2][0], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[2][1], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[0][0], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->display_primaries[0][1], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->white_point[0], chroma_den)); |
| avio_wb16(pb, rescale_mdcv(metadata->white_point[1], chroma_den)); |
| avio_wb32(pb, rescale_mdcv(metadata->max_luminance, luma_den)); |
| avio_wb32(pb, rescale_mdcv(metadata->min_luminance, luma_den)); |
| return 32; |
| } |
| |
| 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(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) |
| { |
| int ret = AVERROR_BUG; |
| int64_t pos = avio_tell(pb); |
| char compressor_name[32] = { 0 }; |
| int avid = 0; |
| |
| int uncompressed_ycbcr = ((track->par->codec_id == AV_CODEC_ID_RAWVIDEO && track->par->format == AV_PIX_FMT_UYVY422) |
| || (track->par->codec_id == AV_CODEC_ID_RAWVIDEO && track->par->format == AV_PIX_FMT_YUYV422) |
| || track->par->codec_id == AV_CODEC_ID_V308 |
| || track->par->codec_id == AV_CODEC_ID_V408 |
| || track->par->codec_id == AV_CODEC_ID_V410 |
| || track->par->codec_id == AV_CODEC_ID_V210); |
| |
| 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 */ |
| |
| if (uncompressed_ycbcr) { |
| avio_wb16(pb, 2); /* Codec stream version */ |
| } else { |
| 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->par->codec_id == AV_CODEC_ID_RAWVIDEO || uncompressed_ycbcr) { |
| 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->par->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->par->codec_id == AV_CODEC_ID_V410 || track->par->codec_id == AV_CODEC_ID_V210)) |
| avio_wb16(pb, 0x18); |
| else if (track->mode == MODE_MOV && 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 */ |
| |
| 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_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); |
| 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 && !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->codec_id == AV_CODEC_ID_VP9) { |
| mov_write_vpcc_tag(mov->fc, pb, track); |
| } else if (track->par->codec_id == AV_CODEC_ID_AV1) { |
| mov_write_av1c_tag(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->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(s, pb, track, mov->gamma); |
| else |
| av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n"); |
| } |
| if (track->mode == MODE_MOV || track->mode == MODE_MP4) { |
| int has_color_info = track->par->color_primaries != AVCOL_PRI_UNSPECIFIED && |
| track->par->color_trc != AVCOL_TRC_UNSPECIFIED && |
| track->par->color_space != AVCOL_SPC_UNSPECIFIED; |
| if (has_color_info || mov->flags & FF_MOV_FLAG_WRITE_COLR || |
| av_stream_get_side_data(track->st, AV_PKT_DATA_ICC_PROFILE, NULL)) { |
| int prefer_icc = mov->flags & FF_MOV_FLAG_PREFER_ICC || !has_color_info; |
| mov_write_colr_tag(pb, track, prefer_icc); |
| } else if (mov->flags & FF_MOV_FLAG_WRITE_COLR) { |
| av_log(mov->fc, AV_LOG_WARNING, "Not writing 'colr' atom. Format is not MOV or MP4.\n"); |
| } |
| } |
| if (track->mode == MODE_MOV || track->mode == MODE_MP4) { |
| mov_write_clli_tag(pb, track); |
| mov_write_mdcv_tag(pb, track); |
| } |
| |
| 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); |
| AVDOVIDecoderConfigurationRecord *dovi = (AVDOVIDecoderConfigurationRecord *) |
| av_stream_get_side_data(track->st, AV_PKT_DATA_DOVI_CONF, NULL);; |
| |
| if (stereo_3d) |
| mov_write_st3d_tag(s, pb, stereo_3d); |
| if (spherical_mapping) |
| mov_write_sv3d_tag(mov->fc, pb, spherical_mapping); |
| if (dovi) |
| mov_write_dvcc_dvvc_tag(s, pb, dovi); |
| } |
| |
| if (track->par->sample_aspect_ratio.den && track->par->sample_aspect_ratio.num) { |
| mov_write_pasp_tag(pb, track); |
| } |
| |
| if (uncompressed_ycbcr){ |
| mov_write_clap_tag(pb, track); |
| } |
| |
| if (mov->encryption_scheme != MOV_ENC_NONE) { |
| ff_mov_cenc_write_sinf_tag(track, pb, mov->encryption_kid); |
| } |
| |
| if (track->mode == MODE_MP4 && |
| ((ret = mov_write_btrt_tag(pb, track)) < 0)) |
| return ret; |
| |
| /* 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; |
| 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 */ |
| avio_wb32(pb, 0); /* Reserved */ |
| 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, MOVMuxContext *mov, MOVTrack *track) |
| { |
| int64_t pos = avio_tell(pb); |
| int ret = 0; |
| avio_wb32(pb, 0); /* size */ |
| ffio_wfourcc(pb, "stsd"); |
| avio_wb32(pb, 0); /* version & flags */ |
| avio_wb32(pb, 1); /* entry count */ |
| if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) |
| ret = mov_write_video_tag(s, pb, mov, track); |
| else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) |
| ret = mov_write_audio_tag(s, pb, mov, track); |
| else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) |
| ret = mov_write_subtitle_tag(pb, track); |
| else if (track->par->codec_tag == MKTAG('r','t','p',' ')) |
| ret = mov_write_rtp_tag(pb, track); |
| else if (track->par->codec_tag == MKTAG('t','m','c','d')) |
| ret = mov_write_tmcd_tag(pb, track); |
| else if (track->par->codec_tag == MKTAG('g','p','m','d')) |
| ret = mov_write_gpmd_tag(pb, track); |
| |
| if (ret < 0) |
| return ret; |
| |
| return update_size(pb, pos); |
| } |
| |
| 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_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; |
|