Skip to content

Commit 0b2ccef

Browse files
committed
feat: Add support for Apple ProRes decode SDK on Linux and Windows
This change enables support for building in ProRes decode using the ProRes SDK provided by Apple (get freely by e-mailing prores@apple.com). The SDK is used only on Linux and Windows and is enabled by adding -DRV_DEPS_APPLE_PRORES_SDK_ZIP_PATH='<full path to SDK .zip file>' and -DRV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE="prores" to the rvcfg command. The FFMPEG bit is needed to enable FFMPEG to allow the file type. The decoding is then done by calling out to the SDK from within MovieFFMpeg.cpp file via small wrappers in the newly added AppleProRes.cpp/.h files. Tested on Linux (Ubuntu 2024.04.1 LTS) and Windows 11. Also verified that macOS, which doesn't use this path, still builds and functions correctly. Signed-off-by: Dean P. Macri <dean.p.macri@gmail.com>
1 parent 4c487b1 commit 0b2ccef

7 files changed

Lines changed: 503 additions & 21 deletions

File tree

cmake/dependencies/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ INCLUDE(yaml-cpp) # depends on OCIO
100100
INCLUDE(bmd.cmake)
101101
INCLUDE(spdlog)
102102
INCLUDE(aja.cmake)
103+
INCLUDE(prores.cmake)
103104

104105
LIST(REMOVE_DUPLICATES RV_DEPS_LIST)
105106
SET(RV_DEPS_LIST
@@ -112,6 +113,9 @@ MESSAGE(STATUS "Using atomic_ops: ${RV_DEPS_ATOMIC_OPS_VERSION}")
112113
IF(RV_DEPS_BMD_VERSION)
113114
MESSAGE(STATUS "Using BMD: ${RV_DEPS_BMD_VERSION}")
114115
ENDIF()
116+
IF(RV_DEPS_APPLE_PRORES_VERSION)
117+
MESSAGE(STATUS "Using Apple ProRes: ${RV_DEPS_APPLE_PRORES_VERSION}")
118+
ENDIF()
115119
MESSAGE(STATUS "Using Boost: ${RV_DEPS_BOOST_VERSION}")
116120
MESSAGE(STATUS "Using Dav1d: ${RV_DEPS_DAV1D_VERSION}")
117121
MESSAGE(STATUS "Using FFMPEG: ${RV_DEPS_FFMPEG_VERSION}")

cmake/dependencies/prores.cmake

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#
2+
# Copyright (C) 2024 Autodesk, Inc. All Rights Reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
INCLUDE(ProcessorCount) # require CMake 3.15+
8+
PROCESSORCOUNT(_cpu_count)
9+
10+
SET(_target
11+
"RV_DEPS_APPLE"
12+
)
13+
14+
SET(RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH
15+
""
16+
CACHE STRING "Path to ProRes SDK from Apple"
17+
)
18+
19+
IF(NOT RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH)
20+
MESSAGE(
21+
WARNING
22+
"ProRes SDK path not specified, disabling ProRes support.\nContact Apple at prores@apple.com to obtain the free SDK. Then set RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH to the path of the received zip file on the rvcfg line.\nExample:\nrvcfg -DRV_DEPS_APPLE_PRORES_SDK_ZIP_PATH='<downloads_path>/ProResDecoder_Linux_x86_64-15B54.zip'"
23+
)
24+
RETURN()
25+
ENDIF()
26+
27+
STRING(
28+
REGEX
29+
REPLACE ".*[-_]([0-9A-Z]+)\\.(zip|tgz)" "\\1" _version ${RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH}
30+
)
31+
32+
SET(_install_dir
33+
${RV_DEPS_BASE_DIR}/${_target}/prores
34+
)
35+
SET(_include_dir
36+
${_install_dir}
37+
)
38+
SET(_lib_dir
39+
${_install_dir}
40+
)
41+
42+
SET(_prores_lib_name
43+
${CMAKE_STATIC_LIBRARY_PREFIX}ProResDecoder${CMAKE_STATIC_LIBRARY_SUFFIX}
44+
)
45+
46+
SET(_install_command
47+
""
48+
)
49+
50+
SET(_download_name
51+
${_target}_prores_${_version}.zip
52+
)
53+
SET(_source_dir
54+
${RV_DEPS_BASE_DIR}/${_target}/prores
55+
)
56+
57+
SET(_prores_lib
58+
${_lib_dir}/${_prores_lib_name}
59+
)
60+
61+
EXTERNALPROJECT_ADD(
62+
${_target}
63+
URL ${RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH}
64+
DOWNLOAD_NAME ${_download_name}
65+
DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR}
66+
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
67+
SOURCE_DIR ${_source_dir}
68+
INSTALL_DIR ${_install_dir}
69+
CONFIGURE_COMMAND ""
70+
BUILD_COMMAND ""
71+
INSTALL_COMMAND ""
72+
BUILD_BYPRODUCTS ${_prores_lib}
73+
)
74+
75+
ADD_LIBRARY(Apple::ProRes STATIC IMPORTED GLOBAL)
76+
TARGET_INCLUDE_DIRECTORIES(
77+
Apple::ProRes
78+
INTERFACE ${_include_dir}
79+
)
80+
SET_PROPERTY(
81+
TARGET Apple::ProRes
82+
PROPERTY IMPORTED_LOCATION ${_prores_lib}
83+
)
84+
85+
SET(RV_DEPS_APPLE_PRORES_VERSION_INCLUDE_DIR
86+
${_include_dir}
87+
CACHE STRING "Path to installed includes for ${_target}"
88+
)
89+
ADD_DEPENDENCIES(Apple::ProRes ${_prores_lib})
90+
IF(RV_TARGET_WINDOWS)
91+
ADD_DEPENDENCIES(Apple::ProRes ${_target})
92+
ENDIF()
93+
94+
SET(RV_DEPS_APPLE_PRORES_VERSION
95+
${_version}
96+
CACHE INTERNAL "" FORCE
97+
)

docs/build_system/config_common_build.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,19 +214,34 @@ For example:
214214
rvcfg -DRV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE="aac;hevc" -DRV_FFMPEG_NON_FREE_ENCODERS_TO_ENABLE="aac"
215215
```
216216
217-
### 11. Apple ProRes on Apple Silicon
217+
### 11. Apple ProRes
218218
219-
On Apple Silicon machines, Open RV supports hardware decoding through Apple's VideoToolbox framework. This feature is enabled by default but can be controlled using the `-DRV_FFMPEG_USE_VIDEOTOOLBOX` option. Set this option to `ON` to enable or `OFF` to disable VideoToolbox hardware decoding.
219+
If you have Apple's ProRes SDK for Windows or Linux (you can obtain freely by contacting [ProRes@apple.com](mailto:ProRes@apple.com)),
220+
then set ```RV_DEPS_APPLE_PRORES_SDK_ZIP_PATH``` to the path of the downloaded zip file on the rvcfg line.<br>
221+
Example:
222+
223+
```bash
224+
-DRV_DEPS_APPLE_PRORES_SDK_ZIP_PATH='<downloads_path>/ProResDecoder_Linux_x86_64-15B54.zip'
225+
```
226+
227+
By default, the ProRes decode via the SDK will use all available system threads. To use a fixed maximum number of threads, set the
228+
environment variable `RV_PREF_GLOBAL_PRORES_DECODER_THREADS` to a positive value.
229+
230+
On Apple Silicon machines, OpenRV supports hardware decoding through Apple's VideoToolbox framework. This feature is enabled by default
231+
but can be controlled using the `-DRV_FFMPEG_USE_VIDEOTOOLBOX` option. Set this option to `ON` to enable or `OFF` to disable VideoToolbox
232+
hardware decoding.
220233
221234
To enable decoding of ProRes media files, you must also specify the following option during the configuration step:
222235
223236
```bash
224237
-DRV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE="prores"
225238
```
226239
227-
Note that you should always have `-DRV_FFMPEG_USE_VIDEOTOOLBOX` enabled when decoding Apple ProRes videos on Apple Silicon machines. Failure to do so will result in performance issues and is not compliant with Apple's licensing requirements.
240+
Note that you should always have `-DRV_FFMPEG_USE_VIDEOTOOLBOX` enabled when decoding Apple ProRes videos on Apple Silicon machines.
241+
Failure to do so will result in performance issues and is not compliant with Apple's licensing requirements.
228242

229-
**Important:** Before enabling ProRes decoding, you are required to obtain a proper license agreement from Apple by contacting [ProRes@apple.com](mailto:ProRes@apple.com).
243+
**Important:** Before enabling ProRes decoding on Linux/Windows, you are required to obtain a proper license agreement and the SDK from
244+
Apple by contacting [ProRes@apple.com](mailto:ProRes@apple.com).
230245

231246
### 12. Running the automated tests
232247

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//******************************************************************************
2+
//
3+
// Copyright (C) 2026 Apple, Inc., All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
//******************************************************************************
8+
9+
#include <ProResDecoder.h>
10+
11+
struct AppleProResContext
12+
{
13+
PRPixelBuffer prpixbuf;
14+
PRDecoderRef prdecoder;
15+
PRPixelFormat pixfmt;
16+
AVPixelFormat avPixelFormat;
17+
18+
void clearPRPixBuf() { memset(&prpixbuf, 0, sizeof(prpixbuf)); }
19+
20+
AppleProResContext()
21+
{
22+
clearPRPixBuf();
23+
prdecoder = nullptr;
24+
pixfmt = kPRFormat_v216;
25+
}
26+
27+
~AppleProResContext()
28+
{
29+
if (prpixbuf.baseAddr)
30+
{
31+
av_freep(prpixbuf.baseAddr);
32+
}
33+
if (prdecoder)
34+
{
35+
PRCloseDecoder(prdecoder);
36+
}
37+
}
38+
39+
AppleProResContext(const AppleProResContext&) = delete;
40+
41+
AppleProResContext& operator=(const AppleProResContext& other)
42+
{
43+
if (this == &other)
44+
{
45+
return *this;
46+
}
47+
pixfmt = other.pixfmt;
48+
avPixelFormat = other.avPixelFormat;
49+
clearPRPixBuf();
50+
prdecoder = nullptr;
51+
return *this;
52+
}
53+
54+
AppleProResContext(AppleProResContext&&) = delete;
55+
AppleProResContext& operator=(AppleProResContext&&) = delete;
56+
57+
// Reads 2 big-Endian bytes from memory and returns a uint16_t
58+
uint16_t readBE(uint8_t* data)
59+
{
60+
uint16_t res = (((uint16_t)(*data)) << 8) | (uint16_t)(*(data + 1));
61+
return res;
62+
}
63+
64+
int decode_frame(AVCodecContext* avctx, AVFrame* videoFrame, AVPacket* videoPacket);
65+
};

0 commit comments

Comments
 (0)