| @@ -0,0 +1,285 @@ |
| | 1 | /* |
| | 2 | * This file is part of FFmpeg. |
| | 3 | * |
| | 4 | * FFmpeg is free software; you can redistribute it and/or |
| | 5 | * modify it under the terms of the GNU Lesser General Public |
| | 6 | * License as published by the Free Software Foundation; either |
| | 7 | * version 2.1 of the License, or (at your option) any later version. |
| | 8 | * |
| | 9 | * FFmpeg is distributed in the hope that it will be useful, |
| | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 12 | * Lesser General Public License for more details. |
| | 13 | * |
| | 14 | * You should have received a copy of the GNU Lesser General Public |
| | 15 | * License along with FFmpeg; if not, write to the Free Software |
| | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 17 | */ |
| | 18 | |
| | 19 | #include "libavutil/common.h" |
| | 20 | #include "libavutil/mem.h" |
| | 21 | #include "libavutil/opt.h" |
| | 22 | |
| | 23 | #include "bsf.h" |
| | 24 | #include "bsf_internal.h" |
| | 25 | #include "cbs.h" |
| | 26 | #include "cbs_bsf.h" |
| | 27 | #include "cbs_av1.h" |
| | 28 | #include "cbs_h265.h" |
| | 29 | #include "dovi_rpu.h" |
| | 30 | #include "h2645data.h" |
| | 31 | #include "h265_profile_level.h" |
| | 32 | #include "itut35.h" |
| | 33 | |
| | 34 | #include "hevc/hevc.h" |
| | 35 | |
| | 36 | typedef struct DoviRpuContext { |
| | 37 | CBSBSFContext common; |
| | 38 | DOVIContext dec; |
| | 39 | DOVIContext enc; |
| | 40 | |
| | 41 | int strip; |
| | 42 | int compression; |
| | 43 | } DoviRpuContext; |
| | 44 | |
| | 45 | static int update_rpu(AVBSFContext *bsf, const AVPacket *pkt, int flags, |
| | 46 | const uint8_t *rpu, size_t rpu_size, |
| | 47 | uint8_t **out_rpu, int *out_size) |
| | 48 | { |
| | 49 | DoviRpuContext *s = bsf->priv_data; |
| | 50 | AVDOVIMetadata *metadata = NULL; |
| | 51 | int ret; |
| | 52 | |
| | 53 | ret = ff_dovi_rpu_parse(&s->dec, rpu, rpu_size, 0); |
| | 54 | if (ret < 0) { |
| | 55 | ff_dovi_ctx_flush(&s->dec); |
| | 56 | return ret; |
| | 57 | } |
| | 58 | |
| | 59 | ret = ff_dovi_get_metadata(&s->dec, &metadata); |
| | 60 | if (ret == 0 /* no metadata */) { |
| | 61 | *out_rpu = NULL; |
| | 62 | *out_size = 0; |
| | 63 | return 0; |
| | 64 | } else if (ret < 0) { |
| | 65 | ff_dovi_ctx_flush(&s->dec); |
| | 66 | return ret; |
| | 67 | } |
| | 68 | |
| | 69 | if (pkt && !(pkt->flags & AV_PKT_FLAG_KEY)) |
| | 70 | flags |= FF_DOVI_COMPRESS_RPU; |
| | 71 | ret = ff_dovi_rpu_generate(&s->enc, metadata, flags, out_rpu, out_size); |
| | 72 | av_free(metadata); |
| | 73 | if (ret < 0) |
| | 74 | ff_dovi_ctx_flush(&s->enc); |
| | 75 | |
| | 76 | return ret; |
| | 77 | } |
| | 78 | |
| | 79 | static int dovi_rpu_update_fragment_hevc(AVBSFContext *bsf, AVPacket *pkt, |
| | 80 | CodedBitstreamFragment *au) |
| | 81 | { |
| | 82 | DoviRpuContext *s = bsf->priv_data; |
| | 83 | CodedBitstreamUnit *nal = au->nb_units ? &au->units[au->nb_units - 1] : NULL; |
| | 84 | uint8_t *rpu = NULL; |
| | 85 | int rpu_size, ret; |
| | 86 | |
| | 87 | if (!nal || nal->type != HEVC_NAL_UNSPEC62) |
| | 88 | return 0; |
| | 89 | |
| | 90 | if (s->strip) { |
| | 91 | ff_cbs_delete_unit(au, au->nb_units - 1); |
| | 92 | return 0; |
| | 93 | } |
| | 94 | |
| | 95 | ret = update_rpu(bsf, pkt, 0, nal->data + 2, nal->data_size - 2, &rpu, &rpu_size); |
| | 96 | if (ret < 0) |
| | 97 | return ret; |
| | 98 | |
| | 99 | /* NAL unit header + NAL prefix */ |
| | 100 | if (rpu_size + 3 <= nal->data_size && av_buffer_is_writable(nal->data_ref)) { |
| | 101 | memcpy(nal->data + 3, rpu, rpu_size); |
| | 102 | av_free(rpu); |
| | 103 | nal->data_size = rpu_size + 3; |
| | 104 | } else { |
| | 105 | AVBufferRef *ref = av_buffer_alloc(rpu_size + 3); |
| | 106 | if (!ref) { |
| | 107 | av_free(rpu); |
| | 108 | return AVERROR(ENOMEM); |
| | 109 | } |
| | 110 | |
| | 111 | memcpy(ref->data, nal->data, 3); |
| | 112 | memcpy(ref->data + 3, rpu, rpu_size); |
| | 113 | av_buffer_unref(&nal->data_ref); |
| | 114 | av_free(rpu); |
| | 115 | nal->data = ref->data; |
| | 116 | nal->data_size = rpu_size + 3; |
| | 117 | nal->data_ref = ref; |
| | 118 | nal->data_bit_padding = 0; |
| | 119 | } |
| | 120 | |
| | 121 | return 0; |
| | 122 | } |
| | 123 | |
| | 124 | static int dovi_rpu_update_fragment_av1(AVBSFContext *bsf, AVPacket *pkt, |
| | 125 | CodedBitstreamFragment *frag) |
| | 126 | { |
| | 127 | DoviRpuContext *s = bsf->priv_data; |
| | 128 | int provider_code, provider_oriented_code, rpu_size, ret; |
| | 129 | AVBufferRef *ref; |
| | 130 | uint8_t *rpu; |
| | 131 | |
| | 132 | for (int i = 0; i < frag->nb_units; i++) { |
| | 133 | AV1RawOBU *obu = frag->units[i].content; |
| | 134 | AV1RawMetadataITUTT35 *t35 = &obu->obu.metadata.metadata.itut_t35; |
| | 135 | if (frag->units[i].type != AV1_OBU_METADATA || |
| | 136 | obu->obu.metadata.metadata_type != AV1_METADATA_TYPE_ITUT_T35 || |
| | 137 | t35->itu_t_t35_country_code != ITU_T_T35_COUNTRY_CODE_US || |
| | 138 | t35->payload_size < 6) |
| | 139 | continue; |
| | 140 | |
| | 141 | provider_code = AV_RB16(t35->payload); |
| | 142 | provider_oriented_code = AV_RB32(t35->payload + 2); |
| | 143 | if (provider_code != ITU_T_T35_PROVIDER_CODE_DOLBY || |
| | 144 | provider_oriented_code != 0x800) |
| | 145 | continue; |
| | 146 | |
| | 147 | if (s->strip) { |
| | 148 | ff_cbs_delete_unit(frag, i); |
| | 149 | return 0; |
| | 150 | } |
| | 151 | |
| | 152 | ret = update_rpu(bsf, pkt, FF_DOVI_WRAP_T35, |
| | 153 | t35->payload + 6, t35->payload_size - 6, |
| | 154 | &rpu, &rpu_size); |
| | 155 | if (ret < 0) |
| | 156 | return ret; |
| | 157 | |
| | 158 | ref = av_buffer_create(rpu, rpu_size, av_buffer_default_free, NULL, 0); |
| | 159 | if (!ref) { |
| | 160 | av_free(rpu); |
| | 161 | return AVERROR(ENOMEM); |
| | 162 | } |
| | 163 | |
| | 164 | av_buffer_unref(&t35->payload_ref); |
| | 165 | t35->payload_ref = ref; |
| | 166 | t35->payload = rpu + 1; /* skip country code */ |
| | 167 | t35->payload_size = rpu_size - 1; |
| | 168 | break; /* should be only one RPU per packet */ |
| | 169 | } |
| | 170 | |
| | 171 | return 0; |
| | 172 | } |
| | 173 | |
| | 174 | static const CBSBSFType dovi_rpu_hevc_type = { |
| | 175 | .codec_id = AV_CODEC_ID_HEVC, |
| | 176 | .fragment_name = "access unit", |
| | 177 | .unit_name = "NAL unit", |
| | 178 | .update_fragment = &dovi_rpu_update_fragment_hevc, |
| | 179 | }; |
| | 180 | |
| | 181 | static const CBSBSFType dovi_rpu_av1_type = { |
| | 182 | .codec_id = AV_CODEC_ID_AV1, |
| | 183 | .fragment_name = "temporal unit", |
| | 184 | .unit_name = "OBU", |
| | 185 | .update_fragment = &dovi_rpu_update_fragment_av1, |
| | 186 | }; |
| | 187 | |
| | 188 | static int dovi_rpu_init(AVBSFContext *bsf) |
| | 189 | { |
| | 190 | int ret; |
| | 191 | DoviRpuContext *s = bsf->priv_data; |
| | 192 | s->dec.logctx = s->enc.logctx = bsf; |
| | 193 | s->enc.enable = 1; |
| | 194 | |
| | 195 | if (s->compression == AV_DOVI_COMPRESSION_RESERVED) { |
| | 196 | av_log(bsf, AV_LOG_ERROR, "Invalid compression level: %d\n", s->compression); |
| | 197 | return AVERROR(EINVAL); |
| | 198 | } |
| | 199 | |
| | 200 | if (s->strip) { |
| | 201 | av_packet_side_data_remove(bsf->par_out->coded_side_data, |
| | 202 | &bsf->par_out->nb_coded_side_data, |
| | 203 | AV_PKT_DATA_DOVI_CONF); |
| | 204 | } else { |
| | 205 | const AVPacketSideData *sd; |
| | 206 | sd = av_packet_side_data_get(bsf->par_out->coded_side_data, |
| | 207 | bsf->par_out->nb_coded_side_data, |
| | 208 | AV_PKT_DATA_DOVI_CONF); |
| | 209 | |
| | 210 | if (sd) { |
| | 211 | AVDOVIDecoderConfigurationRecord *cfg; |
| | 212 | cfg = (AVDOVIDecoderConfigurationRecord *) sd->data; |
| | 213 | s->dec.cfg = *cfg; |
| | 214 | |
| | 215 | /* Update configuration record before setting to enc ctx */ |
| | 216 | cfg->dv_md_compression = s->compression; |
| | 217 | if (s->compression && s->dec.cfg.dv_profile < 8) { |
| | 218 | av_log(bsf, AV_LOG_ERROR, "Invalid compression level %d for " |
| | 219 | "Dolby Vision profile %d.\n", s->compression, s->dec.cfg.dv_profile); |
| | 220 | return AVERROR(EINVAL); |
| | 221 | } |
| | 222 | |
| | 223 | s->enc.cfg = *cfg; |
| | 224 | } else { |
| | 225 | av_log(bsf, AV_LOG_WARNING, "No Dolby Vision configuration record " |
| | 226 | "found? Generating one, but results may be invalid.\n"); |
| | 227 | ret = ff_dovi_configure_ext(&s->enc, bsf->par_out, NULL, s->compression, |
| | 228 | FF_COMPLIANCE_NORMAL); |
| | 229 | if (ret < 0) |
| | 230 | return ret; |
| | 231 | /* Be conservative in accepting all compressed RPUs */ |
| | 232 | s->dec.cfg = s->enc.cfg; |
| | 233 | s->dec.cfg.dv_md_compression = AV_DOVI_COMPRESSION_EXTENDED; |
| | 234 | } |
| | 235 | } |
| | 236 | |
| | 237 | switch (bsf->par_in->codec_id) { |
| | 238 | case AV_CODEC_ID_HEVC: |
| | 239 | return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_hevc_type); |
| | 240 | case AV_CODEC_ID_AV1: |
| | 241 | return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_av1_type); |
| | 242 | default: |
| | 243 | return AVERROR_BUG; |
| | 244 | } |
| | 245 | } |
| | 246 | |
| | 247 | static void dovi_rpu_close(AVBSFContext *bsf) |
| | 248 | { |
| | 249 | DoviRpuContext *s = bsf->priv_data; |
| | 250 | ff_dovi_ctx_unref(&s->dec); |
| | 251 | ff_dovi_ctx_unref(&s->enc); |
| | 252 | ff_cbs_bsf_generic_close(bsf); |
| | 253 | } |
| | 254 | |
| | 255 | #define OFFSET(x) offsetof(DoviRpuContext, x) |
| | 256 | #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM) |
| | 257 | static const AVOption dovi_rpu_options[] = { |
| | 258 | { "strip", "Strip Dolby Vision metadata", OFFSET(strip), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, |
| | 259 | { "compression", "DV metadata compression mode", OFFSET(compression), AV_OPT_TYPE_INT, { .i64 = AV_DOVI_COMPRESSION_LIMITED }, 0, AV_DOVI_COMPRESSION_EXTENDED, FLAGS, .unit = "compression" }, |
| | 260 | { "none", "Don't compress metadata", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, .flags = FLAGS, .unit = "compression" }, |
| | 261 | { "limited", "Limited metadata compression", 0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_LIMITED}, .flags = FLAGS, .unit = "compression" }, |
| | 262 | { "extended", "Extended metadata compression",0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_EXTENDED}, .flags = FLAGS, .unit = "compression" }, |
| | 263 | { NULL } |
| | 264 | }; |
| | 265 | |
| | 266 | static const AVClass dovi_rpu_class = { |
| | 267 | .class_name = "dovi_rpu_bsf", |
| | 268 | .item_name = av_default_item_name, |
| | 269 | .option = dovi_rpu_options, |
| | 270 | .version = LIBAVUTIL_VERSION_INT, |
| | 271 | }; |
| | 272 | |
| | 273 | static const enum AVCodecID dovi_rpu_codec_ids[] = { |
| | 274 | AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1, AV_CODEC_ID_NONE, |
| | 275 | }; |
| | 276 | |
| | 277 | const FFBitStreamFilter ff_dovi_rpu_bsf = { |
| | 278 | .p.name = "dovi_rpu", |
| | 279 | .p.codec_ids = dovi_rpu_codec_ids, |
| | 280 | .p.priv_class = &dovi_rpu_class, |
| | 281 | .priv_data_size = sizeof(DoviRpuContext), |
| | 282 | .init = &dovi_rpu_init, |
| | 283 | .close = &dovi_rpu_close, |
| | 284 | .filter = &ff_cbs_bsf_generic_filter, |
| | 285 | }; |