| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Resolume DXV encoder | ||
| 3 | * Copyright (C) 2024 Emma Worley <emma@emma.gg> | ||
| 4 | * | ||
| 5 | * This file is part of FFmpeg. | ||
| 6 | * | ||
| 7 | * FFmpeg is free software; you can redistribute it and/or | ||
| 8 | * modify it under the terms of the GNU Lesser General Public | ||
| 9 | * License as published by the Free Software Foundation; either | ||
| 10 | * version 2.1 of the License, or (at your option) any later version. | ||
| 11 | * | ||
| 12 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | * Lesser General Public License for more details. | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU Lesser General Public | ||
| 18 | * License along with FFmpeg; if not, write to the Free Software | ||
| 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 20 | */ | ||
| 21 | |||
| 22 | #include <stdint.h> | ||
| 23 | |||
| 24 | #include "libavcodec/hashtable.h" | ||
| 25 | #include "libavutil/imgutils.h" | ||
| 26 | #include "libavutil/mem.h" | ||
| 27 | #include "libavutil/opt.h" | ||
| 28 | |||
| 29 | #include "bytestream.h" | ||
| 30 | #include "codec_internal.h" | ||
| 31 | #include "dxv.h" | ||
| 32 | #include "encode.h" | ||
| 33 | #include "texturedsp.h" | ||
| 34 | |||
| 35 | #define DXV_HEADER_LENGTH 12 | ||
| 36 | |||
| 37 | /* | ||
| 38 | * Resolume will refuse to display frames that are not padded to 16x16 pixels. | ||
| 39 | */ | ||
| 40 | #define DXV_ALIGN(x) FFALIGN(x, 16) | ||
| 41 | |||
| 42 | /* | ||
| 43 | * DXV uses LZ-like back-references to avoid copying words that have already | ||
| 44 | * appeared in the decompressed stream. Using a simple hash table (HT) | ||
| 45 | * significantly speeds up the lookback process while encoding. | ||
| 46 | */ | ||
| 47 | #define LOOKBACK_HT_ELEMS 0x20202 | ||
| 48 | #define LOOKBACK_WORDS 0x20202 | ||
| 49 | |||
| 50 | typedef struct DXVEncContext { | ||
| 51 | AVClass *class; | ||
| 52 | |||
| 53 | PutByteContext pbc; | ||
| 54 | |||
| 55 | uint8_t *tex_data; // Compressed texture | ||
| 56 | int64_t tex_size; // Texture size | ||
| 57 | |||
| 58 | /* Optimal number of slices for parallel decoding */ | ||
| 59 | int slice_count; | ||
| 60 | |||
| 61 | TextureDSPThreadContext enc; | ||
| 62 | |||
| 63 | DXVTextureFormat tex_fmt; | ||
| 64 | int (*compress_tex)(AVCodecContext *avctx); | ||
| 65 | |||
| 66 | FFHashtableContext *color_ht; | ||
| 67 | FFHashtableContext *lut_ht; | ||
| 68 | FFHashtableContext *combo_ht; | ||
| 69 | } DXVEncContext; | ||
| 70 | |||
| 71 | /* Converts an index offset value to a 2-bit opcode and pushes it to a stream. | ||
| 72 | * Inverse of CHECKPOINT in dxv.c. */ | ||
| 73 | #define PUSH_OP(x) \ | ||
| 74 | do { \ | ||
| 75 | if (state == 16) { \ | ||
| 76 | if (bytestream2_get_bytes_left_p(pbc) < 4) { \ | ||
| 77 | return AVERROR_INVALIDDATA; \ | ||
| 78 | } \ | ||
| 79 | value = pbc->buffer; \ | ||
| 80 | bytestream2_put_le32(pbc, 0); \ | ||
| 81 | state = 0; \ | ||
| 82 | } \ | ||
| 83 | if (idx >= 0x102 * x) { \ | ||
| 84 | op = 3; \ | ||
| 85 | bytestream2_put_le16(pbc, (idx / x) - 0x102); \ | ||
| 86 | } else if (idx >= 2 * x) { \ | ||
| 87 | op = 2; \ | ||
| 88 | bytestream2_put_byte(pbc, (idx / x) - 2); \ | ||
| 89 | } else if (idx == x) { \ | ||
| 90 | op = 1; \ | ||
| 91 | } else { \ | ||
| 92 | op = 0; \ | ||
| 93 | } \ | ||
| 94 | AV_WL32(value, AV_RL32(value) | (op << (state * 2))); \ | ||
| 95 | state++; \ | ||
| 96 | } while (0) | ||
| 97 | |||
| 98 | 1 | static int dxv_compress_dxt1(AVCodecContext *avctx) | |
| 99 | { | ||
| 100 | 1 | DXVEncContext *ctx = avctx->priv_data; | |
| 101 | 1 | PutByteContext *pbc = &ctx->pbc; | |
| 102 | void *value; | ||
| 103 | 1 | uint32_t idx, combo_idx, prev_pos, old_pos, state = 16, pos = 0, op = 0; | |
| 104 | |||
| 105 | 1 | ff_hashtable_clear(ctx->color_ht); | |
| 106 | 1 | ff_hashtable_clear(ctx->lut_ht); | |
| 107 | 1 | ff_hashtable_clear(ctx->combo_ht); | |
| 108 | |||
| 109 | 1 | ff_hashtable_set(ctx->combo_ht, ctx->tex_data, &pos); | |
| 110 | |||
| 111 | 1 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data)); | |
| 112 | 1 | ff_hashtable_set(ctx->color_ht, ctx->tex_data, &pos); | |
| 113 | 1 | pos++; | |
| 114 | 1 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + 4)); | |
| 115 | 1 | ff_hashtable_set(ctx->lut_ht, ctx->tex_data + 4, &pos); | |
| 116 | 1 | pos++; | |
| 117 | |||
| 118 |
2/2✓ Branch 0 taken 130559 times.
✓ Branch 1 taken 1 times.
|
130560 | while (pos + 2 <= ctx->tex_size / 4) { |
| 119 |
2/2✓ Branch 1 taken 124388 times.
✓ Branch 2 taken 6171 times.
|
130559 | combo_idx = ff_hashtable_get(ctx->combo_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0; |
| 120 | 130559 | idx = combo_idx; | |
| 121 |
9/10✓ Branch 0 taken 8171 times.
✓ Branch 1 taken 122388 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8171 times.
✓ Branch 6 taken 2704 times.
✓ Branch 7 taken 127855 times.
✓ Branch 9 taken 844 times.
✓ Branch 10 taken 127011 times.
✓ Branch 12 taken 120840 times.
✓ Branch 13 taken 6171 times.
|
130559 | PUSH_OP(2); |
| 122 |
2/2✓ Branch 0 taken 64767 times.
✓ Branch 1 taken 65792 times.
|
130559 | if (pos >= LOOKBACK_WORDS) { |
| 123 | 64767 | old_pos = pos - LOOKBACK_WORDS; | |
| 124 |
3/4✓ Branch 1 taken 64767 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1382 times.
✓ Branch 4 taken 63385 times.
|
64767 | if (ff_hashtable_get(ctx->combo_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos) |
| 125 | 1382 | ff_hashtable_delete(ctx->combo_ht, ctx->tex_data + old_pos * 4); | |
| 126 | } | ||
| 127 | 130559 | ff_hashtable_set(ctx->combo_ht, ctx->tex_data + pos * 4, &pos); | |
| 128 | |||
| 129 |
2/2✓ Branch 0 taken 6171 times.
✓ Branch 1 taken 124388 times.
|
130559 | if (!combo_idx) { |
| 130 |
2/2✓ Branch 1 taken 4495 times.
✓ Branch 2 taken 1676 times.
|
6171 | idx = ff_hashtable_get(ctx->color_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0; |
| 131 |
9/10✓ Branch 0 taken 326 times.
✓ Branch 1 taken 5845 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 326 times.
✓ Branch 6 taken 3374 times.
✓ Branch 7 taken 2797 times.
✓ Branch 9 taken 663 times.
✓ Branch 10 taken 2134 times.
✓ Branch 12 taken 458 times.
✓ Branch 13 taken 1676 times.
|
6171 | PUSH_OP(2); |
| 132 |
2/2✓ Branch 0 taken 1676 times.
✓ Branch 1 taken 4495 times.
|
6171 | if (!idx) |
| 133 | 1676 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + pos * 4)); | |
| 134 | } | ||
| 135 |
2/2✓ Branch 0 taken 64767 times.
✓ Branch 1 taken 65792 times.
|
130559 | if (pos >= LOOKBACK_WORDS) { |
| 136 | 64767 | old_pos = pos - LOOKBACK_WORDS; | |
| 137 |
3/4✓ Branch 1 taken 64767 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 680 times.
✓ Branch 4 taken 64087 times.
|
64767 | if (ff_hashtable_get(ctx->color_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos) |
| 138 | 680 | ff_hashtable_delete(ctx->color_ht, ctx->tex_data + old_pos * 4); | |
| 139 | } | ||
| 140 | 130559 | ff_hashtable_set(ctx->color_ht, ctx->tex_data + pos * 4, &pos); | |
| 141 | 130559 | pos++; | |
| 142 | |||
| 143 |
2/2✓ Branch 0 taken 6171 times.
✓ Branch 1 taken 124388 times.
|
130559 | if (!combo_idx) { |
| 144 |
2/2✓ Branch 1 taken 1965 times.
✓ Branch 2 taken 4206 times.
|
6171 | idx = ff_hashtable_get(ctx->lut_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0; |
| 145 |
9/10✓ Branch 0 taken 435 times.
✓ Branch 1 taken 5736 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 435 times.
✓ Branch 6 taken 1447 times.
✓ Branch 7 taken 4724 times.
✓ Branch 9 taken 357 times.
✓ Branch 10 taken 4367 times.
✓ Branch 12 taken 161 times.
✓ Branch 13 taken 4206 times.
|
6171 | PUSH_OP(2); |
| 146 |
2/2✓ Branch 0 taken 4206 times.
✓ Branch 1 taken 1965 times.
|
6171 | if (!idx) |
| 147 | 4206 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + pos * 4)); | |
| 148 | } | ||
| 149 |
2/2✓ Branch 0 taken 64767 times.
✓ Branch 1 taken 65792 times.
|
130559 | if (pos >= LOOKBACK_WORDS) { |
| 150 | 64767 | old_pos = pos - LOOKBACK_WORDS; | |
| 151 |
3/4✓ Branch 1 taken 64767 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 77 times.
✓ Branch 4 taken 64690 times.
|
64767 | if (ff_hashtable_get(ctx->lut_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos) |
| 152 | 77 | ff_hashtable_delete(ctx->lut_ht, ctx->tex_data + old_pos * 4); | |
| 153 | } | ||
| 154 | 130559 | ff_hashtable_set(ctx->lut_ht, ctx->tex_data + pos * 4, &pos); | |
| 155 | 130559 | pos++; | |
| 156 | } | ||
| 157 | |||
| 158 | 1 | return 0; | |
| 159 | } | ||
| 160 | |||
| 161 | 1 | static int dxv_encode(AVCodecContext *avctx, AVPacket *pkt, | |
| 162 | const AVFrame *frame, int *got_packet) | ||
| 163 | { | ||
| 164 | 1 | DXVEncContext *ctx = avctx->priv_data; | |
| 165 | 1 | PutByteContext *pbc = &ctx->pbc; | |
| 166 | int ret; | ||
| 167 | |||
| 168 | /* unimplemented: needs to depend on compression ratio of tex format */ | ||
| 169 | /* under DXT1, we need 3 words to encode load ops for 32 words. | ||
| 170 | * the first 2 words of the texture do not need load ops. */ | ||
| 171 | 1 | ret = ff_alloc_packet(avctx, pkt, DXV_HEADER_LENGTH + ctx->tex_size + AV_CEIL_RSHIFT(ctx->tex_size - 8, 7) * 12); | |
| 172 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
| 173 | ✗ | return ret; | |
| 174 | |||
| 175 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (ctx->enc.tex_funct) { |
| 176 | 1 | uint8_t *safe_data[4] = {frame->data[0], 0, 0, 0}; | |
| 177 | 1 | int safe_linesize[4] = {frame->linesize[0], 0, 0, 0}; | |
| 178 | |||
| 179 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (avctx->width != DXV_ALIGN(avctx->width) || avctx->height != DXV_ALIGN(avctx->height)) { |
| 180 | 1 | ret = av_image_alloc( | |
| 181 | safe_data, | ||
| 182 | safe_linesize, | ||
| 183 | 1 | DXV_ALIGN(avctx->width), | |
| 184 | 1 | DXV_ALIGN(avctx->height), | |
| 185 | avctx->pix_fmt, | ||
| 186 | 1); | ||
| 187 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
| 188 | ✗ | return ret; | |
| 189 | |||
| 190 | 1 | av_image_copy2( | |
| 191 | safe_data, | ||
| 192 | safe_linesize, | ||
| 193 | 1 | frame->data, | |
| 194 | 1 | frame->linesize, | |
| 195 | avctx->pix_fmt, | ||
| 196 | avctx->width, | ||
| 197 | avctx->height); | ||
| 198 | |||
| 199 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (avctx->width != DXV_ALIGN(avctx->width)) { |
| 200 | ✗ | av_assert0(frame->format == AV_PIX_FMT_RGBA); | |
| 201 | ✗ | for (int y = 0; y < avctx->height; y++) { | |
| 202 | ✗ | memset(safe_data[0] + y * safe_linesize[0] + 4*avctx->width, 0, safe_linesize[0] - 4*avctx->width); | |
| 203 | } | ||
| 204 | } | ||
| 205 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (avctx->height != DXV_ALIGN(avctx->height)) { |
| 206 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
|
9 | for (int y = avctx->height; y < DXV_ALIGN(avctx->height); y++) { |
| 207 | 8 | memset(safe_data[0] + y * safe_linesize[0], 0, safe_linesize[0]); | |
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | 1 | ctx->enc.tex_data.out = ctx->tex_data; | |
| 213 | 1 | ctx->enc.frame_data.in = safe_data[0]; | |
| 214 | 1 | ctx->enc.stride = safe_linesize[0]; | |
| 215 | 1 | ctx->enc.width = DXV_ALIGN(avctx->width); | |
| 216 | 1 | ctx->enc.height = DXV_ALIGN(avctx->height); | |
| 217 | 1 | ff_texturedsp_exec_compress_threads(avctx, &ctx->enc); | |
| 218 | |||
| 219 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (safe_data[0] != frame->data[0]) |
| 220 | 1 | av_freep(&safe_data[0]); | |
| 221 | } else { | ||
| 222 | /* unimplemented: YCoCg formats */ | ||
| 223 | ✗ | return AVERROR_INVALIDDATA; | |
| 224 | } | ||
| 225 | |||
| 226 | 1 | bytestream2_init_writer(pbc, pkt->data, pkt->size); | |
| 227 | |||
| 228 | 1 | bytestream2_put_le32(pbc, ctx->tex_fmt); | |
| 229 | 1 | bytestream2_put_byte(pbc, 4); | |
| 230 | 1 | bytestream2_put_byte(pbc, 0); | |
| 231 | 1 | bytestream2_put_byte(pbc, 0); | |
| 232 | 1 | bytestream2_put_byte(pbc, 0); | |
| 233 | /* Fill in compressed size later */ | ||
| 234 | 1 | bytestream2_skip_p(pbc, 4); | |
| 235 | |||
| 236 | 1 | ret = ctx->compress_tex(avctx); | |
| 237 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
| 238 | ✗ | return ret; | |
| 239 | |||
| 240 | 1 | AV_WL32(pkt->data + 8, bytestream2_tell_p(pbc) - DXV_HEADER_LENGTH); | |
| 241 | 1 | av_shrink_packet(pkt, bytestream2_tell_p(pbc)); | |
| 242 | |||
| 243 | 1 | *got_packet = 1; | |
| 244 | 1 | return 0; | |
| 245 | } | ||
| 246 | |||
| 247 | 9 | static av_cold int dxv_init(AVCodecContext *avctx) | |
| 248 | { | ||
| 249 | 9 | DXVEncContext *ctx = avctx->priv_data; | |
| 250 | TextureDSPEncContext texdsp; | ||
| 251 | 9 | int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); | |
| 252 | |||
| 253 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret < 0) { |
| 254 | ✗ | av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n", | |
| 255 | avctx->width, avctx->height); | ||
| 256 | ✗ | return ret; | |
| 257 | } | ||
| 258 | |||
| 259 | 9 | ff_texturedspenc_init(&texdsp); | |
| 260 | |||
| 261 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | switch (ctx->tex_fmt) { |
| 262 | 9 | case DXV_FMT_DXT1: | |
| 263 | 9 | ctx->compress_tex = dxv_compress_dxt1; | |
| 264 | 9 | ctx->enc.tex_funct = texdsp.dxt1_block; | |
| 265 | 9 | ctx->enc.tex_ratio = 8; | |
| 266 | 9 | break; | |
| 267 | ✗ | default: | |
| 268 | ✗ | av_log(avctx, AV_LOG_ERROR, "Invalid format %08X\n", ctx->tex_fmt); | |
| 269 | ✗ | return AVERROR_INVALIDDATA; | |
| 270 | } | ||
| 271 | 9 | ctx->enc.raw_ratio = 16; | |
| 272 | 9 | ctx->tex_size = DXV_ALIGN(avctx->width) / TEXTURE_BLOCK_W * | |
| 273 | 9 | DXV_ALIGN(avctx->height) / TEXTURE_BLOCK_H * | |
| 274 | 9 | ctx->enc.tex_ratio; | |
| 275 | 9 | ctx->enc.slice_count = av_clip(avctx->thread_count, 1, DXV_ALIGN(avctx->height) / TEXTURE_BLOCK_H); | |
| 276 | |||
| 277 | 9 | ctx->tex_data = av_malloc(ctx->tex_size); | |
| 278 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!ctx->tex_data) { |
| 279 | ✗ | return AVERROR(ENOMEM); | |
| 280 | } | ||
| 281 | |||
| 282 | 9 | ret = ff_hashtable_alloc(&ctx->color_ht, sizeof(uint32_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS); | |
| 283 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret < 0) |
| 284 | ✗ | return ret; | |
| 285 | 9 | ret = ff_hashtable_alloc(&ctx->lut_ht, sizeof(uint32_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS); | |
| 286 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret < 0) |
| 287 | ✗ | return ret; | |
| 288 | 9 | ret = ff_hashtable_alloc(&ctx->combo_ht, sizeof(uint64_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS); | |
| 289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret < 0) |
| 290 | ✗ | return ret; | |
| 291 | |||
| 292 | 9 | return 0; | |
| 293 | } | ||
| 294 | |||
| 295 | 9 | static av_cold int dxv_close(AVCodecContext *avctx) | |
| 296 | { | ||
| 297 | 9 | DXVEncContext *ctx = avctx->priv_data; | |
| 298 | |||
| 299 | 9 | av_freep(&ctx->tex_data); | |
| 300 | |||
| 301 | 9 | ff_hashtable_freep(&ctx->color_ht); | |
| 302 | 9 | ff_hashtable_freep(&ctx->lut_ht); | |
| 303 | 9 | ff_hashtable_freep(&ctx->combo_ht); | |
| 304 | |||
| 305 | 9 | return 0; | |
| 306 | } | ||
| 307 | |||
| 308 | #define OFFSET(x) offsetof(DXVEncContext, x) | ||
| 309 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM | ||
| 310 | static const AVOption options[] = { | ||
| 311 | { "format", NULL, OFFSET(tex_fmt), AV_OPT_TYPE_INT, { .i64 = DXV_FMT_DXT1 }, DXV_FMT_DXT1, DXV_FMT_DXT1, FLAGS, .unit = "format" }, | ||
| 312 | { "dxt1", "DXT1 (Normal Quality, No Alpha)", 0, AV_OPT_TYPE_CONST, { .i64 = DXV_FMT_DXT1 }, 0, 0, FLAGS, .unit = "format" }, | ||
| 313 | { NULL }, | ||
| 314 | }; | ||
| 315 | |||
| 316 | static const AVClass dxvenc_class = { | ||
| 317 | .class_name = "DXV encoder", | ||
| 318 | .option = options, | ||
| 319 | .version = LIBAVUTIL_VERSION_INT, | ||
| 320 | }; | ||
| 321 | |||
| 322 | const FFCodec ff_dxv_encoder = { | ||
| 323 | .p.name = "dxv", | ||
| 324 | CODEC_LONG_NAME("Resolume DXV"), | ||
| 325 | .p.type = AVMEDIA_TYPE_VIDEO, | ||
| 326 | .p.id = AV_CODEC_ID_DXV, | ||
| 327 | .init = dxv_init, | ||
| 328 | FF_CODEC_ENCODE_CB(dxv_encode), | ||
| 329 | .close = dxv_close, | ||
| 330 | .priv_data_size = sizeof(DXVEncContext), | ||
| 331 | .p.capabilities = AV_CODEC_CAP_DR1 | | ||
| 332 | AV_CODEC_CAP_SLICE_THREADS | | ||
| 333 | AV_CODEC_CAP_FRAME_THREADS, | ||
| 334 | .p.priv_class = &dxvenc_class, | ||
| 335 | CODEC_PIXFMTS(AV_PIX_FMT_RGBA), | ||
| 336 | .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, | ||
| 337 | }; | ||
| 338 |