| Line | Branch | Exec | Source |
|---|---|---|---|
| 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 <stdbool.h> | ||
| 20 | |||
| 21 | #include "libavutil/avassert.h" | ||
| 22 | #include "libavutil/dovi_meta.h" | ||
| 23 | #include "libavutil/intreadwrite.h" | ||
| 24 | #include "libavutil/mem.h" | ||
| 25 | #include "libavutil/opt.h" | ||
| 26 | |||
| 27 | #include "libavcodec/bsf.h" | ||
| 28 | #include "libavcodec/bsf_internal.h" | ||
| 29 | #include "libavcodec/h2645_parse.h" | ||
| 30 | #include "libavcodec/packet.h" | ||
| 31 | |||
| 32 | #include "libavcodec/hevc/hevc.h" | ||
| 33 | |||
| 34 | enum DOVISplitMode { | ||
| 35 | DOVI_SPLIT_BL = 0, | ||
| 36 | DOVI_SPLIT_BL_RPU = 1, | ||
| 37 | DOVI_SPLIT_EL = 2, | ||
| 38 | DOVI_SPLIT_EL_RPU = 3, | ||
| 39 | }; | ||
| 40 | |||
| 41 | typedef struct DOVISplitContext { | ||
| 42 | const AVClass *class; | ||
| 43 | int mode; | ||
| 44 | |||
| 45 | int nal_length_size; /* 0 means Annex-B input */ | ||
| 46 | int out_nal_length_size; /* 0 means Annex-B output */ | ||
| 47 | H2645Packet pkt; | ||
| 48 | } DOVISplitContext; | ||
| 49 | |||
| 50 | 8 | static int hvcc_nal_length_size(const uint8_t *data, int size) | |
| 51 | { | ||
| 52 |
4/8✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
8 | if (size >= 23 && data[0] == 1 && AV_RB24(data) != 1 && AV_RB32(data) != 1) |
| 53 | 8 | return (data[21] & 3) + 1; | |
| 54 | ✗ | return 0; | |
| 55 | } | ||
| 56 | |||
| 57 | 4 | static int dovi_split_init(AVBSFContext *ctx) | |
| 58 | { | ||
| 59 | 4 | DOVISplitContext *s = ctx->priv_data; | |
| 60 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
4 | bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU; |
| 61 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
4 | bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU; |
| 62 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
4 | bool keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU; |
| 63 | |||
| 64 | /* Profile 7 is currently the only supported variant with EL by Dolby. | ||
| 65 | * Default to that in case there is no DOVI config. */ | ||
| 66 | 4 | uint8_t dv_profile = 7; | |
| 67 | |||
| 68 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | for (int i = 0; i < ctx->par_out->nb_coded_side_data; i++) { |
| 69 | 4 | AVPacketSideData *sd = &ctx->par_out->coded_side_data[i]; | |
| 70 | AVDOVIDecoderConfigurationRecord *cfg; | ||
| 71 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (sd->type != AV_PKT_DATA_DOVI_CONF) |
| 72 | ✗ | continue; | |
| 73 | 4 | cfg = (AVDOVIDecoderConfigurationRecord *)sd->data; | |
| 74 | 4 | cfg->bl_present_flag &= keep_bl; | |
| 75 | 4 | cfg->el_present_flag &= keep_el; | |
| 76 | 4 | cfg->rpu_present_flag &= keep_rpu; | |
| 77 | 4 | dv_profile = cfg->dv_profile; | |
| 78 | 4 | break; | |
| 79 | } | ||
| 80 | |||
| 81 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (keep_el) { |
| 82 | const AVPacketSideData *sd; | ||
| 83 | 2 | sd = av_packet_side_data_get(ctx->par_out->coded_side_data, | |
| 84 | 2 | ctx->par_out->nb_coded_side_data, | |
| 85 | AV_PKT_DATA_HEVC_CONF); | ||
| 86 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if (sd && sd->size >= 23) { |
| 87 | 2 | uint8_t *new_ed = av_mallocz(sd->size + AV_INPUT_BUFFER_PADDING_SIZE); | |
| 88 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!new_ed) |
| 89 | ✗ | return AVERROR(ENOMEM); | |
| 90 | 2 | memcpy(new_ed, sd->data, sd->size); | |
| 91 | 2 | av_freep(&ctx->par_out->extradata); | |
| 92 | 2 | ctx->par_out->extradata = new_ed; | |
| 93 | 2 | ctx->par_out->extradata_size = sd->size; | |
| 94 | } | ||
| 95 | |||
| 96 | /* DV profile to EL size ratio */ | ||
| 97 | static const uint8_t el_div[] = { | ||
| 98 | [2] = 2, | ||
| 99 | [3] = 1, | ||
| 100 | [4] = 2, | ||
| 101 | [6] = 2, | ||
| 102 | [7] = 2, | ||
| 103 | }; | ||
| 104 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | int div = dv_profile < FF_ARRAY_ELEMS(el_div) ? el_div[dv_profile] : 0; |
| 105 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!div) |
| 106 | ✗ | av_log(ctx, AV_LOG_WARNING, "Unexpected DV Profile %d.\n", dv_profile); | |
| 107 | |||
| 108 | /* P7: EL is 1:1 for FHD BL */ | ||
| 109 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (dv_profile == 7 && ctx->par_in->width <= 1920) |
| 110 | ✗ | div = 1; | |
| 111 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (div > 1) { |
| 112 | 2 | ctx->par_out->width = ctx->par_in->width / div; | |
| 113 | 2 | ctx->par_out->height = ctx->par_in->height / div; | |
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /* Drop AV_PKT_DATA_HEVC_CONF as it's no longer valid on output. It's | ||
| 118 | * set as extradata for EL. */ | ||
| 119 | 4 | av_packet_side_data_remove(ctx->par_out->coded_side_data, | |
| 120 | 4 | &ctx->par_out->nb_coded_side_data, | |
| 121 | AV_PKT_DATA_HEVC_CONF); | ||
| 122 | |||
| 123 | 8 | s->nal_length_size = hvcc_nal_length_size(ctx->par_in->extradata, | |
| 124 | 4 | ctx->par_in->extradata_size); | |
| 125 | 8 | s->out_nal_length_size = hvcc_nal_length_size(ctx->par_out->extradata, | |
| 126 | 4 | ctx->par_out->extradata_size); | |
| 127 | |||
| 128 | 4 | return 0; | |
| 129 | } | ||
| 130 | |||
| 131 | 4 | static void dovi_split_close(AVBSFContext *ctx) | |
| 132 | { | ||
| 133 | 4 | DOVISplitContext *s = ctx->priv_data; | |
| 134 | 4 | ff_h2645_packet_uninit(&s->pkt); | |
| 135 | 4 | } | |
| 136 | |||
| 137 | 176 | static int nal_is_kept(const DOVISplitContext *s, const H2645NAL *nal, | |
| 138 | const uint8_t **payload, int *payload_size) | ||
| 139 | { | ||
| 140 |
4/4✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
|
176 | bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU; |
| 141 |
4/4✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
|
176 | bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU; |
| 142 |
4/4✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
|
176 | bool keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU; |
| 143 | |||
| 144 |
3/3✓ Branch 0 taken 80 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 88 times.
|
176 | switch (nal->type) { |
| 145 | 80 | case HEVC_NAL_UNSPEC63: | |
| 146 | /* EL: keep only when extracting EL, strip two-bytes of outer NAL header */ | ||
| 147 |
3/4✓ Branch 0 taken 40 times.
✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
|
80 | if (!keep_el || nal->raw_size <= 2) |
| 148 | 40 | return 0; | |
| 149 | 40 | *payload = nal->raw_data + 2; | |
| 150 | 40 | *payload_size = nal->raw_size - 2; | |
| 151 | 40 | return 1; | |
| 152 | 8 | case HEVC_NAL_UNSPEC62: | |
| 153 | /* RPU: kept verbatim only when the selected mode opted in. */ | ||
| 154 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (!keep_rpu) |
| 155 | 4 | return 0; | |
| 156 | 4 | *payload = nal->raw_data; | |
| 157 | 4 | *payload_size = nal->raw_size; | |
| 158 | 4 | return 1; | |
| 159 | 88 | default: | |
| 160 | /* Anything else is a base-layer NAL. */ | ||
| 161 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 44 times.
|
88 | if (!keep_bl) |
| 162 | 44 | return 0; | |
| 163 | 44 | *payload = nal->raw_data; | |
| 164 | 44 | *payload_size = nal->raw_size; | |
| 165 | 44 | return 1; | |
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | 12 | static int dovi_split_filter(AVBSFContext *ctx, AVPacket *out) | |
| 170 | { | ||
| 171 | 12 | DOVISplitContext *s = ctx->priv_data; | |
| 172 | 12 | AVPacket *in = NULL; | |
| 173 | 12 | AVBufferRef *out_buf = NULL; | |
| 174 | uint8_t *dst; | ||
| 175 | 12 | size_t out_size = 0; | |
| 176 | 12 | int kept_count = 0; | |
| 177 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | int flags = (s->nal_length_size ? H2645_FLAG_IS_NALFF : 0) | |
| 178 | H2645_FLAG_SMALL_PADDING; | ||
| 179 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | int prefix_size = s->out_nal_length_size ? s->out_nal_length_size : 4; |
| 180 | int ret; | ||
| 181 | |||
| 182 | 12 | ret = ff_bsf_get_packet(ctx, &in); | |
| 183 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
12 | if (ret < 0) |
| 184 | 8 | return ret; | |
| 185 | |||
| 186 | 4 | ret = ff_h2645_packet_split(&s->pkt, in->data, in->size, ctx, | |
| 187 | s->nal_length_size, AV_CODEC_ID_HEVC, flags); | ||
| 188 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
| 189 | ✗ | goto fail; | |
| 190 | |||
| 191 |
2/2✓ Branch 0 taken 88 times.
✓ Branch 1 taken 4 times.
|
92 | for (int i = 0; i < s->pkt.nb_nals; i++) { |
| 192 | const uint8_t *payload; | ||
| 193 | int payload_size; | ||
| 194 |
2/2✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
|
88 | if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size)) |
| 195 | 44 | continue; | |
| 196 | 44 | out_size += prefix_size + payload_size; | |
| 197 | 44 | kept_count++; | |
| 198 | } | ||
| 199 | |||
| 200 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!kept_count) { |
| 201 | ✗ | ret = AVERROR(EAGAIN); | |
| 202 | ✗ | goto fail; | |
| 203 | } | ||
| 204 | |||
| 205 | 4 | out_buf = av_buffer_alloc(out_size + AV_INPUT_BUFFER_PADDING_SIZE); | |
| 206 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!out_buf) { |
| 207 | ✗ | ret = AVERROR(ENOMEM); | |
| 208 | ✗ | goto fail; | |
| 209 | } | ||
| 210 | |||
| 211 | 4 | dst = out_buf->data; | |
| 212 |
2/2✓ Branch 0 taken 88 times.
✓ Branch 1 taken 4 times.
|
92 | for (int i = 0; i < s->pkt.nb_nals; i++) { |
| 213 | const uint8_t *payload; | ||
| 214 | int payload_size; | ||
| 215 |
2/2✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
|
88 | if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size)) |
| 216 | 44 | continue; | |
| 217 |
1/6✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
|
44 | switch (s->out_nal_length_size) { |
| 218 | ✗ | case 0: AV_WB32(dst, 1); break; | |
| 219 | ✗ | case 1: AV_WB8 (dst, payload_size); break; | |
| 220 | ✗ | case 2: AV_WB16(dst, payload_size); break; | |
| 221 | ✗ | case 3: AV_WB24(dst, payload_size); break; | |
| 222 | 44 | case 4: AV_WB32(dst, payload_size); break; | |
| 223 | } | ||
| 224 | 44 | dst += prefix_size; | |
| 225 | 44 | memcpy(dst, payload, payload_size); | |
| 226 | 44 | dst += payload_size; | |
| 227 | } | ||
| 228 | 4 | memset(dst, 0, AV_INPUT_BUFFER_PADDING_SIZE); | |
| 229 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | av_assert0(dst == out_buf->data + out_size); |
| 230 | |||
| 231 | 4 | ret = av_packet_copy_props(out, in); | |
| 232 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
| 233 | ✗ | goto fail; | |
| 234 | |||
| 235 | 4 | out->buf = out_buf; | |
| 236 | 4 | out->data = out_buf->data; | |
| 237 | 4 | out->size = out_size; | |
| 238 | 4 | out_buf = NULL; | |
| 239 | |||
| 240 | 4 | fail: | |
| 241 | 4 | av_buffer_unref(&out_buf); | |
| 242 | 4 | av_packet_free(&in); | |
| 243 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | if (ret < 0 && ret != AVERROR(EAGAIN)) |
| 244 | ✗ | av_packet_unref(out); | |
| 245 | 4 | return ret; | |
| 246 | } | ||
| 247 | |||
| 248 | #define OFFSET(x) offsetof(DOVISplitContext, x) | ||
| 249 | #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM) | ||
| 250 | static const AVOption dovi_split_options[] = { | ||
| 251 | { "mode", "Which Dolby Vision components to keep in the output bitstream", OFFSET(mode), AV_OPT_TYPE_INT, { .i64 = DOVI_SPLIT_BL }, DOVI_SPLIT_BL, DOVI_SPLIT_EL_RPU, FLAGS, .unit = "mode" }, | ||
| 252 | { "bl", "Base layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL }, .flags = FLAGS, .unit = "mode" }, | ||
| 253 | { "bl_rpu", "Base layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL_RPU }, .flags = FLAGS, .unit = "mode" }, | ||
| 254 | { "el", "Enhancement layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL }, .flags = FLAGS, .unit = "mode" }, | ||
| 255 | { "el_rpu", "Enhancement layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL_RPU }, .flags = FLAGS, .unit = "mode" }, | ||
| 256 | { NULL }, | ||
| 257 | }; | ||
| 258 | |||
| 259 | static const AVClass dovi_split_class = { | ||
| 260 | .class_name = "dovi_split_bsf", | ||
| 261 | .item_name = av_default_item_name, | ||
| 262 | .option = dovi_split_options, | ||
| 263 | .version = LIBAVUTIL_VERSION_INT, | ||
| 264 | }; | ||
| 265 | |||
| 266 | static const enum AVCodecID dovi_split_codec_ids[] = { | ||
| 267 | AV_CODEC_ID_HEVC, AV_CODEC_ID_NONE, | ||
| 268 | }; | ||
| 269 | |||
| 270 | const FFBitStreamFilter ff_dovi_split_bsf = { | ||
| 271 | .p.name = "dovi_split", | ||
| 272 | .p.codec_ids = dovi_split_codec_ids, | ||
| 273 | .p.priv_class = &dovi_split_class, | ||
| 274 | .priv_data_size = sizeof(DOVISplitContext), | ||
| 275 | .init = dovi_split_init, | ||
| 276 | .close = dovi_split_close, | ||
| 277 | .filter = dovi_split_filter, | ||
| 278 | }; | ||
| 279 |