mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2026-03-16 17:27:55 +01:00
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>
317 lines
11 KiB
C++
317 lines
11 KiB
C++
/*
|
|
* 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"
|