Skip to content

Commit 31f62e4

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.
1 parent b950e1d commit 31f62e4

7 files changed

Lines changed: 500 additions & 21 deletions

File tree

cmake/dependencies/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ INCLUDE(yaml-cpp) # depends on OCIO
9696
INCLUDE(bmd.cmake)
9797
INCLUDE(spdlog)
9898
INCLUDE(aja.cmake)
99+
INCLUDE(prores.cmake)
99100

100101
LIST(REMOVE_DUPLICATES RV_DEPS_LIST)
101102
SET(RV_DEPS_LIST
@@ -108,6 +109,9 @@ MESSAGE(STATUS "Using atomic_ops: ${RV_DEPS_ATOMIC_OPS_VERSION}")
108109
IF(RV_DEPS_BMD_VERSION)
109110
MESSAGE(STATUS "Using BMD: ${RV_DEPS_BMD_VERSION}")
110111
ENDIF()
112+
IF(RV_DEPS_APPLE_PRORES_VERSION)
113+
MESSAGE(STATUS "Using Apple ProRes: ${RV_DEPS_APPLE_PRORES_VERSION}")
114+
ENDIF()
111115
MESSAGE(STATUS "Using Boost: ${RV_DEPS_BOOST_VERSION}")
112116
MESSAGE(STATUS "Using Dav1d: ${RV_DEPS_DAV1D_VERSION}")
113117
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 `ORV_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: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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("ORV_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+
int bytesDecoded = PRDecodeFrame(prdecoder, videoPacket->data, videoPacket->size, &prpixbuf, kPRFullSize, 0);
117+
if (bytesDecoded != videoPacket->size)
118+
{
119+
av_log(avctx, AV_LOG_ERROR, "Expected to decode %d bytes but decoded %d\n", videoPacket->size, bytesDecoded);
120+
assert(0);
121+
return AVERROR(EINVAL);
122+
}
123+
else
124+
{
125+
// 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
126+
// ProRes uses to the planar format that are used elsewhere by FFMpeg
127+
if (pixfmt == kPRFormat_v216)
128+
{
129+
packedUYVY16_to_planarYUV16_MP(minBytes, videoFrame->height, reinterpret_cast<uint16_t*>(prpixbuf.baseAddr),
130+
reinterpret_cast<uint16_t*>(videoFrame->data[0]),
131+
reinterpret_cast<uint16_t*>(videoFrame->data[1]),
132+
reinterpret_cast<uint16_t*>(videoFrame->data[2]), videoFrame->linesize[0],
133+
videoFrame->linesize[1], videoFrame->linesize[2]);
134+
}
135+
else if (pixfmt == kPRFormat_y416)
136+
{
137+
packedUVYA16_to_planarYUVA16_MP(
138+
minBytes, videoFrame->height, reinterpret_cast<uint64_t*>(prpixbuf.baseAddr),
139+
reinterpret_cast<uint16_t*>(videoFrame->data[0]), reinterpret_cast<uint16_t*>(videoFrame->data[1]),
140+
reinterpret_cast<uint16_t*>(videoFrame->data[2]), reinterpret_cast<uint16_t*>(videoFrame->data[3]), videoFrame->linesize[0],
141+
videoFrame->linesize[1], videoFrame->linesize[2], videoFrame->linesize[3]);
142+
}
143+
else
144+
{
145+
av_log(avctx, AV_LOG_ERROR, "Expected either v216 (value %d) or y416 (value %d) format but got %d\n", kPRFormat_v216,
146+
kPRFormat_y416, pixfmt);
147+
assert(0);
148+
}
149+
}
150+
151+
av_packet_unref(videoPacket);
152+
153+
return 0;
154+
}
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)