X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fmovenc.c;h=5d8dc4fd5d7c26816d0be19eb4124aa8f8e69b63;hb=af9e622776c9268fd473cc23a0fd6b29f0017f64;hp=26cb2e6ea103daa46ac608b076a6e1c7fe2872d0;hpb=27c94c57dc84da8125225fda7d241be57d19b391;p=ffmpeg diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 26cb2e6ea10..5d8dc4fd5d7 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -51,6 +51,7 @@ #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" @@ -72,11 +73,13 @@ static const AVOption options[] = { { "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 (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" }, @@ -313,7 +316,7 @@ static int mov_write_amr_tag(AVIOContext *pb, MOVTrack *track) return 0x11; } -static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_ac3_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { GetBitContext gbc; PutBitContext pbc; @@ -321,7 +324,7 @@ static int mov_write_ac3_tag(AVIOContext *pb, MOVTrack *track) int fscod, bsid, bsmod, acmod, lfeon, frmsizecod; if (track->vos_len < 7) { - av_log(pb, AV_LOG_ERROR, + 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); @@ -518,8 +521,6 @@ concatenate: 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) - goto end; if (info->num_blocks != 6) goto end; av_packet_unref(pkt); @@ -535,7 +536,7 @@ end: } #endif -static int mov_write_eac3_tag(AVIOContext *pb, MOVTrack *track) +static int mov_write_eac3_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { PutBitContext pbc; uint8_t *buf; @@ -543,7 +544,7 @@ static int mov_write_eac3_tag(AVIOContext *pb, MOVTrack *track) int size, i; if (!track->eac3_priv) { - av_log(pb, AV_LOG_ERROR, + av_log(s, AV_LOG_ERROR, "Cannot write moov atom before EAC3 packets parsed.\n"); return AVERROR(EINVAL); } @@ -748,14 +749,14 @@ static int mov_write_dfla_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } -static int mov_write_dops_tag(AVIOContext *pb, MOVTrack *track) +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(pb, AV_LOG_ERROR, "invalid extradata size\n"); + av_log(s, AV_LOG_ERROR, "invalid extradata size\n"); return AVERROR_INVALIDDATA; } /* extradata contains an Ogg OpusHead, other than byte-ordering and @@ -771,6 +772,35 @@ static int mov_write_dops_tag(AVIOContext *pb, MOVTrack *track) 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; @@ -825,9 +855,9 @@ static int mov_write_wave_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra } else if (track->par->codec_id == AV_CODEC_ID_AMR_NB) { mov_write_amr_tag(pb, track); } else if (track->par->codec_id == AV_CODEC_ID_AC3) { - mov_write_ac3_tag(pb, track); + mov_write_ac3_tag(s, pb, track); } else if (track->par->codec_id == AV_CODEC_ID_EAC3) { - mov_write_eac3_tag(pb, track); + 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); @@ -1100,10 +1130,14 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex 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); - avio_wb16(pb, 0); /* Reserved */ + + if (track->par->codec_id != AV_CODEC_ID_TRUEHD) + avio_wb16(pb, 0); /* Reserved */ } if (version == 1) { /* SoundDescription V1 extended info */ @@ -1134,9 +1168,9 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex 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(pb, track); + ret = mov_write_ac3_tag(s, pb, track); else if (track->par->codec_id == AV_CODEC_ID_EAC3) - ret = mov_write_eac3_tag(pb, track); + 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) @@ -1144,7 +1178,9 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex 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(pb, track); + 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); @@ -1691,12 +1727,12 @@ static int mov_write_subtitle_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } -static int mov_write_st3d_tag(AVIOContext *pb, AVStereo3D *stereo_3d) +static int mov_write_st3d_tag(AVFormatContext *s, AVIOContext *pb, AVStereo3D *stereo_3d) { int8_t stereo_mode; if (stereo_3d->flags != 0) { - av_log(pb, AV_LOG_WARNING, "Unsupported stereo_3d flags %x. st3d not written.\n", stereo_3d->flags); + av_log(s, AV_LOG_WARNING, "Unsupported stereo_3d flags %x. st3d not written.\n", stereo_3d->flags); return 0; } @@ -1711,7 +1747,7 @@ static int mov_write_st3d_tag(AVIOContext *pb, AVStereo3D *stereo_3d) stereo_mode = 2; break; default: - av_log(pb, AV_LOG_WARNING, "Unsupported stereo_3d type %s. st3d not written.\n", av_stereo3d_type_name(stereo_3d->type)); + 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 */ @@ -1729,7 +1765,7 @@ static int mov_write_sv3d_tag(AVFormatContext *s, AVIOContext *pb, AVSphericalMa if (spherical_mapping->projection != AV_SPHERICAL_EQUIRECTANGULAR && spherical_mapping->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE && spherical_mapping->projection != AV_SPHERICAL_CUBEMAP) { - av_log(pb, AV_LOG_WARNING, "Unsupported projection %d. sv3d not written.\n", spherical_mapping->projection); + av_log(s, AV_LOG_WARNING, "Unsupported projection %d. sv3d not written.\n", spherical_mapping->projection); return 0; } @@ -1779,6 +1815,36 @@ static int mov_write_sv3d_tag(AVFormatContext *s, AVIOContext *pb, AVSphericalMa 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); @@ -1807,110 +1873,128 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) return 16; } -static int mov_write_gama_tag(AVIOContext *pb, MOVTrack *track, double gamma) +static int mov_write_gama_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track, double gamma) { uint32_t gama = 0; - if (gamma <= 0.0) - { + if (gamma <= 0.0) { gamma = avpriv_get_gamma_from_trc(track->par->color_trc); } - av_log(pb, AV_LOG_DEBUG, "gamma value %g\n", gamma); + 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(pb, AV_LOG_DEBUG, "writing gama value %"PRId32"\n", gama); + 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(pb, AV_LOG_WARNING, "gamma value unknown, unable to write gama atom\n"); + } 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) +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 - if (track->par->color_primaries == AVCOL_PRI_UNSPECIFIED && - track->par->color_trc == AVCOL_TRC_UNSPECIFIED && - track->par->color_space == AVCOL_SPC_UNSPECIFIED) { - if ((track->par->width >= 1920 && track->par->height >= 1080) - || (track->par->width == 1280 && track->par->height == 720)) { - av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt709\n"); - track->par->color_primaries = AVCOL_PRI_BT709; - } else if (track->par->width == 720 && track->height == 576) { - av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt470bg\n"); - track->par->color_primaries = AVCOL_PRI_BT470BG; - } else if (track->par->width == 720 && - (track->height == 486 || track->height == 480)) { - av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming smpte170\n"); - track->par->color_primaries = AVCOL_PRI_SMPTE170M; - } else { - av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, unable to assume anything\n"); + 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; } - switch (track->par->color_primaries) { - case AVCOL_PRI_BT709: - track->par->color_trc = AVCOL_TRC_BT709; - track->par->color_space = AVCOL_SPC_BT709; - break; - case AVCOL_PRI_SMPTE170M: - case AVCOL_PRI_BT470BG: - track->par->color_trc = AVCOL_TRC_BT709; - track->par->color_space = AVCOL_SPC_SMPTE170M; - break; + 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, 18 + (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"); - switch (track->par->color_primaries) { - case AVCOL_PRI_BT709: avio_wb16(pb, 1); break; - case AVCOL_PRI_BT470BG: avio_wb16(pb, 5); break; - case AVCOL_PRI_SMPTE170M: - case AVCOL_PRI_SMPTE240M: avio_wb16(pb, 6); break; - case AVCOL_PRI_BT2020: avio_wb16(pb, 9); break; - case AVCOL_PRI_SMPTE431: avio_wb16(pb, 11); break; - case AVCOL_PRI_SMPTE432: avio_wb16(pb, 12); break; - default: avio_wb16(pb, 2); - } - switch (track->par->color_trc) { - case AVCOL_TRC_BT709: avio_wb16(pb, 1); break; - case AVCOL_TRC_SMPTE170M: avio_wb16(pb, 1); break; // remapped - case AVCOL_TRC_SMPTE240M: avio_wb16(pb, 7); break; - case AVCOL_TRC_SMPTEST2084: avio_wb16(pb, 16); break; - case AVCOL_TRC_SMPTE428: avio_wb16(pb, 17); break; - case AVCOL_TRC_ARIB_STD_B67: avio_wb16(pb, 18); break; - default: avio_wb16(pb, 2); - } - switch (track->par->color_space) { - case AVCOL_SPC_BT709: avio_wb16(pb, 1); break; - case AVCOL_SPC_BT470BG: - case AVCOL_SPC_SMPTE170M: avio_wb16(pb, 6); break; - case AVCOL_SPC_SMPTE240M: avio_wb16(pb, 7); break; - case AVCOL_SPC_BT2020_NCL: avio_wb16(pb, 9); break; - default: avio_wb16(pb, 2); - } - + // 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 19; - } else { - return 18; } + + 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) @@ -1941,7 +2025,7 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) } } -static int mov_write_video_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) +static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int64_t pos = avio_tell(pb); char compressor_name[32] = { 0 }; @@ -2076,25 +2160,33 @@ static int mov_write_video_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *tr if (mov->flags & FF_MOV_FLAG_WRITE_GAMA) { if (track->mode == MODE_MOV) - mov_write_gama_tag(pb, track, mov->gamma); + 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 (mov->flags & FF_MOV_FLAG_WRITE_COLR) { if (track->mode == MODE_MOV || track->mode == MODE_MP4) - mov_write_colr_tag(pb, track); + mov_write_colr_tag(pb, track, mov->flags & FF_MOV_FLAG_PREFER_ICC); else 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(pb, 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) { @@ -2233,7 +2325,7 @@ static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext 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(pb, mov, track); + 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) @@ -2458,6 +2550,8 @@ static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext return ret; mov_write_stts_tag(pb, track); if ((track->par->codec_type == AVMEDIA_TYPE_VIDEO || + track->par->codec_id == AV_CODEC_ID_TRUEHD || + track->par->codec_id == AV_CODEC_ID_MPEGH_3D_AUDIO || track->par->codec_tag == MKTAG('r','t','p',' ')) && track->has_keyframes && track->has_keyframes < track->entry) mov_write_stss_tag(pb, track, MOV_SYNC_SAMPLE); @@ -2629,7 +2723,7 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra } else { hdlr_type = "text"; } - descr = "SubtitleHandler"; + descr = "SubtitleHandler"; } } else if (track->par->codec_tag == MKTAG('r','t','p',' ')) { hdlr_type = "hint"; @@ -2725,10 +2819,28 @@ static int mov_write_minf_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext return update_size(pb, pos); } +static int64_t calc_pts_duration(MOVMuxContext *mov, MOVTrack *track) +{ + if (track->tag == MKTAG('t','m','c','d')) { + // tmcd tracks gets track_duration set in mov_write_moov_tag from + // another track's duration, while the end_pts may be left at zero. + // Calculate the pts duration for that track instead. + return av_rescale(calc_pts_duration(mov, &mov->tracks[track->src_track]), + track->timescale, mov->tracks[track->src_track].timescale); + } + if (track->end_pts != AV_NOPTS_VALUE && + track->start_dts != AV_NOPTS_VALUE && + track->start_cts != AV_NOPTS_VALUE) { + return track->end_pts - (track->start_dts + track->start_cts); + } + return track->track_duration; +} + static int mov_write_mdhd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { - int version = track->track_duration < INT32_MAX ? 0 : 1; + int64_t duration = calc_pts_duration(mov, track); + int version = duration < INT32_MAX ? 0 : 1; if (track->mode == MODE_ISM) version = 1; @@ -2750,7 +2862,7 @@ static int mov_write_mdhd_tag(AVIOContext *pb, MOVMuxContext *mov, else if (!track->entry) (version == 1) ? avio_wb64(pb, 0) : avio_wb32(pb, 0); else - (version == 1) ? avio_wb64(pb, track->track_duration) : avio_wb32(pb, track->track_duration); /* duration */ + (version == 1) ? avio_wb64(pb, duration) : avio_wb32(pb, duration); /* duration */ avio_wb16(pb, track->language); /* language */ avio_wb16(pb, 0); /* reserved (quality) */ @@ -2800,8 +2912,9 @@ static void write_matrix(AVIOContext *pb, int16_t a, int16_t b, int16_t c, static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVStream *st) { - int64_t duration = av_rescale_rnd(track->track_duration, MOV_TIMESCALE, - track->timescale, AV_ROUND_UP); + int64_t duration = av_rescale_rnd(calc_pts_duration(mov, track), + MOV_TIMESCALE, track->timescale, + AV_ROUND_UP); int version = duration < INT32_MAX ? 0 : 1; int flags = MOV_TKHD_FLAG_IN_MOVIE; int rotation = 0; @@ -2947,8 +3060,9 @@ static int mov_write_tapt_tag(AVIOContext *pb, MOVTrack *track) static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { - int64_t duration = av_rescale_rnd(track->track_duration, MOV_TIMESCALE, - track->timescale, AV_ROUND_UP); + int64_t duration = av_rescale_rnd(calc_pts_duration(mov, track), + MOV_TIMESCALE, track->timescale, + AV_ROUND_UP); int version = duration < INT32_MAX ? 0 : 1; int entry_size, entry_count, size; int64_t delay, start_ct = track->start_cts; @@ -3110,12 +3224,12 @@ static int mov_write_track_udta_tag(AVIOContext *pb, MOVMuxContext *mov, if (mov->mode & (MODE_MP4|MODE_MOV)) mov_write_track_metadata(pb_buf, st, "name", "title"); - if ((size = avio_close_dyn_buf(pb_buf, &buf)) > 0) { + if ((size = avio_get_dyn_buf(pb_buf, &buf)) > 0) { avio_wb32(pb, size + 8); ffio_wfourcc(pb, "udta"); avio_write(pb, buf, size); } - av_free(buf); + ffio_free_dyn_buf(&pb_buf); return 0; } @@ -3234,7 +3348,8 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].entry > 0 && mov->tracks[i].timescale) { - int64_t max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration, + int64_t max_track_len_temp = av_rescale_rnd( + calc_pts_duration(mov, &mov->tracks[i]), MOV_TIMESCALE, mov->tracks[i].timescale, AV_ROUND_UP); @@ -3785,12 +3900,12 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov, if (s->nb_chapters && !(mov->flags & FF_MOV_FLAG_DISABLE_CHPL)) mov_write_chpl_tag(pb_buf, s); - if ((size = avio_close_dyn_buf(pb_buf, &buf)) > 0) { + if ((size = avio_get_dyn_buf(pb_buf, &buf)) > 0) { avio_wb32(pb, size + 8); ffio_wfourcc(pb, "udta"); avio_write(pb, buf, size); } - av_free(buf); + ffio_free_dyn_buf(&pb_buf); return 0; } @@ -4174,6 +4289,9 @@ static int mov_write_tfhd_tag(AVIOContext *pb, MOVMuxContext *mov, flags &= ~MOV_TFHD_BASE_DATA_OFFSET; flags |= MOV_TFHD_DEFAULT_BASE_IS_MOOF; } + /* CMAF requires all values to be explicit in tfhd atoms */ + if (mov->flags & FF_MOV_FLAG_CMAF) + flags |= MOV_TFHD_STSD_ID; /* Don't set a default sample size, the silverlight player refuses * to play files with that set. Don't set a default sample duration, @@ -4181,7 +4299,7 @@ static int mov_write_tfhd_tag(AVIOContext *pb, MOVMuxContext *mov, * file format says it MUST NOT be set. */ if (track->mode == MODE_ISM) flags &= ~(MOV_TFHD_DEFAULT_SIZE | MOV_TFHD_DEFAULT_DURATION | - MOV_TFHD_BASE_DATA_OFFSET); + MOV_TFHD_BASE_DATA_OFFSET | MOV_TFHD_STSD_ID); avio_wb32(pb, 0); /* size placeholder */ ffio_wfourcc(pb, "tfhd"); @@ -4191,6 +4309,9 @@ static int mov_write_tfhd_tag(AVIOContext *pb, MOVMuxContext *mov, avio_wb32(pb, track->track_id); /* track-id */ if (flags & MOV_TFHD_BASE_DATA_OFFSET) avio_wb64(pb, moof_offset); + if (flags & MOV_TFHD_STSD_ID) { + avio_wb32(pb, 1); + } if (flags & MOV_TFHD_DEFAULT_DURATION) { track->default_duration = get_cluster_duration(track, 0); avio_wb32(pb, track->default_duration); @@ -4477,7 +4598,8 @@ static int mov_write_sidx_tag(AVIOContext *pb, { int64_t pos = avio_tell(pb), offset_pos, end_pos; int64_t presentation_time, duration, offset; - int starts_with_SAP, i, entries; + unsigned starts_with_SAP; + int i, entries; if (track->entry) { entries = 1; @@ -4572,6 +4694,7 @@ static int mov_write_prft_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks) { int64_t pos = avio_tell(pb), pts_us, ntp_ts; MOVTrack *first_track; + int flags = 24; /* PRFT should be associated with at most one track. So, choosing only the * first track. */ @@ -4590,7 +4713,13 @@ static int mov_write_prft_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks) } if (mov->write_prft == MOV_PRFT_SRC_WALLCLOCK) { - ntp_ts = ff_get_formatted_ntp_time(ff_ntp_time()); + if (first_track->cluster[0].prft.wallclock) { + /* Round the NTP time to whole milliseconds. */ + ntp_ts = ff_get_formatted_ntp_time((first_track->cluster[0].prft.wallclock / 1000) * 1000 + + NTP_OFFSET_US); + flags = first_track->cluster[0].prft.flags; + } else + ntp_ts = ff_get_formatted_ntp_time(ff_ntp_time()); } else if (mov->write_prft == MOV_PRFT_SRC_PTS) { pts_us = av_rescale_q(first_track->cluster[0].pts, first_track->st->time_base, AV_TIME_BASE_Q); @@ -4604,7 +4733,7 @@ static int mov_write_prft_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks) avio_wb32(pb, 0); // Size place holder ffio_wfourcc(pb, "prft"); // Type avio_w8(pb, 1); // Version - avio_wb24(pb, 0); // Flags + avio_wb24(pb, flags); // Flags avio_wb32(pb, first_track->track_id); // reference track ID avio_wb64(pb, ntp_ts); // NTP time stamp avio_wb64(pb, first_track->cluster[0].pts); //media time @@ -4704,27 +4833,11 @@ static int mov_write_mdat_tag(AVIOContext *pb, MOVMuxContext *mov) return 0; } -/* TODO: This needs to be more general */ -static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) +static void mov_write_ftyp_tag_internal(AVIOContext *pb, AVFormatContext *s, + int has_h264, int has_video, int write_minor) { MOVMuxContext *mov = s->priv_data; - int64_t pos = avio_tell(pb); - int has_h264 = 0, has_video = 0; int minor = 0x200; - int i; - - for (i = 0; i < s->nb_streams; i++) { - AVStream *st = s->streams[i]; - if (is_cover_image(st)) - continue; - if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) - has_video = 1; - if (st->codecpar->codec_id == AV_CODEC_ID_H264) - has_h264 = 1; - } - - avio_wb32(pb, 0); /* size */ - ffio_wfourcc(pb, "ftyp"); if (mov->major_brand && strlen(mov->major_brand) >= 4) ffio_wfourcc(pb, mov->major_brand); @@ -4736,6 +4849,9 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) minor = has_h264 ? 0x20000 : 0x10000; } else if (mov->mode == MODE_PSP) ffio_wfourcc(pb, "MSNV"); + else if (mov->mode == MODE_MP4 && mov->flags & FF_MOV_FLAG_FRAGMENT && + mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + ffio_wfourcc(pb, "iso6"); // Required when using signed CTS offsets in trun boxes else if (mov->mode == MODE_MP4 && mov->flags & FF_MOV_FLAG_DEFAULT_BASE_MOOF) ffio_wfourcc(pb, "iso5"); // Required when using default-base-is-moof else if (mov->mode == MODE_MP4 && mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) @@ -4751,31 +4867,68 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) else ffio_wfourcc(pb, "qt "); - avio_wb32(pb, minor); + if (write_minor) + avio_wb32(pb, minor); +} - if (mov->mode == MODE_MOV) - ffio_wfourcc(pb, "qt "); - else if (mov->mode == MODE_ISM) { - ffio_wfourcc(pb, "piff"); - } else if (!(mov->flags & FF_MOV_FLAG_DEFAULT_BASE_MOOF)) { - ffio_wfourcc(pb, "isom"); - ffio_wfourcc(pb, "iso2"); - if (has_h264) - ffio_wfourcc(pb, "avc1"); +static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) +{ + MOVMuxContext *mov = s->priv_data; + int64_t pos = avio_tell(pb); + int has_h264 = 0, has_video = 0; + int i; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (is_cover_image(st)) + continue; + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + has_video = 1; + if (st->codecpar->codec_id == AV_CODEC_ID_H264) + has_h264 = 1; } - // We add tfdt atoms when fragmenting, signal this with the iso6 compatible - // brand. This is compatible with users that don't understand tfdt. - if (mov->flags & FF_MOV_FLAG_FRAGMENT && mov->mode != MODE_ISM) - ffio_wfourcc(pb, "iso6"); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ftyp"); - if (mov->mode == MODE_3GP) - ffio_wfourcc(pb, has_h264 ? "3gp6":"3gp4"); - else if (mov->mode & MODE_3G2) - ffio_wfourcc(pb, has_h264 ? "3g2b":"3g2a"); - else if (mov->mode == MODE_PSP) - ffio_wfourcc(pb, "MSNV"); - else if (mov->mode == MODE_MP4) + // Write major brand + mov_write_ftyp_tag_internal(pb, s, has_h264, has_video, 1); + // Write the major brand as the first compatible brand as well + mov_write_ftyp_tag_internal(pb, s, has_h264, has_video, 0); + + // Write compatible brands, ensuring that we don't write the major brand as a + // compatible brand a second time. + if (mov->mode == MODE_ISM) { + ffio_wfourcc(pb, "piff"); + } else if (mov->mode != MODE_MOV) { + // We add tfdt atoms when fragmenting, signal this with the iso6 compatible + // brand, if not already the major brand. This is compatible with users that + // don't understand tfdt. + if (mov->mode == MODE_MP4) { + if (mov->flags & FF_MOV_FLAG_CMAF) + ffio_wfourcc(pb, "cmfc"); + if (mov->flags & FF_MOV_FLAG_FRAGMENT && !(mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS)) + ffio_wfourcc(pb, "iso6"); + } else { + if (mov->flags & FF_MOV_FLAG_FRAGMENT) + ffio_wfourcc(pb, "iso6"); + if (mov->flags & FF_MOV_FLAG_DEFAULT_BASE_MOOF) + ffio_wfourcc(pb, "iso5"); + else if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + ffio_wfourcc(pb, "iso4"); + } + // Brands prior to iso5 can't be signaled when using default-base-is-moof + if (!(mov->flags & FF_MOV_FLAG_DEFAULT_BASE_MOOF)) { + // write isom for mp4 only if it it's not the major brand already. + if (mov->mode != MODE_MP4 || mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) + ffio_wfourcc(pb, "isom"); + ffio_wfourcc(pb, "iso2"); + if (has_h264) + ffio_wfourcc(pb, "avc1"); + } + } + + if (mov->mode == MODE_MP4) ffio_wfourcc(pb, "mp41"); if (mov->flags & FF_MOV_FLAG_DASH && mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) @@ -4965,6 +5118,25 @@ static void mov_parse_vc1_frame(AVPacket *pkt, MOVTrack *trk) } } +static void mov_parse_truehd_frame(AVPacket *pkt, MOVTrack *trk) +{ + int length; + + if (pkt->size < 8) + return; + + length = (AV_RB16(pkt->data) & 0xFFF) * 2; + if (length < 8 || length > pkt->size) + return; + + if (AV_RB32(pkt->data + 4) == 0xF8726FBA) { + trk->cluster[trk->entry].flags |= MOV_SYNC_SAMPLE; + trk->has_keyframes++; + } + + return; +} + static int mov_flush_fragment_interleaving(AVFormatContext *s, MOVTrack *track) { MOVMuxContext *mov = s->priv_data; @@ -4978,12 +5150,11 @@ static int mov_flush_fragment_interleaving(AVFormatContext *s, MOVTrack *track) if ((ret = avio_open_dyn_buf(&mov->mdat_buf)) < 0) return ret; } - buf_size = avio_close_dyn_buf(track->mdat_buf, &buf); - track->mdat_buf = NULL; + buf_size = avio_get_dyn_buf(track->mdat_buf, &buf); offset = avio_tell(mov->mdat_buf); avio_write(mov->mdat_buf, buf, buf_size); - av_free(buf); + ffio_free_dyn_buf(&track->mdat_buf); for (i = track->entries_flushed; i < track->entry; i++) track->cluster[i].pos += offset; @@ -5075,17 +5246,16 @@ static int mov_flush_fragment(AVFormatContext *s, int force) if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) { if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) mov->reserved_header_pos = avio_tell(s->pb); - avio_flush(s->pb); + avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); mov->moov_written = 1; return 0; } - buf_size = avio_close_dyn_buf(mov->mdat_buf, &buf); - mov->mdat_buf = NULL; + buf_size = avio_get_dyn_buf(mov->mdat_buf, &buf); avio_wb32(s->pb, buf_size + 8); ffio_wfourcc(s->pb, "mdat"); avio_write(s->pb, buf, buf_size); - av_free(buf); + ffio_free_dyn_buf(&mov->mdat_buf); if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) mov->reserved_header_pos = avio_tell(s->pb); @@ -5100,7 +5270,7 @@ static int mov_flush_fragment(AVFormatContext *s, int force) mov->tracks[i].entry = 0; mov->tracks[i].end_reliable = 0; } - avio_flush(s->pb); + avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); return 0; } @@ -5165,7 +5335,7 @@ static int mov_flush_fragment(AVFormatContext *s, int force) } if (write_moof) { - avio_flush(s->pb); + avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); mov_write_moof_tag(s->pb, mov, moof_tracks, mdat_size); mov->fragments++; @@ -5197,7 +5367,7 @@ static int mov_flush_fragment(AVFormatContext *s, int force) mov->mdat_size = 0; - avio_flush(s->pb); + avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); return 0; } @@ -5259,8 +5429,10 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) AVIOContext *pb = s->pb; MOVTrack *trk = &mov->tracks[pkt->stream_index]; AVCodecParameters *par = trk->par; + AVProducerReferenceTime *prft; unsigned int samples_in_chunk = 0; - int size = pkt->size, ret = 0; + int size = pkt->size, ret = 0, offset = 0; + int prft_size; uint8_t *reformatted_data = NULL; ret = check_pkt(s, pkt); @@ -5323,12 +5495,30 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) !TAG_IS_AVCI(trk->tag) && (par->codec_id != AV_CODEC_ID_DNXHD)) { trk->vos_len = par->extradata_size; - trk->vos_data = av_malloc(trk->vos_len); + trk->vos_data = av_malloc(trk->vos_len + AV_INPUT_BUFFER_PADDING_SIZE); if (!trk->vos_data) { ret = AVERROR(ENOMEM); goto err; } memcpy(trk->vos_data, par->extradata, trk->vos_len); + memset(trk->vos_data + trk->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE); + } + + if ((par->codec_id == AV_CODEC_ID_DNXHD || + par->codec_id == AV_CODEC_ID_H264 || + par->codec_id == AV_CODEC_ID_HEVC || + par->codec_id == AV_CODEC_ID_TRUEHD || + par->codec_id == AV_CODEC_ID_AC3) && !trk->vos_len && + !TAG_IS_AVCI(trk->tag)) { + /* copy frame to create needed atoms */ + trk->vos_len = size; + trk->vos_data = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!trk->vos_data) { + ret = AVERROR(ENOMEM); + goto err; + } + memcpy(trk->vos_data, pkt->data, size); + memset(trk->vos_data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); } if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && @@ -5345,8 +5535,10 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) /* from x264 or from bytestream H.264 */ /* NAL reformatting needed */ if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { - ff_avc_parse_nal_units_buf(pkt->data, &reformatted_data, - &size); + ret = ff_avc_parse_nal_units_buf(pkt->data, &reformatted_data, + &size); + if (ret < 0) + return ret; avio_write(pb, reformatted_data, size); } else { if (trk->cenc.aes_ctr) { @@ -5363,14 +5555,20 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) (AV_RB24(trk->vos_data) == 1 || AV_RB32(trk->vos_data) == 1)) { /* extradata is Annex B, assume the bitstream is too and convert it */ if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { - ff_hevc_annexb2mp4_buf(pkt->data, &reformatted_data, &size, 0, NULL); + ret = ff_hevc_annexb2mp4_buf(pkt->data, &reformatted_data, + &size, 0, NULL); + if (ret < 0) + return ret; avio_write(pb, reformatted_data, size); } else { size = ff_hevc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL); } } else if (par->codec_id == AV_CODEC_ID_AV1) { if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { - ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size); + ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, + &size, &offset); + if (ret < 0) + return ret; avio_write(pb, reformatted_data, size); } else { size = ff_av1_filter_obus(pb, pkt->data, pkt->size); @@ -5401,20 +5599,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } - if ((par->codec_id == AV_CODEC_ID_DNXHD || - par->codec_id == AV_CODEC_ID_AC3) && !trk->vos_len) { - /* copy frame to create needed atoms */ - trk->vos_len = size; - trk->vos_data = av_malloc(size); - if (!trk->vos_data) { - ret = AVERROR(ENOMEM); - goto err; - } - memcpy(trk->vos_data, pkt->data, size); - } - if (trk->entry >= trk->cluster_capacity) { - unsigned new_capacity = 2 * (trk->entry + MOV_INDEX_CLUSTER_SIZE); + unsigned new_capacity = trk->entry + MOV_INDEX_CLUSTER_SIZE; if (av_reallocp_array(&trk->cluster, new_capacity, sizeof(*trk->cluster))) { ret = AVERROR(ENOMEM); @@ -5511,6 +5697,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) if (par->codec_id == AV_CODEC_ID_VC1) { mov_parse_vc1_frame(pkt, trk); + } else if (par->codec_id == AV_CODEC_ID_TRUEHD) { + mov_parse_truehd_frame(pkt, trk); } else if (pkt->flags & AV_PKT_FLAG_KEY) { if (mov->mode == MODE_MOV && par->codec_id == AV_CODEC_ID_MPEG2VIDEO && trk->entry > 0) { // force sync sample for the first key frame @@ -5527,104 +5715,113 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) trk->cluster[trk->entry].flags |= MOV_DISPOSABLE_SAMPLE; trk->has_disposable++; } + + prft = (AVProducerReferenceTime *)av_packet_get_side_data(pkt, AV_PKT_DATA_PRFT, &prft_size); + if (prft && prft_size == sizeof(AVProducerReferenceTime)) + memcpy(&trk->cluster[trk->entry].prft, prft, prft_size); + else + memset(&trk->cluster[trk->entry].prft, 0, sizeof(AVProducerReferenceTime)); + trk->entry++; trk->sample_count += samples_in_chunk; mov->mdat_size += size; if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) ff_mov_add_hinted_packet(s, pkt, trk->hint_track, trk->entry, - reformatted_data, size); + reformatted_data ? reformatted_data + offset + : NULL, size); end: err: - av_free(reformatted_data); + if (pkt->data != reformatted_data) + av_free(reformatted_data); return ret; } static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt) { - MOVMuxContext *mov = s->priv_data; - MOVTrack *trk = &mov->tracks[pkt->stream_index]; - AVCodecParameters *par = trk->par; - int64_t frag_duration = 0; - int size = pkt->size; + MOVMuxContext *mov = s->priv_data; + MOVTrack *trk = &mov->tracks[pkt->stream_index]; + AVCodecParameters *par = trk->par; + int64_t frag_duration = 0; + int size = pkt->size; - int ret = check_pkt(s, pkt); - if (ret < 0) - return ret; + int ret = check_pkt(s, pkt); + if (ret < 0) + return ret; - if (mov->flags & FF_MOV_FLAG_FRAG_DISCONT) { - int i; - for (i = 0; i < s->nb_streams; i++) - mov->tracks[i].frag_discont = 1; - mov->flags &= ~FF_MOV_FLAG_FRAG_DISCONT; - } + if (mov->flags & FF_MOV_FLAG_FRAG_DISCONT) { + int i; + for (i = 0; i < s->nb_streams; i++) + mov->tracks[i].frag_discont = 1; + mov->flags &= ~FF_MOV_FLAG_FRAG_DISCONT; + } - if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) { - if (trk->dts_shift == AV_NOPTS_VALUE) - trk->dts_shift = pkt->pts - pkt->dts; - pkt->dts += trk->dts_shift; - } + if (mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS) { + if (trk->dts_shift == AV_NOPTS_VALUE) + trk->dts_shift = pkt->pts - pkt->dts; + pkt->dts += trk->dts_shift; + } - if (trk->par->codec_id == AV_CODEC_ID_MP4ALS || + if (trk->par->codec_id == AV_CODEC_ID_MP4ALS || trk->par->codec_id == AV_CODEC_ID_AAC || trk->par->codec_id == AV_CODEC_ID_AV1 || trk->par->codec_id == AV_CODEC_ID_FLAC) { - int side_size = 0; - uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); - if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { - void *newextra = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE); - if (!newextra) - return AVERROR(ENOMEM); - av_free(par->extradata); - par->extradata = newextra; - memcpy(par->extradata, side, side_size); - par->extradata_size = side_size; - if (!pkt->size) // Flush packet - mov->need_rewrite_extradata = 1; - } + int side_size = 0; + uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); + if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { + void *newextra = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!newextra) + return AVERROR(ENOMEM); + av_free(par->extradata); + par->extradata = newextra; + memcpy(par->extradata, side, side_size); + par->extradata_size = side_size; + if (!pkt->size) // Flush packet + mov->need_rewrite_extradata = 1; } + } - if (!pkt->size) { - if (trk->start_dts == AV_NOPTS_VALUE && trk->frag_discont) { - trk->start_dts = pkt->dts; - if (pkt->pts != AV_NOPTS_VALUE) - trk->start_cts = pkt->pts - pkt->dts; - else - trk->start_cts = 0; - } - - return 0; /* Discard 0 sized packets */ + if (!pkt->size) { + if (trk->start_dts == AV_NOPTS_VALUE && trk->frag_discont) { + trk->start_dts = pkt->dts; + if (pkt->pts != AV_NOPTS_VALUE) + trk->start_cts = pkt->pts - pkt->dts; + else + trk->start_cts = 0; } - if (trk->entry && pkt->stream_index < s->nb_streams) - frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts, - s->streams[pkt->stream_index]->time_base, - AV_TIME_BASE_Q); - if ((mov->max_fragment_duration && - frag_duration >= mov->max_fragment_duration) || - (mov->max_fragment_size && mov->mdat_size + size >= mov->max_fragment_size) || - (mov->flags & FF_MOV_FLAG_FRAG_KEYFRAME && - par->codec_type == AVMEDIA_TYPE_VIDEO && - trk->entry && pkt->flags & AV_PKT_FLAG_KEY) || - (mov->flags & FF_MOV_FLAG_FRAG_EVERY_FRAME)) { - if (frag_duration >= mov->min_fragment_duration) { - // Set the duration of this track to line up with the next - // sample in this track. This avoids relying on AVPacket - // duration, but only helps for this particular track, not - // for the other ones that are flushed at the same time. - trk->track_duration = pkt->dts - trk->start_dts; - if (pkt->pts != AV_NOPTS_VALUE) - trk->end_pts = pkt->pts; - else - trk->end_pts = pkt->dts; - trk->end_reliable = 1; - mov_auto_flush_fragment(s, 0); - } + return 0; /* Discard 0 sized packets */ + } + + if (trk->entry && pkt->stream_index < s->nb_streams) + frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts, + s->streams[pkt->stream_index]->time_base, + AV_TIME_BASE_Q); + if ((mov->max_fragment_duration && + frag_duration >= mov->max_fragment_duration) || + (mov->max_fragment_size && mov->mdat_size + size >= mov->max_fragment_size) || + (mov->flags & FF_MOV_FLAG_FRAG_KEYFRAME && + par->codec_type == AVMEDIA_TYPE_VIDEO && + trk->entry && pkt->flags & AV_PKT_FLAG_KEY) || + (mov->flags & FF_MOV_FLAG_FRAG_EVERY_FRAME)) { + if (frag_duration >= mov->min_fragment_duration) { + // Set the duration of this track to line up with the next + // sample in this track. This avoids relying on AVPacket + // duration, but only helps for this particular track, not + // for the other ones that are flushed at the same time. + trk->track_duration = pkt->dts - trk->start_dts; + if (pkt->pts != AV_NOPTS_VALUE) + trk->end_pts = pkt->pts; + else + trk->end_pts = pkt->dts; + trk->end_reliable = 1; + mov_auto_flush_fragment(s, 0); } + } - return ff_mov_write_packet(s, pkt); + return ff_mov_write_packet(s, pkt); } static int mov_write_subtitle_end_packet(AVFormatContext *s, @@ -6033,12 +6230,13 @@ static int mov_create_dvd_sub_decoder_specific_info(MOVTrack *track, cur += strspn(cur, "\n\r"); } if (have_palette) { - track->vos_data = av_malloc(16*4); + track->vos_data = av_malloc(16*4 + AV_INPUT_BUFFER_PADDING_SIZE); if (!track->vos_data) return AVERROR(ENOMEM); for (i = 0; i < 16; i++) { AV_WB32(track->vos_data + i * 4, palette[i]); } + memset(track->vos_data + 16*4, 0, AV_INPUT_BUFFER_PADDING_SIZE); track->vos_len = 16 * 4; } st->codecpar->width = width; @@ -6087,6 +6285,9 @@ static int mov_init(AVFormatContext *s) if (mov->flags & FF_MOV_FLAG_DASH) mov->flags |= FF_MOV_FLAG_FRAGMENT | FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_DEFAULT_BASE_MOOF; + if (mov->flags & FF_MOV_FLAG_CMAF) + mov->flags |= FF_MOV_FLAG_FRAGMENT | FF_MOV_FLAG_EMPTY_MOOV | + FF_MOV_FLAG_DEFAULT_BASE_MOOF | FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS; if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && s->flags & AVFMT_FLAG_AUTO_BSF) { av_log(s, AV_LOG_VERBOSE, "Empty MOOV enabled; disabling automatic bitstream filtering\n"); @@ -6113,12 +6314,21 @@ static int mov_init(AVFormatContext *s) s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) mov->use_editlist = 0; } + if (mov->flags & FF_MOV_FLAG_CMAF) { + // CMAF Track requires negative cts offsets without edit lists + mov->use_editlist = 0; + } } if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && !(mov->flags & FF_MOV_FLAG_DELAY_MOOV) && mov->use_editlist) av_log(s, AV_LOG_WARNING, "No meaningful edit list will be written when using empty_moov without delay_moov\n"); - if (!mov->use_editlist && s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO) + if (mov->flags & FF_MOV_FLAG_CMAF && mov->use_editlist) { + av_log(s, AV_LOG_WARNING, "Edit list enabled; Assuming writing CMAF Track File\n"); + mov->flags &= ~FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS; + } + if (!mov->use_editlist && s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO && + !(mov->flags & FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS)) s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_ZERO; /* Clear the omit_tfhd_offset flag if default_base_moof is set; @@ -6221,7 +6431,7 @@ static int mov_init(AVFormatContext *s) track->par = st->codecpar; track->language = ff_mov_iso639_to_lang(lang?lang->value:"und", mov->mode!=MODE_MOV); if (track->language < 0) - track->language = 0; + track->language = 32767; // Unspecified Macintosh language code track->mode = mov->mode; track->tag = mov_find_codec_tag(s, track); if (!track->tag) { @@ -6249,6 +6459,8 @@ static int mov_init(AVFormatContext *s) } if (mov->video_track_timescale) { track->timescale = mov->video_track_timescale; + if (mov->mode == MODE_ISM && mov->video_track_timescale != 10000000) + av_log(s, AV_LOG_WARNING, "Warning: some tools, like mp4split, assume a timescale of 10000000 for ISMV.\n"); } else { track->timescale = st->time_base.den; while(track->timescale < 10000) @@ -6323,12 +6535,14 @@ static int mov_init(AVFormatContext *s) } } if (track->par->codec_id == AV_CODEC_ID_FLAC || + track->par->codec_id == AV_CODEC_ID_TRUEHD || track->par->codec_id == AV_CODEC_ID_OPUS) { if (track->mode != MODE_MP4) { av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id)); return AVERROR(EINVAL); } - if (s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { + if (track->par->codec_id != AV_CODEC_ID_OPUS && + s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) { av_log(s, AV_LOG_ERROR, "%s in MP4 support is experimental, add " "'-strict %d' if you want to use it.\n", @@ -6345,10 +6559,14 @@ static int mov_init(AVFormatContext *s) } if (!track->height) track->height = st->codecpar->height; - /* The ism specific timescale isn't mandatory, but is assumed by - * some tools, such as mp4split. */ - if (mov->mode == MODE_ISM) - track->timescale = 10000000; + /* The Protected Interoperable File Format (PIFF) standard, used by ISMV recommends but + doesn't mandate a track timescale of 10,000,000. The muxer allows a custom timescale + for video tracks, so if user-set, it isn't overwritten */ + if (mov->mode == MODE_ISM && + (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO || + (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !mov->video_track_timescale))) { + track->timescale = 10000000; + } avpriv_set_pts_info(st, 64, 1, track->timescale); @@ -6381,7 +6599,7 @@ static int mov_write_header(AVFormatContext *s) nb_tracks++; } - if (mov->mode == MODE_MOV || mov->mode == MODE_MP4) + if (mov->nb_meta_tmcd) tmcd_track = nb_tracks; for (i = 0; i < s->nb_streams; i++) { @@ -6395,11 +6613,12 @@ static int mov_write_header(AVFormatContext *s) mov_create_dvd_sub_decoder_specific_info(track, st); else if (!TAG_IS_AVCI(track->tag) && st->codecpar->codec_id != AV_CODEC_ID_DNXHD) { track->vos_len = st->codecpar->extradata_size; - track->vos_data = av_malloc(track->vos_len); + track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE); if (!track->vos_data) { return AVERROR(ENOMEM); } memcpy(track->vos_data, st->codecpar->extradata, track->vos_len); + memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE); } } @@ -6495,7 +6714,6 @@ static int mov_write_header(AVFormatContext *s) !(mov->flags & FF_MOV_FLAG_DELAY_MOOV)) { if ((ret = mov_write_moov_tag(pb, mov, s)) < 0) return ret; - avio_flush(pb); mov->moov_written = 1; if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) mov->reserved_header_pos = avio_tell(pb); @@ -6579,7 +6797,7 @@ static int shift_data(AVFormatContext *s) { int ret = 0, moov_size; MOVMuxContext *mov = s->priv_data; - int64_t pos, pos_end = avio_tell(s->pb); + int64_t pos, pos_end; uint8_t *buf, *read_buf[2]; int read_buf_id = 0; int read_size[2]; @@ -6655,10 +6873,11 @@ static int mov_write_trailer(AVFormatContext *s) AVCodecParameters *par = track->par; track->vos_len = par->extradata_size; - track->vos_data = av_malloc(track->vos_len); + track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE); if (!track->vos_data) return AVERROR(ENOMEM); memcpy(track->vos_data, par->extradata, track->vos_len); + memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE); } mov->need_rewrite_extradata = 0; } @@ -6745,9 +6964,8 @@ static int mov_write_trailer(AVFormatContext *s) avio_seek(pb, mov->reserved_header_pos, SEEK_SET); mov_write_sidx_tags(pb, mov, -1, 0); avio_seek(pb, end, SEEK_SET); - avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); - mov_write_mfra_tag(pb, mov); - } else if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) { + } + if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) { avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); mov_write_mfra_tag(pb, mov); } @@ -6783,37 +7001,39 @@ static const AVCodecTag codec_3gp_tags[] = { }; const AVCodecTag codec_mp4_tags[] = { - { AV_CODEC_ID_MPEG4 , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_H264 , MKTAG('a', 'v', 'c', '1') }, - { AV_CODEC_ID_H264 , MKTAG('a', 'v', 'c', '3') }, - { AV_CODEC_ID_HEVC , MKTAG('h', 'e', 'v', '1') }, - { AV_CODEC_ID_HEVC , MKTAG('h', 'v', 'c', '1') }, - { AV_CODEC_ID_MPEG2VIDEO , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_MPEG1VIDEO , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_MJPEG , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_PNG , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_JPEG2000 , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_VC1 , MKTAG('v', 'c', '-', '1') }, - { AV_CODEC_ID_DIRAC , MKTAG('d', 'r', 'a', 'c') }, - { AV_CODEC_ID_TSCC2 , MKTAG('m', 'p', '4', 'v') }, - { AV_CODEC_ID_VP9 , MKTAG('v', 'p', '0', '9') }, - { AV_CODEC_ID_AV1 , MKTAG('a', 'v', '0', '1') }, - { AV_CODEC_ID_AAC , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_MP4ALS , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_MP3 , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_MP2 , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_AC3 , MKTAG('a', 'c', '-', '3') }, - { AV_CODEC_ID_EAC3 , MKTAG('e', 'c', '-', '3') }, - { AV_CODEC_ID_DTS , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_FLAC , MKTAG('f', 'L', 'a', 'C') }, - { AV_CODEC_ID_OPUS , MKTAG('O', 'p', 'u', 's') }, - { AV_CODEC_ID_VORBIS , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_QCELP , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_EVRC , MKTAG('m', 'p', '4', 'a') }, - { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') }, - { AV_CODEC_ID_MOV_TEXT , MKTAG('t', 'x', '3', 'g') }, - { AV_CODEC_ID_BIN_DATA , MKTAG('g', 'p', 'm', 'd') }, - { AV_CODEC_ID_NONE , 0 }, + { AV_CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '1') }, + { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '3') }, + { AV_CODEC_ID_HEVC, MKTAG('h', 'e', 'v', '1') }, + { AV_CODEC_ID_HEVC, MKTAG('h', 'v', 'c', '1') }, + { AV_CODEC_ID_MPEG2VIDEO, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_MJPEG, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_PNG, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_JPEG2000, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_VC1, MKTAG('v', 'c', '-', '1') }, + { AV_CODEC_ID_DIRAC, MKTAG('d', 'r', 'a', 'c') }, + { AV_CODEC_ID_TSCC2, MKTAG('m', 'p', '4', 'v') }, + { AV_CODEC_ID_VP9, MKTAG('v', 'p', '0', '9') }, + { AV_CODEC_ID_AV1, MKTAG('a', 'v', '0', '1') }, + { AV_CODEC_ID_AAC, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP4ALS, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP3, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_MP2, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_AC3, MKTAG('a', 'c', '-', '3') }, + { AV_CODEC_ID_EAC3, MKTAG('e', 'c', '-', '3') }, + { AV_CODEC_ID_DTS, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_TRUEHD, MKTAG('m', 'l', 'p', 'a') }, + { AV_CODEC_ID_FLAC, MKTAG('f', 'L', 'a', 'C') }, + { AV_CODEC_ID_OPUS, MKTAG('O', 'p', 'u', 's') }, + { AV_CODEC_ID_VORBIS, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_QCELP, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_EVRC, MKTAG('m', 'p', '4', 'a') }, + { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') }, + { AV_CODEC_ID_MOV_TEXT, MKTAG('t', 'x', '3', 'g') }, + { AV_CODEC_ID_BIN_DATA, MKTAG('g', 'p', 'm', 'd') }, + { AV_CODEC_ID_MPEGH_3D_AUDIO, MKTAG('m', 'h', 'm', '1') }, + { AV_CODEC_ID_NONE, 0 }, }; const AVCodecTag codec_ism_tags[] = {