|
| 1 | +//****************************************************************************** |
| 2 | +// |
| 3 | +// Copyright (C) 2026 Apple, Inc., All Rights Reserved. |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: Apache-2.0 |
| 6 | +// |
| 7 | +//****************************************************************************** |
| 8 | + |
| 9 | +#include <TwkFB/FastMemcpy.h> |
| 10 | +#include <TwkFB/FastConversion.h> |
| 11 | +#include <assert.h> |
| 12 | + |
| 13 | +extern "C" |
| 14 | +{ |
| 15 | +#include <libavcodec/avcodec.h> |
| 16 | +#include <libavformat/avformat.h> |
| 17 | +#include <libavutil/imgutils.h> |
| 18 | +#include <libavutil/opt.h> |
| 19 | +#include <libavutil/pixdesc.h> |
| 20 | +#include <libavutil/timecode.h> |
| 21 | +#include <libavutil/display.h> |
| 22 | +#include <libswscale/swscale.h> |
| 23 | +} |
| 24 | + |
| 25 | +#include <AppleProRes.h> |
| 26 | + |
| 27 | +// Returns the number of threads to use in the Apple ProRes decoder. |
| 28 | +static int getNumProresThreads() |
| 29 | +{ |
| 30 | + // Only evaluate this value once. |
| 31 | + static int proresDecoderThreads = -1; |
| 32 | + if (proresDecoderThreads == -1) |
| 33 | + { |
| 34 | + const char* evPrefValue = getenv("RV_PREF_GLOBAL_PRORES_DECODER_THREADS"); |
| 35 | + if (evPrefValue && atoi(evPrefValue) >= 0) |
| 36 | + { |
| 37 | + proresDecoderThreads = atoi(evPrefValue); |
| 38 | + } |
| 39 | + else |
| 40 | + { |
| 41 | + // 0 = All available threads according to the number of |
| 42 | + // processors detected in the system |
| 43 | + proresDecoderThreads = 0; |
| 44 | + } |
| 45 | + } |
| 46 | + return proresDecoderThreads; |
| 47 | +} |
| 48 | + |
| 49 | +int AppleProResContext::decode_frame(AVCodecContext* avctx, AVFrame* videoFrame, AVPacket* videoPacket) |
| 50 | +{ |
| 51 | + int ret = 0; |
| 52 | + |
| 53 | + // Read info from the frame header including, width height, and color details. First make sure we have enough |
| 54 | + // bytes for everything we want to read. The colorspace is at offset 24 (16 bytes past the 8 byte header size). |
| 55 | + // Any other errors will be caught when trying to decode the frame, so for now, just check the size. |
| 56 | + if (videoPacket->size < 25) |
| 57 | + { |
| 58 | + return AVERROR(EINVAL); |
| 59 | + } |
| 60 | + // Skip over the first 8 bytes which is the header size |
| 61 | + uint8_t* header_chunk = (uint8_t*)videoPacket->data + 8; |
| 62 | + uint16_t version = readBE(header_chunk + 2); |
| 63 | + uint16_t width = readBE(header_chunk + 8); |
| 64 | + uint16_t height = readBE(header_chunk + 10); |
| 65 | + |
| 66 | + // Color primaries, transfer function, color space, and color range |
| 67 | + videoFrame->color_primaries = static_cast<AVColorPrimaries>(header_chunk[14]); |
| 68 | + videoFrame->color_trc = static_cast<AVColorTransferCharacteristic>(header_chunk[15]); |
| 69 | + videoFrame->colorspace = static_cast<AVColorSpace>(header_chunk[16]); |
| 70 | + videoFrame->color_range = AVCOL_RANGE_MPEG; |
| 71 | + |
| 72 | + videoFrame->width = width; |
| 73 | + videoFrame->height = height; |
| 74 | + videoFrame->format = avctx->pix_fmt; |
| 75 | + avctx->sw_pix_fmt = avctx->pix_fmt; |
| 76 | + |
| 77 | + // Make sure our PixBuf is of the correct format and has sufficient memory/alignment |
| 78 | + int minBytes = PRBytesPerRowNeededInPixelBuffer(videoFrame->width, pixfmt, kPRFullSize); |
| 79 | + if ((prpixbuf.baseAddr == NULL) || (prpixbuf.rowBytes < minBytes) || (prpixbuf.width != videoFrame->width) |
| 80 | + || (prpixbuf.height != videoFrame->height) || (prpixbuf.format != pixfmt)) |
| 81 | + { |
| 82 | + prpixbuf.rowBytes = minBytes; |
| 83 | + prpixbuf.width = videoFrame->width; |
| 84 | + prpixbuf.height = videoFrame->height; |
| 85 | + prpixbuf.format = pixfmt; |
| 86 | + if (prpixbuf.baseAddr) |
| 87 | + { |
| 88 | + av_free(prpixbuf.baseAddr); |
| 89 | + prpixbuf.baseAddr = NULL; |
| 90 | + } |
| 91 | + prpixbuf.baseAddr = (unsigned char*)av_malloc(prpixbuf.rowBytes * prpixbuf.height); |
| 92 | + if (NULL == prpixbuf.baseAddr) |
| 93 | + { |
| 94 | + return AVERROR(ENOMEM); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + // Make sure we have memory in which to decode. We assume that if the |
| 99 | + // linesize[0] isn't initialized that we haven't gotten the buffer, yet. |
| 100 | + if (!videoFrame->linesize[0]) |
| 101 | + { |
| 102 | + av_frame_get_buffer(videoFrame, 0); |
| 103 | + } |
| 104 | + |
| 105 | + // Allocate a decoder if we don't have one |
| 106 | + if (!prdecoder) |
| 107 | + { |
| 108 | + prdecoder = PROpenDecoder(getNumProresThreads(), NULL); |
| 109 | + if (NULL == prdecoder) |
| 110 | + { |
| 111 | + return AVERROR(ENOMEM); |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + // Now decode the frame |
| 116 | + // PRDecodeFrame returns the number of bytes consumed from the compressed |
| 117 | + // buffer, which may be less than videoPacket->size because the container |
| 118 | + // (MOV, MXF, MKV, etc.) can report a larger sample size than the actual |
| 119 | + // ProRes frame. A negative return value indicates a decode error. |
| 120 | + int bytesDecoded = PRDecodeFrame(prdecoder, videoPacket->data, videoPacket->size, &prpixbuf, kPRFullSize, 0); |
| 121 | + if (bytesDecoded <= 0) |
| 122 | + { |
| 123 | + av_log(avctx, AV_LOG_ERROR, "PRDecodeFrame failed: returned %d (packet size %d)\n", bytesDecoded, videoPacket->size); |
| 124 | + return AVERROR(EINVAL); |
| 125 | + } |
| 126 | + else |
| 127 | + { |
| 128 | + // We're assuming v216 is used for 4:2:2 formats and y416 for 4:4:4:4 formats. Convert from the packed format that |
| 129 | + // ProRes uses to the planar format that are used elsewhere by FFMpeg |
| 130 | + if (pixfmt == kPRFormat_v216) |
| 131 | + { |
| 132 | + packedUYVY16_to_planarYUV16_MP(minBytes, videoFrame->height, reinterpret_cast<uint16_t*>(prpixbuf.baseAddr), |
| 133 | + reinterpret_cast<uint16_t*>(videoFrame->data[0]), |
| 134 | + reinterpret_cast<uint16_t*>(videoFrame->data[1]), |
| 135 | + reinterpret_cast<uint16_t*>(videoFrame->data[2]), videoFrame->linesize[0], |
| 136 | + videoFrame->linesize[1], videoFrame->linesize[2]); |
| 137 | + } |
| 138 | + else if (pixfmt == kPRFormat_y416) |
| 139 | + { |
| 140 | + packedUVYA16_to_planarYUVA16_MP( |
| 141 | + minBytes, videoFrame->height, reinterpret_cast<uint64_t*>(prpixbuf.baseAddr), |
| 142 | + reinterpret_cast<uint16_t*>(videoFrame->data[0]), reinterpret_cast<uint16_t*>(videoFrame->data[1]), |
| 143 | + reinterpret_cast<uint16_t*>(videoFrame->data[2]), reinterpret_cast<uint16_t*>(videoFrame->data[3]), videoFrame->linesize[0], |
| 144 | + videoFrame->linesize[1], videoFrame->linesize[2], videoFrame->linesize[3]); |
| 145 | + } |
| 146 | + else |
| 147 | + { |
| 148 | + av_log(avctx, AV_LOG_ERROR, "Expected either v216 (value %d) or y416 (value %d) format but got %d\n", kPRFormat_v216, |
| 149 | + kPRFormat_y416, pixfmt); |
| 150 | + assert(0); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + av_packet_unref(videoPacket); |
| 155 | + |
| 156 | + return 0; |
| 157 | +} |
0 commit comments