FFmpeg
ocio_wrapper.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2026 Sam Richards
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 
22 #include <OpenColorIO/OpenColorIO.h>
23 #include <exception>
24 
25 namespace OCIO = OCIO_NAMESPACE;
26 
27 struct OCIOState {
28  OCIO::ConstConfigRcPtr config;
29  OCIO::ConstProcessorRcPtr processor;
30  OCIO::ConstCPUProcessorRcPtr cpu;
31  int channels;
32 };
33 
34 extern "C" {
35 
36 #include "formats.h"
37 #include "ocio_wrapper.hpp"
38 #include <libavutil/frame.h>
39 #include <libavutil/pixdesc.h>
40 #include <libavutil/dict.h>
41 
42 // Helper to map AV_PIX_FMT to OCIO BitDepth
43 static OCIO::BitDepth get_ocio_depth(int format)
44 {
45  switch (format) {
46  case AV_PIX_FMT_RGB24:
47  case AV_PIX_FMT_RGBA:
48  return OCIO::BIT_DEPTH_UINT8;
49 
50  case AV_PIX_FMT_RGB48:
51  case AV_PIX_FMT_RGBA64:
52  return OCIO::BIT_DEPTH_UINT16;
53 
54  case AV_PIX_FMT_GBRP10:
55  case AV_PIX_FMT_GBRAP10:
56  return OCIO::BIT_DEPTH_UINT10;
57 
58  case AV_PIX_FMT_GBRP12:
59  case AV_PIX_FMT_GBRAP12:
60  return OCIO::BIT_DEPTH_UINT12;
61 
62  // Note: FFmpeg treats half-float as specific types often requiring casts.
63  // For this snippet we map F16 directly if your system supports it,
64  // otherwise, standard float (F32) is safer.
65  case AV_PIX_FMT_GBRPF16:
67  return OCIO::BIT_DEPTH_F16;
68 
69  case AV_PIX_FMT_GBRPF32:
71  return OCIO::BIT_DEPTH_F32;
72 
73  default:
74  return OCIO::BIT_DEPTH_UNKNOWN;
75  }
76 }
77 
78 static OCIO::ConstContextRcPtr add_context_params(OCIO::ConstConfigRcPtr config, AVDictionary *params)
79 {
80 
81  OCIO::ConstContextRcPtr context = config->getCurrentContext();
82  if (!params)
83  return context;
84  if (!context)
85  return nullptr;
86 
87  OCIO::ContextRcPtr ctx = context->createEditableCopy();
88  if (!ctx) {
89  return context;
90  }
91  const AVDictionaryEntry *e = NULL;
92  while ((e = av_dict_iterate(params, e))) {
93  ctx->setStringVar(e->key, e->value);
94  }
95  return ctx;
96 }
97 
100  const char *input_color_space,
101  const char *output_color_space,
102  AVDictionary *params)
103 {
104  try {
105  OCIOState *s = new OCIOState();
106  if (!config_path)
107  s->config = OCIO::Config::CreateFromEnv();
108  else
109  s->config = OCIO::Config::CreateFromFile(config_path);
110 
111  if (!s->config || !input_color_space || !output_color_space) {
112  av_log(ctx, AV_LOG_ERROR, "Error: Config or color spaces invalid.\n");
113  if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n");
114  if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n");
115  if (!output_color_space) av_log(ctx, AV_LOG_ERROR, "Output color space is null\n");
116  delete s;
117  return nullptr;
118  }
119 
120  // ColorSpace Transform: InputSpace -> OutputSpace
121  OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create();
122  cst->setSrc(input_color_space);
123  cst->setDst(output_color_space);
124  auto context = add_context_params(s->config, params);
125  s->processor = s->config->getProcessor(context, cst, OCIO::TRANSFORM_DIR_FORWARD);
126 
127  return (OCIOHandle)s;
128  } catch (OCIO::Exception &e) {
129  av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Error in create_output_colorspace_processor: %s\n", e.what());
130  return nullptr;
131  } catch (...) {
132  av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Unknown Error in create_output_colorspace_processor\n");
133  return nullptr;
134  }
135 }
136 
138  const char *config_path,
139  const char *input_color_space,
140  const char *display,
141  const char *view, int inverse,
142  AVDictionary *params)
143 {
144  try {
145 
146  OCIOState *s = new OCIOState();
147  if (!config_path)
148  s->config = OCIO::Config::CreateFromEnv();
149  else
150  s->config = OCIO::Config::CreateFromFile(config_path);
151 
152  if (!s->config || !input_color_space || !display || !view) {
153  av_log(ctx, AV_LOG_ERROR, "Error: Config or arguments invalid.\n");
154  if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n");
155  if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n");
156  if (!display) av_log(ctx, AV_LOG_ERROR, "Display is null\n");
157  if (!view) av_log(ctx, AV_LOG_ERROR, "View is null\n");
158  delete s;
159  return nullptr;
160  }
161 
162  // Display/View Transform: InputSpace -> Display/View
163  OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create();
164  dv->setSrc(input_color_space);
165  dv->setDisplay(display);
166  dv->setView(view);
167  OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD;
168  if (inverse)
169  direction = OCIO::TRANSFORM_DIR_INVERSE;
170  OCIO::ConstContextRcPtr context = add_context_params(s->config, params);
171  s->processor = s->config->getProcessor(context, dv, direction);
172 
173  return (OCIOHandle)s;
174  } catch (OCIO::Exception &e) {
175  av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_display_view_processor: %s\n", e.what());
176  return nullptr;
177  } catch (...) {
178  av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_display_view_processor\n");
179  return nullptr;
180  }
181 }
182 
184  const char *file_transform,
185  int inverse)
186 {
187  try {
188  if (!file_transform) {
189  av_log(ctx, AV_LOG_ERROR, "File transform is null\n");
190  return nullptr;
191  }
192  OCIOState *s = new OCIOState();
193 
194  // File Transform: InputSpace -> FileTransform -> OutputSpace
195  OCIO::FileTransformRcPtr ft = OCIO::FileTransform::Create();
196  ft->setSrc(file_transform);
197  OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD;
198  if (inverse)
199  direction = OCIO::TRANSFORM_DIR_INVERSE;
200  s->config = OCIO::Config::Create();
201  s->processor = s->config->getProcessor(ft, direction);
202 
203  return (OCIOHandle)s;
204  } catch (OCIO::Exception &e) {
205  av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_file_transform_processor: %s\n", e.what());
206  return nullptr;
207  } catch (...) {
208  av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_file_transform_processor\n");
209  return nullptr;
210  }
211 }
212 
213 // In ocio_wrapper.cpp
214 int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format,
215  int output_format)
216 {
217  try {
218  OCIOState *s = (OCIOState *)handle;
219  if (!s || !s->processor)
220  return -1;
221 
222  s->cpu = s->processor->getOptimizedCPUProcessor(
224  OCIO::OPTIMIZATION_DEFAULT);
225 
226  return 0;
227  } catch (OCIO::Exception &e) {
228  av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", e.what());
229  return -1;
230  } catch (...) {
231  av_log(ctx, AV_LOG_ERROR, "Unknown error in ocio_finalize_processor\n");
232  return -1;
233  }
234 }
235 
236 static OCIO::ImageDesc *AVFrame2ImageDescSlice(AVFrame *frame, int y_start,
237  int height)
238 {
239  OCIO::BitDepth ocio_bitdepth = get_ocio_depth(frame->format);
240  if (ocio_bitdepth == OCIO::BIT_DEPTH_UNKNOWN) {
241  throw std::runtime_error("Unsupported pixel format for OCIO processing");
242  }
243 
244  int stridex = frame->linesize[0];
245  const AVPixFmtDescriptor *desc =
246  av_pix_fmt_desc_get((enum AVPixelFormat)frame->format);
247  if (!desc) {
248  throw std::runtime_error("Invalid pixel format descriptor");
249  }
250 
251  bool is_planar = desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR);
252 
253  if (is_planar) {
254  // For planar, we need to offset each plane
255  uint8_t *red = frame->data[2] + y_start * frame->linesize[2];
256  uint8_t *green = frame->data[0] + y_start * frame->linesize[0];
257  uint8_t *blue = frame->data[1] + y_start * frame->linesize[1];
258  uint8_t *alpha = (desc->nb_components == 4)
259  ? (frame->data[3] + y_start * frame->linesize[3])
260  : nullptr;
261 
262  return new OCIO::PlanarImageDesc(
263  (void *)red, (void *)green, (void *)blue, (void *)alpha, frame->width,
264  height, ocio_bitdepth, desc->comp[0].step, stridex);
265  }
266 
267  uint8_t *data = frame->data[0] + y_start * frame->linesize[0];
268  // Note we are assuming that these are RGB or RGBA channel ordering.
269  // And are also likely to be integer.
270  return new OCIO::PackedImageDesc(
271  (void *)data, frame->width, height, desc->nb_components, ocio_bitdepth,
272  desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0]);
273 }
274 
276  int y_start, int height)
277 {
278  OCIOState *s = (OCIOState *)handle;
279  if (!s || !s->cpu)
280  return -1;
281 
282  try {
283  if (input_frame == output_frame) {
284  OCIO::ImageDesc *imgDesc = AVFrame2ImageDescSlice(input_frame, y_start, height);
285  s->cpu->apply(*imgDesc);
286  delete imgDesc;
287  return 0;
288  }
289 
290  OCIO::ImageDesc *input = AVFrame2ImageDescSlice(input_frame, y_start, height);
291  OCIO::ImageDesc *output = AVFrame2ImageDescSlice(output_frame, y_start, height);
292  s->cpu->apply(*input, *output);
293 
294  delete input;
295  delete output;
296  return 0;
297  } catch (const OCIO::Exception &ex) {
298  av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", ex.what());
299  return -2; // or another error code
300  } catch (const std::exception &ex) {
301  av_log(ctx, AV_LOG_ERROR, "OCIO error: Standard exception: %s\n", ex.what());
302  return -3;
303  } catch (...) {
304  av_log(ctx, AV_LOG_ERROR, "OCIO error: Unknown error in OCIO processing.\n");
305  return -4;
306  }
307 }
308 
310 {
311  if (!handle)
312  return;
313  delete (OCIOState *)handle;
314 }
315 
316 } // extern "C"
AVPixelFormat
AVPixelFormat
Pixel format.
Definition: pixfmt.h:71
OCIOState
Definition: ocio_wrapper.cpp:27
inverse
inverse
Definition: af_crystalizer.c:122
av_pix_fmt_desc_get
const AVPixFmtDescriptor * av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt)
Definition: pixdesc.c:3456
OCIOState::cpu
OCIO::ConstCPUProcessorRcPtr cpu
Definition: ocio_wrapper.cpp:30
output
filter_frame For filters that do not use the this method is called when a frame is pushed to the filter s input It can be called at any time except in a reentrant way If the input frame is enough to produce output
Definition: filter_design.txt:226
AVFrame
This structure describes decoded (raw) audio or video data.
Definition: frame.h:427
pixdesc.h
data
const char data[16]
Definition: mxf.c:149
ocio_apply
int ocio_apply(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame, int y_start, int height)
Definition: ocio_wrapper.cpp:275
AVDictionary
Definition: dict.c:32
tf_sess_config.config
config
Definition: tf_sess_config.py:33
formats.h
AV_PIX_FMT_GBRP10
#define AV_PIX_FMT_GBRP10
Definition: pixfmt.h:558
ocio_wrapper.hpp
ocio_create_output_colorspace_processor
OCIOHandle ocio_create_output_colorspace_processor(AVFilterContext *ctx, const char *config_path, const char *input_color_space, const char *output_color_space, AVDictionary *params)
Definition: ocio_wrapper.cpp:99
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
AV_PIX_FMT_GBRAP10
#define AV_PIX_FMT_GBRAP10
Definition: pixfmt.h:562
s
#define s(width, name)
Definition: cbs_vp9.c:198
AV_PIX_FMT_GBRAP12
#define AV_PIX_FMT_GBRAP12
Definition: pixfmt.h:563
AVDictionaryEntry::key
char * key
Definition: dict.h:91
OCIOState::config
OCIO::ConstConfigRcPtr config
Definition: ocio_wrapper.cpp:28
ctx
static AVFormatContext * ctx
Definition: movenc.c:49
AV_PIX_FMT_RGBA
@ AV_PIX_FMT_RGBA
packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
Definition: pixfmt.h:100
context
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf default minimum maximum flags name is the option keep it simple and lowercase description are in without and describe what they for example set the foo of the bar offset is the offset of the field in your context
Definition: writing_filters.txt:91
AV_PIX_FMT_RGBA64
#define AV_PIX_FMT_RGBA64
Definition: pixfmt.h:529
NULL
#define NULL
Definition: coverity.c:32
format
New swscale design to change SwsGraph is what coordinates multiple passes These can include cascaded scaling error diffusion and so on Or we could have separate passes for the vertical and horizontal scaling In between each SwsPass lies a fully allocated image buffer Graph passes may have different levels of e g we can have a single threaded error diffusion pass following a multi threaded scaling pass SwsGraph is internally recreated whenever the image format
Definition: swscale-v2.txt:14
AV_PIX_FMT_GBRPF16
#define AV_PIX_FMT_GBRPF16
Definition: pixfmt.h:576
AV_PIX_FMT_RGB24
@ AV_PIX_FMT_RGB24
packed RGB 8:8:8, 24bpp, RGBRGB...
Definition: pixfmt.h:75
height
#define height
Definition: dsp.h:89
AV_PIX_FMT_GBRPF32
#define AV_PIX_FMT_GBRPF32
Definition: pixfmt.h:578
AV_PIX_FMT_RGB48
#define AV_PIX_FMT_RGB48
Definition: pixfmt.h:525
frame.h
ocio_create_file_transform_processor
OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx, const char *file_transform, int inverse)
Definition: ocio_wrapper.cpp:183
AVFrame2ImageDescSlice
static OCIO::ImageDesc * AVFrame2ImageDescSlice(AVFrame *frame, int y_start, int height)
Definition: ocio_wrapper.cpp:236
OCIOState::processor
OCIO::ConstProcessorRcPtr processor
Definition: ocio_wrapper.cpp:29
output_frame
static int output_frame(H264Context *h, AVFrame *dst, H264Picture *srcp)
Definition: h264dec.c:861
input
and forward the test the status of outputs and forward it to the corresponding return FFERROR_NOT_READY If the filters stores internally one or a few frame for some input
Definition: filter_design.txt:172
ocio_create_display_view_processor
OCIOHandle ocio_create_display_view_processor(AVFilterContext *ctx, const char *config_path, const char *input_color_space, const char *display, const char *view, int inverse, AVDictionary *params)
Definition: ocio_wrapper.cpp:137
AV_PIX_FMT_GBRP12
#define AV_PIX_FMT_GBRP12
Definition: pixfmt.h:559
frame
these buffered frames must be flushed immediately if a new input produces new the filter must not call request_frame to get more It must just process the frame or queue it The task of requesting more frames is left to the filter s request_frame method or the application If a filter has several the filter must be ready for frames arriving randomly on any input any filter with several inputs will most likely require some kind of queuing mechanism It is perfectly acceptable to have a limited queue and to drop frames when the inputs are too unbalanced request_frame For filters that do not use the this method is called when a frame is wanted on an output For a it should directly call filter_frame on the corresponding output For a if there are queued frames already one of these frames should be pushed If the filter should request a frame on one of its repeatedly until at least one frame has been pushed Return or at least make progress towards producing a frame
Definition: filter_design.txt:265
dict.h
AV_PIX_FMT_GBRAPF32
#define AV_PIX_FMT_GBRAPF32
Definition: pixfmt.h:579
AV_PIX_FMT_GBRAPF16
#define AV_PIX_FMT_GBRAPF16
Definition: pixfmt.h:577
output_format
static char * output_format
Definition: ffprobe.c:144
AV_PIX_FMT_FLAG_PLANAR
#define AV_PIX_FMT_FLAG_PLANAR
At least one pixel component is not in the first data plane.
Definition: pixdesc.h:132
AVFilterContext
An instance of a filter.
Definition: avfilter.h:274
OCIOHandle
void * OCIOHandle
Definition: ocio_wrapper.hpp:28
desc
const char * desc
Definition: libsvtav1.c:78
add_context_params
static OCIO::ConstContextRcPtr add_context_params(OCIO::ConstConfigRcPtr config, AVDictionary *params)
Definition: ocio_wrapper.cpp:78
AVPixFmtDescriptor
Descriptor that unambiguously describes how the bits of a pixel are stored in the up to 4 data planes...
Definition: pixdesc.h:69
AVDictionaryEntry
Definition: dict.h:90
ocio_destroy_processor
void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle)
Definition: ocio_wrapper.cpp:309
alpha
static const int16_t alpha[]
Definition: ilbcdata.h:55
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
get_ocio_depth
static OCIO::BitDepth get_ocio_depth(int format)
Definition: ocio_wrapper.cpp:43
AVDictionaryEntry::value
char * value
Definition: dict.h:92
OCIOState::channels
int channels
Definition: ocio_wrapper.cpp:31
av_dict_iterate
const AVDictionaryEntry * av_dict_iterate(const AVDictionary *m, const AVDictionaryEntry *prev)
Iterate over a dictionary.
Definition: dict.c:42
ocio_finalize_processor
int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format, int output_format)
Definition: ocio_wrapper.cpp:214