mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2026-04-18 08:30:24 +00: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:
parent
a7522f3fef
commit
677cf95ea4
7 changed files with 770 additions and 0 deletions
|
|
@ -416,6 +416,7 @@ OBJS-$(CONFIG_NORMALIZE_FILTER) += vf_normalize.o
|
|||
OBJS-$(CONFIG_NULL_FILTER) += vf_null.o
|
||||
OBJS-$(CONFIG_OCR_FILTER) += vf_ocr.o
|
||||
OBJS-$(CONFIG_OCV_FILTER) += vf_libopencv.o
|
||||
OBJS-$(CONFIG_OCIO_FILTER) += vf_opencolorio.o ocio_wrapper.o
|
||||
OBJS-$(CONFIG_OSCILLOSCOPE_FILTER) += vf_datascope.o
|
||||
OBJS-$(CONFIG_OVERLAY_FILTER) += vf_overlay.o framesync.o
|
||||
OBJS-$(CONFIG_OVERLAY_CUDA_FILTER) += vf_overlay_cuda.o framesync.o vf_overlay_cuda.ptx.o \
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ extern const FFFilter ff_vf_null;
|
|||
extern const FFFilter ff_vf_ocr;
|
||||
extern const FFFilter ff_vf_ocv;
|
||||
extern const FFFilter ff_vf_oscilloscope;
|
||||
extern const FFFilter ff_vf_ocio;
|
||||
extern const FFFilter ff_vf_overlay;
|
||||
extern const FFFilter ff_vf_overlay_opencl;
|
||||
extern const FFFilter ff_vf_overlay_qsv;
|
||||
|
|
|
|||
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"
|
||||
72
libavfilter/ocio_wrapper.hpp
Normal file
72
libavfilter/ocio_wrapper.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *OCIOHandle;
|
||||
|
||||
// Create an OCIO processor for Display/View transform.
|
||||
// Returns NULL on failure.
|
||||
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);
|
||||
|
||||
// Create an OCIO processor for output colorspace transform.
|
||||
// Returns NULL on failure.
|
||||
OCIOHandle
|
||||
ocio_create_output_colorspace_processor(AVFilterContext *ctx,
|
||||
const char *config_path,
|
||||
const char *input_color_space,
|
||||
const char *output_color_space,
|
||||
AVDictionary *params);
|
||||
|
||||
// Create an OCIO processor for file transform.
|
||||
// Returns NULL on failure.
|
||||
OCIOHandle ocio_create_file_transform_processor(AVFilterContext *ctx,
|
||||
const char *file_transform,
|
||||
int inverse);
|
||||
|
||||
// Finalize OCIO processor for given bit depth.
|
||||
// is_half_float: true for half-float, false for float
|
||||
int ocio_finalize_processor(AVFilterContext *ctx, OCIOHandle handle, int input_format,
|
||||
int output_format);
|
||||
|
||||
// Apply processor to planar float RGB(A).
|
||||
// pixels: pointer to float samples
|
||||
// w,h: image dimensions
|
||||
// channels: 3 or 4
|
||||
// stride_bytes: bytes between row starts (use 0 for tightly packed)
|
||||
int ocio_apply(AVFilterContext *ctx, OCIOHandle handle, AVFrame *input_frame, AVFrame *output_frame,
|
||||
int y_start, int height);
|
||||
|
||||
// Destroy OCIO processor.
|
||||
void ocio_destroy_processor(AVFilterContext *ctx, OCIOHandle handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
289
libavfilter/vf_opencolorio.c
Normal file
289
libavfilter/vf_opencolorio.c
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* 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 "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "libavutil/half2float.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/time.h"
|
||||
#include "ocio_wrapper.hpp"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *config_path;
|
||||
char *input_space;
|
||||
char *output_space;
|
||||
char *display;
|
||||
char *view;
|
||||
char *filetransform;
|
||||
int inverse;
|
||||
OCIOHandle ocio;
|
||||
int output_format;
|
||||
char *out_format_string; // e.g. "rgb48le" which is converted to AVPixelFormat
|
||||
// as output_format
|
||||
int channels; // 3 or 4 depending on pixfmt
|
||||
AVDictionary *context_params;
|
||||
} OCIOContext;
|
||||
|
||||
typedef struct ThreadData {
|
||||
AVFrame *in, *out;
|
||||
} ThreadData;
|
||||
|
||||
static int ocio_filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
|
||||
{
|
||||
OCIOContext *s = ctx->priv;
|
||||
ThreadData *td = arg;
|
||||
AVFrame *in = td->in;
|
||||
AVFrame *out = td->out;
|
||||
const int height = out->height;
|
||||
const int slice_start = (height * jobnr) / nb_jobs;
|
||||
const int slice_end = (height * (jobnr + 1)) / nb_jobs;
|
||||
const int slice_h = slice_end - slice_start;
|
||||
|
||||
return ocio_apply(ctx, s->ocio, in, out, slice_start, slice_h);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx) {
|
||||
static const enum AVPixelFormat pix_fmts[] = {
|
||||
// 8-bit
|
||||
AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB24,
|
||||
// 16-bit
|
||||
AV_PIX_FMT_RGBA64, AV_PIX_FMT_RGB48,
|
||||
// 10-bit
|
||||
AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10,
|
||||
// 12-bit
|
||||
AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12,
|
||||
// Half-float and float
|
||||
AV_PIX_FMT_GBRPF16, AV_PIX_FMT_GBRAPF16,
|
||||
// Float
|
||||
AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, AV_PIX_FMT_NONE};
|
||||
return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
OCIOContext *s = ctx->priv;
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
|
||||
|
||||
if (!desc) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid pixel format\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
int is_half =
|
||||
(desc->comp[0].depth == 16 && desc->flags & AV_PIX_FMT_FLAG_FLOAT);
|
||||
if (s->output_format == AV_PIX_FMT_NONE) {
|
||||
// Need to set the output format now, if not known.
|
||||
if (is_half) {
|
||||
// If its half-float, we output float, due to a bug in ffmpeg with
|
||||
// half-float frames
|
||||
s->output_format = AV_PIX_FMT_GBRAPF32;
|
||||
} else {
|
||||
// If output format not set, use same as input
|
||||
s->output_format = inlink->format;
|
||||
}
|
||||
}
|
||||
|
||||
s->channels = desc->nb_components; // 3 or 4
|
||||
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"Configuring OCIO for %s (bit depth: %d, channels: %d), output "
|
||||
"format: (%s)\n",
|
||||
av_get_pix_fmt_name(inlink->format), desc->comp[0].depth, s->channels,
|
||||
av_get_pix_fmt_name(s->output_format));
|
||||
|
||||
// Now finalize the OCIO processor with the correct bit depth
|
||||
int ret = ocio_finalize_processor(ctx, s->ocio, inlink->format, s->output_format);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Failed to finalize OCIO processor for bit depth\n");
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
OCIOContext *s = ctx->priv;
|
||||
if (s->out_format_string != NULL)
|
||||
s->output_format = av_get_pix_fmt(s->out_format_string);
|
||||
else
|
||||
s->output_format = AV_PIX_FMT_NONE; // Default to same as input format (see later).
|
||||
|
||||
if (s->filetransform && strlen(s->filetransform) > 0) {
|
||||
s->ocio = ocio_create_file_transform_processor(
|
||||
ctx, s->filetransform, s->inverse);
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"Creating OCIO processor with FileTransform: %s, Inverse: %d\n",
|
||||
s->filetransform, s->inverse);
|
||||
} else if (s->output_space && strlen(s->output_space) > 0) {
|
||||
s->ocio = ocio_create_output_colorspace_processor(
|
||||
ctx, s->config_path, s->input_space, s->output_space, s->context_params);
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"Creating OCIO processor with config: %s, input: %s, output: %s\n",
|
||||
s->config_path, s->input_space, s->output_space);
|
||||
} else {
|
||||
s->ocio = ocio_create_display_view_processor(
|
||||
ctx, s->config_path, s->input_space, s->display, s->view, s->inverse, s->context_params);
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"Creating OCIO processor with config: %s, input: %s, display: %s, "
|
||||
"view: %s, Inverse: %d\n",
|
||||
s->config_path, s->input_space, s->display, s->view, s->inverse);
|
||||
}
|
||||
if (!s->ocio) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Failed to create OCIO processor.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
OCIOContext *s = ctx->priv;
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
|
||||
|
||||
if (!desc)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
int ret;
|
||||
AVFrame *output_frame;
|
||||
ThreadData td;
|
||||
|
||||
if (s->output_format == inlink->format) {
|
||||
/* No pixel-format conversion needed. If the input frame is
|
||||
* writable we can apply OCIO in-place, otherwise allocate a
|
||||
* separate output frame to avoid mutating shared buffers. */
|
||||
if (av_frame_is_writable(frame)) {
|
||||
output_frame = frame;
|
||||
} else {
|
||||
output_frame = av_frame_alloc();
|
||||
if (!output_frame) {
|
||||
av_frame_free(&frame);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
output_frame->format = s->output_format;
|
||||
output_frame->width = frame->width;
|
||||
output_frame->height = frame->height;
|
||||
ret = av_frame_get_buffer(output_frame, 32);
|
||||
|
||||
if (ret < 0) {
|
||||
av_frame_free(&output_frame);
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
av_frame_copy_props(output_frame, frame);
|
||||
}
|
||||
} else {
|
||||
// Allocate new output frame
|
||||
output_frame = av_frame_alloc();
|
||||
if (!output_frame) {
|
||||
av_frame_free(&frame);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
output_frame->format = s->output_format;
|
||||
output_frame->width = frame->width;
|
||||
output_frame->height = frame->height;
|
||||
ret = av_frame_get_buffer(output_frame, 32);
|
||||
|
||||
if (ret < 0) {
|
||||
av_frame_free(&output_frame);
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
av_frame_copy_props(output_frame, frame);
|
||||
}
|
||||
|
||||
td.in = frame;
|
||||
td.out = output_frame;
|
||||
|
||||
// Use threads from context if set, otherwise let ffmpeg decide based on global settings or defaults
|
||||
// Note: ctx->graph->nb_threads is usually the global thread count.
|
||||
// ff_filter_get_nb_threads(ctx) gives the number of threads available for this filter.
|
||||
|
||||
int nb_jobs = ff_filter_get_nb_threads(ctx);
|
||||
|
||||
ret = ff_filter_execute(ctx, ocio_filter_slice, &td, NULL, FFMIN(output_frame->height, nb_jobs));
|
||||
|
||||
if (frame != output_frame)
|
||||
av_frame_free(&frame);
|
||||
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OCIO apply failed.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return ff_filter_frame(ctx->outputs[0], output_frame);
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
OCIOContext *s = ctx->priv;
|
||||
if (s->ocio) {
|
||||
ocio_destroy_processor(ctx, s->ocio);
|
||||
s->ocio = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(OCIOContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption ocio_options[] = {
|
||||
{ "config", "OCIO config path, overriding OCIO environment variable.", OFFSET(config_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "input", "Input color space", OFFSET(input_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "output", "Output color space", OFFSET(output_space), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "filetransform", "Specify a File Transform", OFFSET(filetransform), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "display", "Output display, used instead of output color space.", OFFSET(display), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "view", "View, output view transform, used in combination with display.", OFFSET(view), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "inverse", "Invert output display/view transform.", OFFSET(inverse), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
|
||||
{ "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "context_params", "OCIO context parameters", OFFSET(context_params), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, FLAGS },{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(ocio);
|
||||
|
||||
static const AVFilterPad inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
.config_props = config_props,
|
||||
},
|
||||
};
|
||||
|
||||
static const AVFilterPad outputs[] = {
|
||||
{.name = "default", .type = AVMEDIA_TYPE_VIDEO}};
|
||||
|
||||
const FFFilter ff_vf_ocio = {
|
||||
.p.name = "ocio",
|
||||
.p.description = NULL_IF_CONFIG_SMALL("Apply OCIO Display/View transform"),
|
||||
.p.priv_class = &ocio_class,
|
||||
.p.flags = AVFILTER_FLAG_SLICE_THREADS,
|
||||
.priv_size = sizeof(OCIOContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
FILTER_INPUTS(inputs),
|
||||
FILTER_OUTPUTS(outputs),
|
||||
FILTER_QUERY_FUNC(query_formats)};
|
||||
Loading…
Add table
Add a link
Reference in a new issue