Files
LynneandClaude Opus 5 faa7c882cb prores_raw: use the 16-bit iDCT
The decoder converted the 32-bit output of its transform to 16 bits
through an empirically found offset, and added one to every DC
coefficient to compensate for the bias of the result. Neither
reproduces the transform well: against a bit-exact model of the
reference decoder, most samples are off, with a quantizer-dependent
bias and errors of up to 357 out of 65535 at the extremes of the range,
and the DC offset only helps on HQ material.

Use the 16-bit output variant of simple_idct instead, with the
dequantized coefficients saturated to 16 bits as the reference does,
and the centered transform output biased in the output stage.

Against an exact transform, the error drops from 5.6 LSB RMS with a
maximum of 21 to 0.49 LSB RMS with a maximum of 2.3 and no bias, on par
with the reference decoder's 0.29 and 2.4. The decoded output agrees
with the reference decoder on 95% of the samples of a 5.7K RAW HQ frame
with a log curve, within 3.5 LSB of a 14-bit sensor in its top stop and
within 1 LSB elsewhere. Speed is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016LFnYMstwe4woBzpxfzDzg
2026-09-24 08:02:22 +09:00

657 lines
21 KiB
C

/*
* ProRes RAW decoder
* Copyright (c) 2023-2025 Paul B Mahol
* Copyright (c) 2025 Lynne
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libavutil/avassert.h"
#include "libavutil/intfloat.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mem_internal.h"
#include "libavutil/mem.h"
#include "libavutil/raw_color_params.h"
#include "libavutil/rational.h"
#define CACHED_BITSTREAM_READER !ARCH_X86_32
#include "config_components.h"
#include "avcodec.h"
#include "bytestream.h"
#include "codec_internal.h"
#include "decode.h"
#include "get_bits.h"
#include "idctdsp.h"
#include "proresdata.h"
#include "thread.h"
#include "hwconfig.h"
#include "hwaccel_internal.h"
#include "prores_raw.h"
static av_cold int decode_init(AVCodecContext *avctx)
{
ProResRAWContext *s = avctx->priv_data;
/* The codec outputs linear data, with the transfer function of the
* camera and any adjustments built into an 8-point linearization curve */
avctx->bits_per_raw_sample = 16;
avctx->color_trc = AVCOL_TRC_LINEAR;
avctx->color_primaries = AVCOL_PRI_UNSPECIFIED;
avctx->colorspace = AVCOL_SPC_UNSPECIFIED;
s->pix_fmt = AV_PIX_FMT_NONE;
ff_blockdsp_init(&s->bdsp);
/* Coefficients and the iDCT are 12-bit, the linearization curve then
* expands the result to the 16-bit linear output range. */
ff_proresdsp_init(&s->prodsp, 12);
ff_permute_scantable(s->scan, ff_prores_interlaced_scan, s->prodsp.idct_permutation);
return 0;
}
/* Decodes a symbol from the next 32 bits, zero-filled past the end of the
* data. Returns a negative value once those are all zero, which is where
* the reference decoder stops decoding a component. */
static av_always_inline int get_value(GetBitContext *gb, int16_t codebook)
{
const int switch_bits = codebook >> 8;
const int rice_order = codebook & 0xf;
const int exp_order = (codebook >> 4) & 0xf;
int left = get_bits_left(gb);
uint32_t b;
int q, bits;
b = show_bits_long(gb, 32);
if (left < 32) {
/* The reader has read past the end of the data; mask that off */
if (left <= 0)
return -1;
b &= 0xFFFFFFFFu << (32 - left);
}
if (!b)
return -1;
q = ff_clz(b);
if (b & 0x80000000) {
skip_bits_long(gb, 1 + rice_order);
return (b & 0x7FFFFFFF) >> (31 - rice_order);
}
if (q <= switch_bits) {
skip_bits_long(gb, 1 + rice_order + q);
return (q << rice_order) +
(((b << (q + 1)) >> 1) >> (31 - rice_order));
}
/* No valid code is longer than the window */
bits = exp_order + (q << 1) - switch_bits;
if (bits > 32)
return -1;
skip_bits_long(gb, bits);
return (b >> (32 - bits)) +
((switch_bits + 1) << rice_order) -
(1 << exp_order);
}
#define TODCCODEBOOK(x) ((x + 1) >> 1)
#define DC_CB_MAX 12
const uint8_t ff_prores_raw_dc_cb[DC_CB_MAX + 1] = {
0x010, 0x021, 0x032, 0x033, 0x033, 0x033, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x076,
};
#define AC_CB_MAX 94
const int16_t ff_prores_raw_ac_cb[AC_CB_MAX + 1] = {
0x000, 0x211, 0x111, 0x111, 0x222, 0x222, 0x222, 0x122, 0x122, 0x122,
0x233, 0x233, 0x233, 0x233, 0x233, 0x233, 0x233, 0x233, 0x133, 0x133,
0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244,
0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244, 0x244,
0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355,
0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355,
0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355,
0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x355, 0x166,
};
#define RN_CB_MAX 27
const int16_t ff_prores_raw_rn_cb[RN_CB_MAX + 1] = {
0x200, 0x100, 0x000, 0x000, 0x211, 0x211, 0x111, 0x111, 0x011, 0x011, 0x021, 0x021, 0x222, 0x022,
0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x032, 0x032, 0x044
};
#define LN_CB_MAX 14
const int16_t ff_prores_raw_ln_cb[LN_CB_MAX + 1] = {
0x100, 0x111, 0x222, 0x222, 0x122, 0x122, 0x433, 0x433, 0x233, 0x233, 0x233, 0x233, 0x233, 0x233, 0x033,
};
static int decode_comp(AVCodecContext *avctx, TileContext *tile,
AVFrame *frame, const uint8_t *data, int size,
int component, int16_t *qmat)
{
int ret;
ProResRAWContext *s = avctx->priv_data;
const ptrdiff_t linesize = frame->linesize[0] >> 1;
uint16_t *dst = (uint16_t *)(frame->data[0] + tile->y*frame->linesize[0] + 2*tile->x);
int idx;
const int log2_nb_blocks = tile->log2_nb_blocks;
const int nb_blocks = 1 << log2_nb_blocks;
const int block_mask = nb_blocks - 1;
const int nb_codes = 64 * nb_blocks;
LOCAL_ALIGNED_32(int32_t, block, [64*16]);
int sign = 0;
int dc_add = 0;
int16_t dc_codebook;
int ac, rn, ln;
int16_t ac_codebook = 49;
int16_t rn_codebook = 0;
int16_t ln_codebook = 66;
const uint8_t *scan = s->scan;
GetBitContext gb;
if (component > 1)
dst += linesize;
dst += component & 1;
if ((ret = init_get_bits8(&gb, data, size)) < 0)
return ret;
memset(block, 0, nb_blocks * 64 * sizeof(*block));
/* Special handling for first block */
int dc = get_value(&gb, 700);
if (dc < 0)
goto end;
int prev_dc = (dc >> 1) ^ -(dc & 1);
block[0] = prev_dc;
for (int n = 1; n < nb_blocks; n++) {
if ((n & 15) == 1)
dc_codebook = 100;
else
dc_codebook = ff_prores_raw_dc_cb[FFMIN(TODCCODEBOOK(dc), DC_CB_MAX)];
dc = get_value(&gb, dc_codebook);
if (dc < 0)
goto end;
sign = sign ^ dc & 1;
dc_add = (-sign ^ TODCCODEBOOK(dc)) + sign;
sign = dc_add < 0;
prev_dc += dc_add;
block[n*64] = prev_dc;
}
for (int n = nb_blocks; n < nb_codes;) {
ln = get_value(&gb, ln_codebook);
if (ln < 0)
goto end;
for (int i = 0; i < ln; i++) {
ac = get_value(&gb, ac_codebook);
if (ac < 0)
goto end;
ac_codebook = ff_prores_raw_ac_cb[FFMIN(ac, AC_CB_MAX)];
sign = -get_bits1(&gb);
idx = scan[n >> log2_nb_blocks] + ((n & block_mask) << 6);
block[idx] = ((ac + 1) ^ sign) - sign;
if (++n == nb_codes)
goto end;
}
rn = get_value(&gb, rn_codebook);
if (rn < 0)
goto end;
rn_codebook = ff_prores_raw_rn_cb[FFMIN(rn, RN_CB_MAX)];
n += rn + 1;
if (n >= nb_codes)
break;
ac = get_value(&gb, ac_codebook);
if (ac < 0)
goto end;
ac_codebook = ff_prores_raw_ac_cb[FFMIN(ac, AC_CB_MAX)];
ln_codebook = ff_prores_raw_ln_cb[FFMIN(ac, LN_CB_MAX)];
sign = -get_bits1(&gb);
idx = scan[n >> log2_nb_blocks] + ((n & block_mask) << 6);
block[idx] = ((ac + 1) ^ sign) - sign;
n++;
}
end:
for (int n = 0; n < nb_blocks; n++) {
uint16_t *ptr = dst + n*16;
s->prodsp.idct_put_bayer(ptr, linesize, block + n*64, qmat, s->lin_curve);
}
return 0;
}
static int decode_tile(AVCodecContext *avctx, TileContext *tile,
AVFrame *frame)
{
int ret;
ProResRAWContext *s = avctx->priv_data;
GetByteContext *gb = &tile->gb;
LOCAL_ALIGNED_32(int16_t, qmat, [64]);
if (tile->x >= avctx->width)
return 0;
/* Tile header */
int header_len = bytestream2_get_byteu(gb) >> 3;
int16_t scale = bytestream2_get_byteu(gb);
int size[4];
size[0] = bytestream2_get_be16(gb);
size[1] = bytestream2_get_be16(gb);
size[2] = bytestream2_get_be16(gb);
size[3] = bytestream2_size(gb) - size[0] - size[1] - size[2] - header_len;
if (size[3] < 0)
return AVERROR_INVALIDDATA;
for (int i = 0; i < 64; i++)
qmat[i] = s->qmat[i] * scale;
const uint8_t *comp_start = gb->buffer_start + header_len;
ret = decode_comp(avctx, tile, frame, comp_start,
size[0], 2, qmat);
if (ret < 0)
goto fail;
ret = decode_comp(avctx, tile, frame, comp_start + size[0],
size[1], 1, qmat);
if (ret < 0)
goto fail;
ret = decode_comp(avctx, tile, frame, comp_start + size[0] + size[1],
size[2], 3, qmat);
if (ret < 0)
goto fail;
ret = decode_comp(avctx, tile, frame, comp_start + size[0] + size[1] + size[2],
size[3], 0, qmat);
if (ret < 0)
goto fail;
return 0;
fail:
av_log(avctx, AV_LOG_ERROR, "tile %d/%d decoding error\n", tile->x, tile->y);
return ret;
}
static int decode_tiles(AVCodecContext *avctx, void *arg,
int n, int thread_nb)
{
ProResRAWContext *s = avctx->priv_data;
TileContext *tile = &s->tiles[n];
AVFrame *frame = arg;
return decode_tile(avctx, tile, frame);
}
static enum AVPixelFormat get_pixel_format(AVCodecContext *avctx,
enum AVPixelFormat pix_fmt)
{
enum AVPixelFormat pix_fmts[] = {
#if CONFIG_PRORES_RAW_VULKAN_HWACCEL
AV_PIX_FMT_VULKAN,
#endif
#if CONFIG_PRORES_RAW_VIDEOTOOLBOX_HWACCEL
AV_PIX_FMT_VIDEOTOOLBOX,
#endif
pix_fmt,
AV_PIX_FMT_NONE,
};
return ff_get_format(avctx, pix_fmts);
}
static int decode_frame(AVCodecContext *avctx,
AVFrame *frame, int *got_frame_ptr,
AVPacket *avpkt)
{
ProResRAWContext *s = avctx->priv_data;
int ret, dimensions_changed = 0, old_version = s->version;
DECLARE_ALIGNED(32, uint8_t, qmat)[64];
memset(qmat, 1, 64);
switch (avctx->codec_tag) {
case 0:
break;
case MKTAG('a','p','r','n'):
avctx->profile = AV_PROFILE_PRORES_RAW;
break;
case MKTAG('a','p','r','h'):
avctx->profile = AV_PROFILE_PRORES_RAW_HQ;
break;
default:
avpriv_request_sample(avctx, "Profile %d", avctx->codec_tag);
return AVERROR_PATCHWELCOME;
break;
}
GetByteContext gb;
bytestream2_init(&gb, avpkt->data, avpkt->size);
if (bytestream2_get_be32(&gb) != avpkt->size)
return AVERROR_INVALIDDATA;
/* ProRes RAW frame */
if (bytestream2_get_be32(&gb) != MKBETAG('p','r','r','f'))
return AVERROR_INVALIDDATA;
int header_len = bytestream2_get_be16(&gb);
if (header_len < 62 || bytestream2_get_bytes_left(&gb) < header_len - 2)
return AVERROR_INVALIDDATA;
GetByteContext gb_hdr;
bytestream2_init(&gb_hdr, gb.buffer, header_len - 2);
bytestream2_skip(&gb, header_len - 2);
bytestream2_skip(&gb_hdr, 1); /* 1 reserved byte */
s->version = bytestream2_get_byte(&gb_hdr);
if (s->version > 1) {
avpriv_request_sample(avctx, "Version %d", s->version);
return AVERROR_PATCHWELCOME;
}
/* Vendor header (e.g. "peac" for Panasonic or "atm0" for Atmos) */
bytestream2_skip(&gb_hdr, 4);
/* Width and height must always be even */
int w = bytestream2_get_be16(&gb_hdr);
int h = bytestream2_get_be16(&gb_hdr);
if ((w & 1) || (h & 1))
return AVERROR_INVALIDDATA;
if (w != avctx->width || h != avctx->height) {
av_log(avctx, AV_LOG_WARNING, "picture resolution change: %ix%i -> %ix%i\n",
avctx->width, avctx->height, w, h);
if ((ret = ff_set_dimensions(avctx, w, h)) < 0)
return ret;
dimensions_changed = 1;
}
avctx->coded_width = FFALIGN(w, 16);
avctx->coded_height = FFALIGN(h, 16);
enum AVPixelFormat pix_fmt = AV_PIX_FMT_BAYER_RGGB16;
if (pix_fmt != s->pix_fmt || dimensions_changed ||
s->version != old_version) {
s->pix_fmt = pix_fmt;
ret = get_pixel_format(avctx, pix_fmt);
if (ret < 0)
return ret;
avctx->pix_fmt = ret;
}
/* RecommendedCrop: pixel margins to discard after debayer. Order is
* left/right/top/bottom */
uint8_t crop_l = bytestream2_get_byte(&gb_hdr);
uint8_t crop_r = bytestream2_get_byte(&gb_hdr);
uint8_t crop_t = bytestream2_get_byte(&gb_hdr);
uint8_t crop_b = bytestream2_get_byte(&gb_hdr);
/* BayerPattern: 0=RGGB, 1/2/3 = alternates */
int bayer_pattern = bytestream2_get_be16(&gb_hdr) & 0x3;
if (bayer_pattern != 0) {
avpriv_request_sample(avctx, "Bayer pattern %d", bayer_pattern);
return AVERROR_PATCHWELCOME;
}
/* senselValueRange: black_level is hardcoded to 0x100,
* white_level = senselValueRange + 0x100 */
uint16_t black_level = 0x100;
uint16_t white_level = bytestream2_get_be16(&gb_hdr) + 0x100;
float wb_red = av_int2float(bytestream2_get_be32(&gb_hdr)); /* WhiteBalanceRedFactor */
float wb_blue = av_int2float(bytestream2_get_be32(&gb_hdr)); /* WhiteBalanceBlueFactor */
/* ColorMatrix (3x3 float, camera RGB -> CIE 1931 XYZ D65, row-major) */
float color_matrix[3][3];
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
color_matrix[r][c] = av_int2float(bytestream2_get_be32(&gb_hdr));
float gain = av_int2float(bytestream2_get_be32(&gb_hdr)); /* GainFactor (post-matrix mult) */
uint16_t wb_cct = bytestream2_get_be16(&gb_hdr); /* WhiteBalanceCCT (Kelvin, informational) */
/* Flags */
int flags = bytestream2_get_be16(&gb_hdr);
int align = (flags >> 1) & 0x7;
if (align > 4) {
av_log(avctx, AV_LOG_ERROR,
"Invalid tile alignment %d (max 4)\n", align);
return AVERROR_INVALIDDATA;
}
/* Quantization matrix */
if (flags & 1)
bytestream2_get_buffer(&gb_hdr, qmat, 64);
if ((flags >> 4) & 1) {
/* 8-poing 16-bit control points, defining the combined linearization
* curve (inv. transfer fn + encoder-defined shaping) */
for (int i = 0; i < 8; i++)
s->lin_curve[i] = bytestream2_get_be16(&gb_hdr);
} else {
/* default curve: ptwos */
static const uint16_t default_lin_curve[8] =
{ 0, 512, 1024, 2048, 4096, 8192, 16384, 32768 };
memcpy(s->lin_curve, default_lin_curve, sizeof(s->lin_curve));
}
ff_permute_scantable(s->qmat, s->prodsp.idct_permutation, qmat);
int tw16 = (w + 15) >> 4;
s->nb_tw = (tw16 >> align) + av_popcount(~(-1 * (1 << align)) & tw16);
s->nb_th = (h + 15) >> 4;
s->nb_tiles = s->nb_tw * s->nb_th;
av_log(avctx, AV_LOG_DEBUG, "%dx%d | nb_tiles: %d\n", s->nb_tw, s->nb_th, s->nb_tiles);
s->th = 16;
av_fast_mallocz(&s->tiles, &s->tiles_size, s->nb_tiles * sizeof(*s->tiles));
if (!s->tiles)
return AVERROR(ENOMEM);
if (bytestream2_get_bytes_left(&gb) < s->nb_tiles * 2)
return AVERROR_INVALIDDATA;
/* First tile that extends past the right edge gets halved in width,
* next one gets quartered, and so on */
int offset = bytestream2_tell(&gb) + s->nb_tiles * 2;
int n = 0;
for (int ty = 0; ty < s->nb_th; ty++) {
unsigned tx = 0;
int rem = tw16;
for (int e = align; rem > 0; e--) {
int unit = 1 << e;
while (unit <= rem) {
TileContext *tile = &s->tiles[n++];
int size = bytestream2_get_be16(&gb);
if (offset >= avpkt->size)
return AVERROR_INVALIDDATA;
if (size >= avpkt->size)
return AVERROR_INVALIDDATA;
if (offset > avpkt->size - size)
return AVERROR_INVALIDDATA;
bytestream2_init(&tile->gb, avpkt->data + offset, size);
tile->x = tx * 16;
tile->y = ty * s->th;
tile->log2_nb_blocks = e;
offset += size;
tx += unit;
rem -= unit;
}
}
}
av_assert1(n == s->nb_tiles);
/* The stream parameters are all set by now, which is all that probing
* with frames skipped needs */
if (avctx->skip_frame >= AVDISCARD_ALL)
return avpkt->size;
/**
* Any data between last tile and frame end is vendor-specific metadata:
* [psim record] 4 be32 size + "psim" + pascal string + payload
* <more records, if any>, or:
* [eomd] 4 be32 size=8 + "eomd" fourcc (end of metadata)
* [padding] zero-fill to next-frame alignment
*
* Known records (feel free to extend):
* com.panasonic.Semi-Pro.optical_correction (IEEE doubles):
* R - radial polynomial? + padding: [ k0, k1, k2, k3, pad0, pad1 ]
* G, B: same
* Trailer: optical center in normalized frame coords: [ x, y ]
*/
ret = ff_thread_get_buffer(avctx, frame, 0);
if (ret < 0)
return ret;
s->frame = frame;
ret = ff_hwaccel_frame_priv_alloc(avctx, &s->hwaccel_picture_private);
if (ret < 0)
return ret;
/* Everything the next frame thread needs is known, let it start */
ff_thread_finish_setup(avctx);
/* Start */
if (avctx->hwaccel) {
const FFHWAccel *hwaccel = ffhwaccel(avctx->hwaccel);
ret = hwaccel->start_frame(avctx, avpkt->buf, avpkt->data, avpkt->size);
if (ret < 0)
return ret;
for (int n = 0; n < s->nb_tiles; n++) {
TileContext *tile = &s->tiles[n];
ret = hwaccel->decode_slice(avctx, tile->gb.buffer,
tile->gb.buffer_end - tile->gb.buffer);
if (ret < 0)
return ret;
}
ret = hwaccel->end_frame(avctx);
if (ret < 0)
return ret;
av_refstruct_unref(&s->hwaccel_picture_private);
} else {
avctx->execute2(avctx, decode_tiles, frame, NULL, s->nb_tiles);
}
frame->pict_type = AV_PICTURE_TYPE_I;
frame->flags |= AV_FRAME_FLAG_KEY;
frame->crop_left = crop_l;
frame->crop_right = crop_r;
frame->crop_top = crop_t;
frame->crop_bottom = crop_b;
AVRawColorParams *rcp = av_raw_color_params_create_side_data(frame);
if (!rcp)
return AVERROR(ENOMEM);
rcp->type = AV_RAW_COLOR_PARAMS_PRORES_RAW;
rcp->black_level = av_make_q(black_level, 65535);
rcp->white_level = av_make_q(white_level, 65535);
rcp->wb_cct = wb_cct;
AVProResRawColorParams *pr = &rcp->codec.prores_raw;
pr->wb_red = av_d2q(wb_red, INT_MAX);
pr->wb_blue = av_d2q(wb_blue, INT_MAX);
pr->gain = av_d2q(gain, INT_MAX);
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
pr->color_matrix[r][c] = av_d2q(color_matrix[r][c], INT_MAX);
*got_frame_ptr = 1;
return avpkt->size;
}
static av_cold int decode_end(AVCodecContext *avctx)
{
ProResRAWContext *s = avctx->priv_data;
av_refstruct_unref(&s->hwaccel_picture_private);
av_freep(&s->tiles);
return 0;
}
#if HAVE_THREADS
static int update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
{
ProResRAWContext *rsrc = src->priv_data;
ProResRAWContext *rdst = dst->priv_data;
rdst->pix_fmt = rsrc->pix_fmt;
rdst->version = rsrc->version;
return 0;
}
#endif
const FFCodec ff_prores_raw_decoder = {
.p.name = "prores_raw",
CODEC_LONG_NAME("Apple ProRes RAW"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_PRORES_RAW,
.priv_data_size = sizeof(ProResRAWContext),
.init = decode_init,
.close = decode_end,
FF_CODEC_DECODE_CB(decode_frame),
UPDATE_THREAD_CONTEXT(update_thread_context),
.p.capabilities = AV_CODEC_CAP_DR1 |
AV_CODEC_CAP_FRAME_THREADS |
AV_CODEC_CAP_SLICE_THREADS,
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP |
FF_CODEC_CAP_SKIP_FRAME_FILL_PARAM,
.hw_configs = (const AVCodecHWConfigInternal *const []) {
#if CONFIG_PRORES_RAW_VULKAN_HWACCEL
HWACCEL_VULKAN(prores_raw),
#endif
#if CONFIG_PRORES_RAW_VIDEOTOOLBOX_HWACCEL
HWACCEL_VIDEOTOOLBOX(prores_raw),
#endif
NULL
},
};