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
+8
View File
@@ -456,6 +456,14 @@ int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder)
return decoder->fmt_ctx->start_time;
}
int64_t fb_decoder_get_format_duration(const FBDecoder *decoder)
{
if (!decoder || !decoder->fmt_ctx) {
return FB_NOPTS_VALUE;
}
return decoder->fmt_ctx->duration;
}
int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder,
FBFrame *frame, int *num, int *den)
{
+12 -4
View File
@@ -566,6 +566,8 @@ int fb_encoder_write_audio(FBEncoder *e, const uint8_t *const *channel_data,
int bytes_per_sample =
av_get_bytes_per_sample(static_cast<AVSampleFormat>(sample_format));
int planar =
av_sample_fmt_is_planar(static_cast<AVSampleFormat>(sample_format));
bool result = true;
@@ -588,10 +590,16 @@ int fb_encoder_write_audio(FBEncoder *e, const uint8_t *const *channel_data,
e->SetError("Failed to allocate sample array", r);
return r;
} else {
for (int i = 0; i < channels; i++) {
memcpy(input_data[i],
channel_data[i] + start * size_t(bytes_per_sample),
input_sample_count * size_t(bytes_per_sample));
if (planar) {
for (int i = 0; i < channels; i++) {
memcpy(input_data[i],
channel_data[i] + start * size_t(bytes_per_sample),
input_sample_count * size_t(bytes_per_sample));
}
} else {
size_t stride = size_t(bytes_per_sample) * size_t(channels);
memcpy(input_data[0], channel_data[0] + start * stride,
input_sample_count * stride);
}
start += input_sample_count;
+8
View File
@@ -59,6 +59,14 @@ int fb_frame_get_buffer(FBFrame *frame, int align)
return av_frame_get_buffer(frame->frame, align);
}
int fb_frame_make_writable(FBFrame *frame)
{
if (!frame || !frame->frame) {
return AVERROR(EINVAL);
}
return av_frame_make_writable(frame->frame);
}
int fb_frame_copy_props(FBFrame *dst, const FBFrame *src)
{
if (!dst || !src) {
+1
View File
@@ -68,6 +68,7 @@ static_assert(FB_PIX_FMT_YUVJ422P == AV_PIX_FMT_YUVJ422P, "pixfmt mismatch");
static_assert(FB_PIX_FMT_YUVJ444P == AV_PIX_FMT_YUVJ444P, "pixfmt mismatch");
static_assert(FB_PIX_FMT_RGBA == AV_PIX_FMT_RGBA, "pixfmt mismatch");
static_assert(FB_PIX_FMT_GRAY16LE == AV_PIX_FMT_GRAY16LE, "pixfmt mismatch");
static_assert(FB_PIX_FMT_YUV440P == AV_PIX_FMT_YUV440P, "pixfmt mismatch");
static_assert(FB_PIX_FMT_YUVJ440P == AV_PIX_FMT_YUVJ440P, "pixfmt mismatch");
static_assert(FB_PIX_FMT_RGB48LE == AV_PIX_FMT_RGB48LE, "pixfmt mismatch");
static_assert(FB_PIX_FMT_YUV420P10LE == AV_PIX_FMT_YUV420P10LE, "pixfmt mismatch");
+8
View File
@@ -309,6 +309,14 @@ int fb_probe_read_subtitle_stream(const char *filename, int stream_index,
return AVERROR_EXTERNAL;
}
// Limit to SRT for now (mirrors the editor's historical behavior)
FBStreamInfo stream_info;
if (fb_decoder_get_stream_info(decoder, &stream_info) < 0 ||
stream_info.codec_id != (int)AV_CODEC_ID_SUBRIP) {
fb_decoder_free(&decoder);
return AVERROR(EINVAL);
}
FBPacket *pkt = fb_packet_alloc();
while (fb_decoder_get_packet(decoder, pkt) >= 0) {
+11
View File
@@ -81,3 +81,14 @@ int fb_resampler_convert(FBResampler *resampler, uint8_t **out, int out_count,
}
return swr_convert(resampler->ctx, out, out_count, in, in_count);
}
int fb_resampler_convert_frame(FBResampler *resampler, uint8_t **out,
int out_count, const FBFrame *in_frame)
{
if (!resampler || !in_frame || !in_frame->frame) {
return AVERROR(EINVAL);
}
return swr_convert(resampler->ctx, out, out_count,
(const uint8_t **)in_frame->frame->extended_data,
in_frame->frame->nb_samples);
}
+2 -1
View File
@@ -172,7 +172,8 @@ int fb_pix_fmt_component_size(int pix_fmt)
if (!desc || desc->nb_components == 0) {
return 0;
}
return desc->comp[0].step;
// Bytes used to store one component: 1 for 8-bit formats, 2 for 9-16bit
return (desc->comp[0].depth + 7) / 8;
}
int fb_find_best_pix_fmt_of_list(const int *list, int pix_fmt)