You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-04-06 09:34:36 +02:00
Initial checkin of OCIO filter.
Initial checkin of OCIO filter. Initial checkin of OCIO filter. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Change for the right C++ library, should work on linux too. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Adding inverse when using display/view. Removed comments. Removed code that was setting the CICP values. Hopefully this can be done through OCIO at some point. Config cleanup - need a modified require_cpp to handle namespacing. Switch to using require_cpp so that namespace can be used. Adding documentation. Sadly a bit of linting went in here, but more importantly added a threads option to split the image into horizontal tiles, since OCIO was running rather slow. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Adding context parameters. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Add the OCIO config parameter. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Make the min threads 1 for now, reserve 0 for later if we can automatically pick something. Also added a few comments. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> This is using ffmpeg-slicing. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Adding OCIO filetransform. Making sure everything is using av_log rather than std::cerr. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Updating the tests so they would work without additional files. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Adding the file-transform documentation. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Adding copyright/license info. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Removing tests, since this is optional code. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Code cleanup. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Typo. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> I went the wrong way, av_log is expecting \n Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Fix indenting to 4 spaces. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Fixing lint issues and a spelling mistake. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Code formatting cleanup to match conventions. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org> Whitespace removal. Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
This commit is contained in:
committed by
Lynne
parent
a7522f3fef
commit
677cf95ea4
316
libavfilter/ocio_wrapper.cpp
Normal file
316
libavfilter/ocio_wrapper.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Sam Richards
|
||||
*
|
||||
* 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 <OpenColorIO/OpenColorIO.h>
|
||||
#include <exception>
|
||||
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
|
||||
struct OCIOState {
|
||||
OCIO::ConstConfigRcPtr config;
|
||||
OCIO::ConstProcessorRcPtr processor;
|
||||
OCIO::ConstCPUProcessorRcPtr cpu;
|
||||
int channels;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
#include "formats.h"
|
||||
#include "ocio_wrapper.hpp"
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/dict.h>
|
||||
|
||||
// Helper to map AV_PIX_FMT to OCIO BitDepth
|
||||
static OCIO::BitDepth get_ocio_depth(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AV_PIX_FMT_RGB24:
|
||||
case AV_PIX_FMT_RGBA:
|
||||
return OCIO::BIT_DEPTH_UINT8;
|
||||
|
||||
case AV_PIX_FMT_RGB48:
|
||||
case AV_PIX_FMT_RGBA64:
|
||||
return OCIO::BIT_DEPTH_UINT16;
|
||||
|
||||
case AV_PIX_FMT_GBRP10:
|
||||
case AV_PIX_FMT_GBRAP10:
|
||||
return OCIO::BIT_DEPTH_UINT10;
|
||||
|
||||
case AV_PIX_FMT_GBRP12:
|
||||
case AV_PIX_FMT_GBRAP12:
|
||||
return OCIO::BIT_DEPTH_UINT12;
|
||||
|
||||
// Note: FFmpeg treats half-float as specific types often requiring casts.
|
||||
// For this snippet we map F16 directly if your system supports it,
|
||||
// otherwise, standard float (F32) is safer.
|
||||
case AV_PIX_FMT_GBRPF16:
|
||||
case AV_PIX_FMT_GBRAPF16:
|
||||
return OCIO::BIT_DEPTH_F16;
|
||||
|
||||
case AV_PIX_FMT_GBRPF32:
|
||||
case AV_PIX_FMT_GBRAPF32:
|
||||
return OCIO::BIT_DEPTH_F32;
|
||||
|
||||
default:
|
||||
return OCIO::BIT_DEPTH_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static OCIO::ConstContextRcPtr add_context_params(OCIO::ConstConfigRcPtr config, AVDictionary *params)
|
||||
{
|
||||
|
||||
OCIO::ConstContextRcPtr context = config->getCurrentContext();
|
||||
if (!params)
|
||||
return context;
|
||||
if (!context)
|
||||
return nullptr;
|
||||
|
||||
OCIO::ContextRcPtr ctx = context->createEditableCopy();
|
||||
if (!ctx) {
|
||||
return context;
|
||||
}
|
||||
const AVDictionaryEntry *e = NULL;
|
||||
while ((e = av_dict_iterate(params, e))) {
|
||||
ctx->setStringVar(e->key, e->value);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
OCIOHandle
|
||||
ocio_create_output_colorspace_processor(AVFilterContext *ctx, const char *config_path,
|
||||
const char *input_color_space,
|
||||
const char *output_color_space,
|
||||
AVDictionary *params)
|
||||
{
|
||||
try {
|
||||
OCIOState *s = new OCIOState();
|
||||
if (!config_path)
|
||||
s->config = OCIO::Config::CreateFromEnv();
|
||||
else
|
||||
s->config = OCIO::Config::CreateFromFile(config_path);
|
||||
|
||||
if (!s->config || !input_color_space || !output_color_space) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error: Config or color spaces invalid.\n");
|
||||
if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n");
|
||||
if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n");
|
||||
if (!output_color_space) av_log(ctx, AV_LOG_ERROR, "Output color space is null\n");
|
||||
delete s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ColorSpace Transform: InputSpace -> OutputSpace
|
||||
OCIO::ColorSpaceTransformRcPtr cst = OCIO::ColorSpaceTransform::Create();
|
||||
cst->setSrc(input_color_space);
|
||||
cst->setDst(output_color_space);
|
||||
auto context = add_context_params(s->config, params);
|
||||
s->processor = s->config->getProcessor(context, cst, OCIO::TRANSFORM_DIR_FORWARD);
|
||||
|
||||
return (OCIOHandle)s;
|
||||
} catch (OCIO::Exception &e) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Error in create_output_colorspace_processor: %s\n", e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO Filter: Unknown Error in create_output_colorspace_processor\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try {
|
||||
|
||||
OCIOState *s = new OCIOState();
|
||||
if (!config_path)
|
||||
s->config = OCIO::Config::CreateFromEnv();
|
||||
else
|
||||
s->config = OCIO::Config::CreateFromFile(config_path);
|
||||
|
||||
if (!s->config || !input_color_space || !display || !view) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error: Config or arguments invalid.\n");
|
||||
if (!s->config) av_log(ctx, AV_LOG_ERROR, "Config is null\n");
|
||||
if (!input_color_space) av_log(ctx, AV_LOG_ERROR, "Input color space is null\n");
|
||||
if (!display) av_log(ctx, AV_LOG_ERROR, "Display is null\n");
|
||||
if (!view) av_log(ctx, AV_LOG_ERROR, "View is null\n");
|
||||
delete s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Display/View Transform: InputSpace -> Display/View
|
||||
OCIO::DisplayViewTransformRcPtr dv = OCIO::DisplayViewTransform::Create();
|
||||
dv->setSrc(input_color_space);
|
||||
dv->setDisplay(display);
|
||||
dv->setView(view);
|
||||
OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD;
|
||||
if (inverse)
|
||||
direction = OCIO::TRANSFORM_DIR_INVERSE;
|
||||
OCIO::ConstContextRcPtr context = add_context_params(s->config, params);
|
||||
s->processor = s->config->getProcessor(context, dv, direction);
|
||||
|
||||
return (OCIOHandle)s;
|
||||
} catch (OCIO::Exception &e) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_display_view_processor: %s\n", e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_display_view_processor\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx,
|
||||
const char *file_transform,
|
||||
int inverse)
|
||||
{
|
||||
try {
|
||||
if (!file_transform) {
|
||||
av_log(ctx, AV_LOG_ERROR, "File transform is null\n");
|
||||
return nullptr;
|
||||
}
|
||||
OCIOState *s = new OCIOState();
|
||||
|
||||
// File Transform: InputSpace -> FileTransform -> OutputSpace
|
||||
OCIO::FileTransformRcPtr ft = OCIO::FileTransform::Create();
|
||||
ft->setSrc(file_transform);
|
||||
OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_FORWARD;
|
||||
if (inverse)
|
||||
direction = OCIO::TRANSFORM_DIR_INVERSE;
|
||||
s->config = OCIO::Config::Create();
|
||||
s->processor = s->config->getProcessor(ft, direction);
|
||||
|
||||
return (OCIOHandle)s;
|
||||
} catch (OCIO::Exception &e) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO Error in create_file_transform_processor: %s\n", e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Unknown Error in create_file_transform_processor\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// In ocio_wrapper.cpp
|
||||
int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format,
|
||||
int output_format)
|
||||
{
|
||||
try {
|
||||
OCIOState *s = (OCIOState *)handle;
|
||||
if (!s || !s->processor)
|
||||
return -1;
|
||||
|
||||
s->cpu = s->processor->getOptimizedCPUProcessor(
|
||||
get_ocio_depth(input_format), get_ocio_depth(output_format),
|
||||
OCIO::OPTIMIZATION_DEFAULT);
|
||||
|
||||
return 0;
|
||||
} catch (OCIO::Exception &e) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Unknown error in ocio_finalize_processor\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static OCIO::ImageDesc *AVFrame2ImageDescSlice(AVFrame *frame, int y_start,
|
||||
int height)
|
||||
{
|
||||
OCIO::BitDepth ocio_bitdepth = get_ocio_depth(frame->format);
|
||||
if (ocio_bitdepth == OCIO::BIT_DEPTH_UNKNOWN) {
|
||||
throw std::runtime_error("Unsupported pixel format for OCIO processing");
|
||||
}
|
||||
|
||||
int stridex = frame->linesize[0];
|
||||
const AVPixFmtDescriptor *desc =
|
||||
av_pix_fmt_desc_get((enum AVPixelFormat)frame->format);
|
||||
if (!desc) {
|
||||
throw std::runtime_error("Invalid pixel format descriptor");
|
||||
}
|
||||
|
||||
bool is_planar = desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR);
|
||||
|
||||
if (is_planar) {
|
||||
// For planar, we need to offset each plane
|
||||
uint8_t *red = frame->data[2] + y_start * frame->linesize[2];
|
||||
uint8_t *green = frame->data[0] + y_start * frame->linesize[0];
|
||||
uint8_t *blue = frame->data[1] + y_start * frame->linesize[1];
|
||||
uint8_t *alpha = (desc->nb_components == 4)
|
||||
? (frame->data[3] + y_start * frame->linesize[3])
|
||||
: nullptr;
|
||||
|
||||
return new OCIO::PlanarImageDesc(
|
||||
(void *)red, (void *)green, (void *)blue, (void *)alpha, frame->width,
|
||||
height, ocio_bitdepth, desc->comp[0].step, stridex);
|
||||
}
|
||||
|
||||
uint8_t *data = frame->data[0] + y_start * frame->linesize[0];
|
||||
// Note we are assuming that these are RGB or RGBA channel ordering.
|
||||
// And are also likely to be integer.
|
||||
return new OCIO::PackedImageDesc(
|
||||
(void *)data, frame->width, height, desc->nb_components, ocio_bitdepth,
|
||||
desc->comp[0].depth / 8, desc->comp[0].step, frame->linesize[0]);
|
||||
}
|
||||
|
||||
int ocio_apply(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame,
|
||||
int y_start, int height)
|
||||
{
|
||||
OCIOState *s = (OCIOState *)handle;
|
||||
if (!s || !s->cpu)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
if (input_frame == output_frame) {
|
||||
OCIO::ImageDesc *imgDesc = AVFrame2ImageDescSlice(input_frame, y_start, height);
|
||||
s->cpu->apply(*imgDesc);
|
||||
delete imgDesc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
OCIO::ImageDesc *input = AVFrame2ImageDescSlice(input_frame, y_start, height);
|
||||
OCIO::ImageDesc *output = AVFrame2ImageDescSlice(output_frame, y_start, height);
|
||||
s->cpu->apply(*input, *output);
|
||||
|
||||
delete input;
|
||||
delete output;
|
||||
return 0;
|
||||
} catch (const OCIO::Exception &ex) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO error: %s\n", ex.what());
|
||||
return -2; // or another error code
|
||||
} catch (const std::exception &ex) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO error: Standard exception: %s\n", ex.what());
|
||||
return -3;
|
||||
} catch (...) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO error: Unknown error in OCIO processing.\n");
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle)
|
||||
{
|
||||
if (!handle)
|
||||
return;
|
||||
delete (OCIOState *)handle;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user