app: route all FFmpeg access through the ffmpeg_bridge C API

Replace every direct FFmpeg call in the editor and the render worker
with the pure C ffmpeg_bridge API, wrapped in thin C++ adapters that
preserve the original interfaces:

- avframeptr.h: olive::AVFrame adapter around FBFrame handles
- ffmpegutils: format conversion helpers on FB_* constants; the int
  overload is renamed GetCompatibleBridgePixelFormat to avoid a silent
  overload-resolution trap with the PixelFormat enum
- ffmpegdecoder/ffmpegencoder: rewritten as handle-based adapters over
  FBDecoder/FBProbe/FBEncoder/FBScaler/FBResampler
- audioprocessor: FBAudioGraph push/pull adapter
- pluginrenderer/OliveClip: sws/pixdesc usage converted to FBScaler and
  fb_pix_fmt_* queries
- AudioParams/channel layouts are plain uint64_t masks everywhere

Build integration: the root project no longer links FFMPEG directly;
only ffmpeg_bridge does. Binaries resolve the bridge library at runtime
via @loader_path inside the macOS app bundle (copied there post-build)
and via $ORIGIN/../ffmpeg_bridge/bin on Linux; on Windows the DLL is
installed next to the executables, so packages on all three platforms
ship the bridge library.

ffmpeg_bridge gains the extra API the adapters need:
fb_frame_make_writable, fb_decoder_get_format_duration,
fb_resampler_convert_frame, FB_PIX_FMT_YUV440P, SRT validation in
fb_probe_read_subtitle_stream, packed/planar fixes in
fb_encoder_write_audio, and a component-size fix in
fb_pix_fmt_component_size.
This commit is contained in:
2026-07-15 23:00:06 +08:00
parent ddb2b30cf4
commit 3a3ead6fc0
42 changed files with 1382 additions and 4546 deletions
+104 -87
View File
@@ -37,12 +37,16 @@
#endif
#include "common/ffmpegutils.h"
#include "render/renderer.h"
extern "C" {
#include <libswscale/swscale.h>
#include <libavutil/pixdesc.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
namespace
{
// The bridge header only defines the little-endian pixel formats. FFmpeg
// numbers each big-endian variant immediately before its little-endian
// counterpart (BE == LE - 1), so derive the BE constants used below.
constexpr int FB_PIX_FMT_GRAYF32BE = FB_PIX_FMT_GRAYF32LE - 1;
constexpr int FB_PIX_FMT_RGBF32BE = FB_PIX_FMT_RGBF32LE - 1;
constexpr int FB_PIX_FMT_RGBAF32BE = FB_PIX_FMT_RGBAF32LE - 1;
const std::string kBitDepthNoneStr(kOfxBitDepthNone);
const std::string kBitDepthByteStr(kOfxBitDepthByte);
const std::string kBitDepthShortStr(kOfxBitDepthShort);
@@ -68,48 +72,48 @@ static int BytesToPixels(int byte_linesize, const olive::VideoParams &params)
return byte_linesize / bytes_per_pixel;
}
static int PackedFloatChannels(AVPixelFormat fmt)
static int PackedFloatChannels(int fmt)
{
switch (fmt) {
case AV_PIX_FMT_GRAYF32LE:
case AV_PIX_FMT_GRAYF32BE:
case FB_PIX_FMT_GRAYF32LE:
case FB_PIX_FMT_GRAYF32BE:
return 1;
case AV_PIX_FMT_RGBF32LE:
case AV_PIX_FMT_RGBF32BE:
case FB_PIX_FMT_RGBF32LE:
case FB_PIX_FMT_RGBF32BE:
return 3;
case AV_PIX_FMT_RGBAF32LE:
case AV_PIX_FMT_RGBAF32BE:
case FB_PIX_FMT_RGBAF32LE:
case FB_PIX_FMT_RGBAF32BE:
return 4;
default:
return 0;
}
}
static bool PackedDstInfo(AVPixelFormat fmt, int *channels,
static bool PackedDstInfo(int fmt, int *channels,
int *bytes_per_component)
{
switch (fmt) {
case AV_PIX_FMT_GRAY8:
case FB_PIX_FMT_GRAY8:
*channels = 1;
*bytes_per_component = 1;
return true;
case AV_PIX_FMT_RGB24:
case FB_PIX_FMT_RGB24:
*channels = 3;
*bytes_per_component = 1;
return true;
case AV_PIX_FMT_RGBA:
case FB_PIX_FMT_RGBA:
*channels = 4;
*bytes_per_component = 1;
return true;
case AV_PIX_FMT_GRAY16LE:
case FB_PIX_FMT_GRAY16LE:
*channels = 1;
*bytes_per_component = 2;
return true;
case AV_PIX_FMT_RGB48LE:
case FB_PIX_FMT_RGB48LE:
*channels = 3;
*bytes_per_component = 2;
return true;
case AV_PIX_FMT_RGBA64LE:
case FB_PIX_FMT_RGBA64LE:
*channels = 4;
*bytes_per_component = 2;
return true;
@@ -126,28 +130,23 @@ ReadbackTextureToFrame(olive::TexturePtr texture,
return nullptr;
}
AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(
int pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(
params.format(), params.channel_count());
if (pix_fmt == AV_PIX_FMT_NONE) {
if (pix_fmt == FB_PIX_FMT_NONE) {
return nullptr;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
if (!desc) {
return nullptr;
}
if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) {
if (!fb_pix_fmt_is_planar(pix_fmt)) {
olive::AVFramePtr frame = olive::CreateAVFramePtr();
frame->format = pix_fmt;
frame->width = params.width();
frame->height = params.height();
if (av_frame_get_buffer(frame.get(), 0) < 0) {
frame->set_format(pix_fmt);
frame->set_width(params.width());
frame->set_height(params.height());
if (frame->get_buffer(0) < 0) {
return nullptr;
}
const int linesize_pixels = BytesToPixels(frame->linesize[0], params);
const int linesize_pixels = BytesToPixels(frame->linesize(0), params);
texture->renderer()->DownloadFromTexture(
texture->id(), params, frame->data[0], linesize_pixels);
texture->id(), params, frame->data(0), linesize_pixels);
return frame;
}
@@ -157,48 +156,57 @@ ReadbackTextureToFrame(olive::TexturePtr texture,
params.interlacing(), params.divider());
olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr();
rgba_frame->format = AV_PIX_FMT_RGBA;
rgba_frame->width = params.width();
rgba_frame->height = params.height();
if (av_frame_get_buffer(rgba_frame.get(), 0) < 0) {
rgba_frame->set_format(FB_PIX_FMT_RGBA);
rgba_frame->set_width(params.width());
rgba_frame->set_height(params.height());
if (rgba_frame->get_buffer(0) < 0) {
return nullptr;
}
const int linesize_pixels =
BytesToPixels(rgba_frame->linesize[0], rgba_params);
BytesToPixels(rgba_frame->linesize(0), rgba_params);
texture->renderer()->DownloadFromTexture(
texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels);
texture->id(), rgba_params, rgba_frame->data(0), linesize_pixels);
olive::AVFramePtr dst = olive::CreateAVFramePtr();
dst->format = pix_fmt;
dst->width = params.width();
dst->height = params.height();
if (av_frame_get_buffer(dst.get(), 0) < 0) {
dst->set_format(pix_fmt);
dst->set_width(params.width());
dst->set_height(params.height());
if (dst->get_buffer(0) < 0) {
return rgba_frame;
}
SwsContext *sws_ctx = sws_getContext(
rgba_frame->width, rgba_frame->height,
static_cast<AVPixelFormat>(rgba_frame->format), dst->width, dst->height,
pix_fmt, SWS_POINT, nullptr, nullptr, nullptr);
if (!sws_ctx) {
FBScaler *scaler = fb_scaler_create(
rgba_frame->width(), rgba_frame->height(), rgba_frame->format(),
dst->width(), dst->height(), pix_fmt, FB_SCALER_POINT);
if (!scaler) {
return rgba_frame;
}
sws_scale(sws_ctx, rgba_frame->data, rgba_frame->linesize, 0,
rgba_frame->height, dst->data, dst->linesize);
sws_freeContext(sws_ctx);
uint8_t *src_data[4];
int src_linesize[4];
uint8_t *dst_data[4];
int dst_linesize[4];
for (int i = 0; i < 4; ++i) {
src_data[i] = rgba_frame->data(i);
src_linesize[i] = rgba_frame->linesize(i);
dst_data[i] = dst->data(i);
dst_linesize[i] = dst->linesize(i);
}
fb_scaler_scale_slices(scaler, src_data, src_linesize,
rgba_frame->height(), dst_data, dst_linesize);
fb_scaler_free(&scaler);
return dst;
}
static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
AVPixelFormat dst_fmt)
int dst_fmt)
{
if (!src || !src->data[0]) {
if (!src || !src->data(0)) {
return nullptr;
}
const int src_channels =
PackedFloatChannels(static_cast<AVPixelFormat>(src->format));
PackedFloatChannels(src->format());
if (src_channels == 0) {
return nullptr;
}
@@ -210,23 +218,23 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
}
olive::AVFramePtr dst = olive::CreateAVFramePtr();
dst->format = dst_fmt;
dst->width = src->width;
dst->height = src->height;
if (av_frame_get_buffer(dst.get(), 0) < 0) {
dst->set_format(dst_fmt);
dst->set_width(src->width());
dst->set_height(src->height());
if (dst->get_buffer(0) < 0) {
return nullptr;
}
auto clamp01 = [](float v) -> float { return std::clamp(v, 0.0f, 1.0f); };
for (int y = 0; y < src->height; ++y) {
for (int y = 0; y < src->height(); ++y) {
const float *src_row = reinterpret_cast<const float *>(
src->data[0] + y * src->linesize[0]);
uint8_t *dst_row = dst->data[0] + y * dst->linesize[0];
src->data(0) + y * src->linesize(0));
uint8_t *dst_row = dst->data(0) + y * dst->linesize(0);
if (bytes_per_component == 2) {
auto *dst_row_u16 = reinterpret_cast<uint16_t *>(dst_row);
for (int x = 0; x < src->width; ++x) {
for (int x = 0; x < src->width(); ++x) {
const float *pix = src_row + x * src_channels;
float r = pix[0];
float g = (src_channels > 1) ? pix[1] : r;
@@ -250,7 +258,7 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
}
}
} else {
for (int x = 0; x < src->width; ++x) {
for (int x = 0; x < src->width(); ++x) {
const float *pix = src_row + x * src_channels;
float r = pix[0];
float g = (src_channels > 1) ? pix[1] : r;
@@ -613,12 +621,12 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
}
AVFramePtr frame = texture->frame();
if (!frame || !frame->data[0]) {
if (!frame || !frame->data(0)) {
frame = ReadbackTextureToFrame(texture, params_);
}
AVPixelFormat expected_fmt = FFmpegUtils::GetFFmpegPixelFormat(
int expected_fmt = FFmpegUtils::GetFFmpegPixelFormat(
params_.format(), params_.channel_count());
if (expected_fmt == AV_PIX_FMT_NONE) {
if (expected_fmt == FB_PIX_FMT_NONE) {
return;
}
OfxRectI bounds = { 0, 0, params_.width(), params_.height() };
@@ -646,7 +654,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
return;
}
if (!frame || !frame->data[0]) {
if (!frame || !frame->data(0)) {
std::memset(dst, 0, image->row_bytes() * image->height());
return;
}
@@ -657,8 +665,8 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
// and SIGSEGV on Apple Silicon (where (int)NaN often evaluates to 0 or
// INT_MIN, causing huge offsets into bgrid._data).
if (params_.format() == core::PixelFormat::F32) {
const float *fptr = reinterpret_cast<const float *>(frame->data[0]);
int row_floats = frame->linesize[0] / static_cast<int>(sizeof(float));
const float *fptr = reinterpret_cast<const float *>(frame->data(0));
int row_floats = frame->linesize(0) / static_cast<int>(sizeof(float));
bool has_nan = false;
for (int y = 0; y < params_.height() && !has_nan; ++y) {
for (int x = 0; x < params_.width() * params_.channel_count();
@@ -684,9 +692,9 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
}
AVFramePtr src_frame = frame;
if (frame->format != expected_fmt || frame->width != params_.width() ||
frame->height != params_.height()) {
if (PackedFloatChannels(static_cast<AVPixelFormat>(frame->format)) >
if (frame->format() != expected_fmt || frame->width() != params_.width() ||
frame->height() != params_.height()) {
if (PackedFloatChannels(frame->format()) >
0) {
AVFramePtr converted = ConvertPackedFloatFrame(frame, expected_fmt);
if (converted) {
@@ -695,25 +703,34 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
}
}
AVFramePtr converted = CreateAVFramePtr();
converted->format = expected_fmt;
converted->width = params_.width();
converted->height = params_.height();
if (av_frame_get_buffer(converted.get(), 0) < 0) {
converted->set_format(expected_fmt);
converted->set_width(params_.width());
converted->set_height(params_.height());
if (converted->get_buffer(0) < 0) {
return;
}
SwsContext *sws_ctx = sws_getContext(
frame->width, frame->height,
static_cast<AVPixelFormat>(frame->format), converted->width,
converted->height, static_cast<AVPixelFormat>(converted->format),
SWS_POINT, nullptr, nullptr, nullptr);
if (!sws_ctx) {
FBScaler *scaler = fb_scaler_create(
frame->width(), frame->height(), frame->format(),
converted->width(), converted->height(), converted->format(),
FB_SCALER_POINT);
if (!scaler) {
return;
}
sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height,
converted->data, converted->linesize);
sws_freeContext(sws_ctx);
uint8_t *src_data[4];
int src_linesize[4];
uint8_t *dst_data[4];
int dst_linesize[4];
for (int i = 0; i < 4; ++i) {
src_data[i] = frame->data(i);
src_linesize[i] = frame->linesize(i);
dst_data[i] = converted->data(i);
dst_linesize[i] = converted->linesize(i);
}
fb_scaler_scale_slices(scaler, src_data, src_linesize, frame->height(),
dst_data, dst_linesize);
fb_scaler_free(&scaler);
src_frame = converted;
}
@@ -722,13 +739,13 @@ copy_pixels:
int bytes_per_component = params_.format().byte_count();
int bytes_per_row =
params_.width() * params_.channel_count() * bytes_per_component;
int src_row_bytes = src_frame->linesize[0];
int src_row_bytes = src_frame->linesize(0);
int dst_row_bytes = image->row_bytes();
int copy_bytes =
std::min(bytes_per_row, std::min(src_row_bytes, dst_row_bytes));
int copy_height = std::min(image->height(), src_frame->height);
int copy_height = std::min(image->height(), src_frame->height());
const uint8_t *src = src_frame->data[0];
const uint8_t *src = src_frame->data(0);
if (params_.format() == core::PixelFormat::F32) {
const float *src_f = reinterpret_cast<const float *>(src);
float *dst_f = reinterpret_cast<float *>(dst);