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
+4 -61
View File
@@ -203,68 +203,11 @@ list(APPEND OLIVE_LIBRARIES
# Link OFX HostSupport wherever libolive-editor objects are used.
list(APPEND OLIVE_LIBRARIES OfxHost)
# Link FFmpeg
find_package(FFMPEG 6.0 REQUIRED
COMPONENTS
avutil
avcodec
avformat
avfilter
swscale
swresample
)
list(APPEND OLIVE_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS})
list(APPEND OLIVE_LIBRARIES
FFMPEG::avutil
FFMPEG::avcodec
FFMPEG::avformat
FFMPEG::avfilter
FFMPEG::swscale
FFMPEG::swresample
)
# FFmpeg isolation: all FFmpeg access is being moved behind this shared
# library's pure C API. Built early so the app can link it.
# FFmpeg isolation: all FFmpeg access in the editor goes through this shared
# library's pure C API. It is the only component that links FFmpeg.
add_subdirectory(ffmpeg_bridge)
# Detect FFmpeg pixel formats that may not exist in all versions
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDE_DIRS})
check_cxx_source_compiles("
#include <libavutil/pixfmt.h>
int main() { AVPixelFormat f = AV_PIX_FMT_GRAYF16; (void)f; return 0; }
" HAVE_AV_PIX_FMT_GRAYF16)
check_cxx_source_compiles("
#include <libavutil/pixfmt.h>
int main() { AVPixelFormat f = AV_PIX_FMT_RGBF16; (void)f; return 0; }
" HAVE_AV_PIX_FMT_RGBF16)
check_cxx_source_compiles("
#include <libavutil/pixfmt.h>
int main() { AVPixelFormat f = AV_PIX_FMT_RGBAF16; (void)f; return 0; }
" HAVE_AV_PIX_FMT_RGBAF16)
unset(CMAKE_REQUIRED_INCLUDES)
if(HAVE_AV_PIX_FMT_GRAYF16)
add_compile_definitions(HAVE_AV_PIX_FMT_GRAYF16)
endif()
if(HAVE_AV_PIX_FMT_RGBF16)
add_compile_definitions(HAVE_AV_PIX_FMT_RGBF16)
endif()
if(HAVE_AV_PIX_FMT_RGBAF16)
add_compile_definitions(HAVE_AV_PIX_FMT_RGBAF16)
endif()
# Static FFmpeg (e.g. our Linux CI build) needs system libs for libavcodec/libavformat.
find_package(ZLIB REQUIRED)
list(APPEND OLIVE_LIBRARIES ZLIB::ZLIB)
find_package(BZip2 REQUIRED)
list(APPEND OLIVE_LIBRARIES BZip2::BZip2)
find_package(LibLZMA)
if (LIBLZMA_FOUND)
list(APPEND OLIVE_LIBRARIES ${LIBLZMA_LIBRARIES})
endif()
list(APPEND OLIVE_LIBRARIES ffmpeg_bridge)
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg_bridge/include)
# Link PortAudio
find_package(PortAudio REQUIRED)
+30 -4
View File
@@ -313,14 +313,16 @@ elseif (APPLE)
OUTPUT_NAME "Oak"
)
# Copy the render worker and dynamic render backends into the app bundle.
# They are looked up in QCoreApplication::applicationDirPath(), which on
# macOS points to Oak.app/Contents/MacOS.
# Copy the render worker, dynamic render backends, and the FFmpeg bridge
# library into the app bundle. They are looked up in
# QCoreApplication::applicationDirPath(), which on macOS points to
# Oak.app/Contents/MacOS.
add_custom_command(TARGET olive-editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-render-worker> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakgl> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMENT "Copying oak-render-worker and render backends into Oak.app"
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMENT "Copying oak-render-worker, render backends, and ffmpeg_bridge into Oak.app"
)
if (TARGET oakvulkan)
add_custom_command(TARGET olive-editor POST_BUILD
@@ -337,6 +339,30 @@ target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
target_link_libraries(olive-render-worker PRIVATE ${OLIVE_LIBRARIES})
# The ffmpeg_bridge shared library ships next to the binaries: inside the
# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in
# ffmpeg_bridge/bin beside bin/ on other platforms. (Build-tree binaries get
# the correct RPATH from CMake automatically.)
if (APPLE)
# macOS bundles are distributed via POST_BUILD copies (not install()), so
# @loader_path must already be in the build-tree binaries' RPATH.
set(OLIVE_FB_RPATH "@loader_path")
set_target_properties(olive-editor olive-render-worker PROPERTIES
BUILD_RPATH "@loader_path")
elseif (UNIX)
set(OLIVE_FB_RPATH "$ORIGIN/../ffmpeg_bridge/bin")
endif ()
if (OLIVE_FB_RPATH)
set_target_properties(olive-editor olive-render-worker PROPERTIES
INSTALL_RPATH "${OLIVE_FB_RPATH}")
if (TARGET oakgl)
set_target_properties(oakgl PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}")
endif ()
if (TARGET oakvulkan)
set_target_properties(oakvulkan PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}")
endif ()
endif ()
# Set compile options
target_compile_options(olive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
target_compile_options(libolive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
+56 -226
View File
@@ -21,10 +21,7 @@
#include "audioprocessor.h"
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}
#include <cstring>
#include <QDebug>
@@ -34,50 +31,36 @@ namespace olive
{
/**
* @brief Ensure an AudioParams has a usable native channel layout mask.
* @brief Ensure an AudioParams has a usable channel layout mask.
*
* FFmpeg's abuffer/aformat filters reject channel_layout=0x0 / unspecified
* layouts (e.g. when the user config or a source stream reports a mask of 0).
* If the provided layout is not a valid native mask, fall back to a default
* layout derived from the channel count (stereo when unknown).
* The bridge's abuffer/aformat filters reject a channel layout mask of 0
* (e.g. when the user config or a source stream reports a mask of 0).
* If the mask is zero, fall back to a default layout derived from the
* channel count (stereo when unknown).
*/
static AudioParams FixChannelLayout(const AudioParams &params)
{
AudioParams result = params;
const AVChannelLayout &layout = params.channel_layout();
AudioParams result = params;
bool needs_fix = false;
if (!av_channel_layout_check(&layout)) {
needs_fix = true;
} else if (layout.order != AV_CHANNEL_ORDER_NATIVE) {
needs_fix = true;
} else if (layout.u.mask == 0) {
needs_fix = true;
}
if (params.channel_layout() == 0) {
int channels = params.channel_count();
if (channels <= 0) {
channels = 2;
}
if (needs_fix) {
int channels = params.channel_count();
if (channels <= 0) {
channels = 2;
}
qWarning() << "AudioProcessor: fixing unspecified channel layout"
<< "(channels=" << params.channel_count() << ") -> default"
<< channels << "channel layout";
qWarning() << "AudioProcessor: fixing invalid/unspecified channel layout"
<< "(channels=" << params.channel_count() << ") -> default"
<< channels << "channel layout";
result.set_channel_layout(fb_channel_layout_default(channels));
}
AVChannelLayout fallback;
av_channel_layout_default(&fallback, channels);
result.set_channel_layout(fallback);
av_channel_layout_uninit(&fallback);
}
return result;
return result;
}
AudioProcessor::AudioProcessor()
{
filter_graph_ = nullptr;
in_frame_ = nullptr;
graph_ = nullptr;
out_frame_ = nullptr;
}
@@ -89,161 +72,45 @@ AudioProcessor::~AudioProcessor()
bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
double tempo)
{
if (filter_graph_) {
if (graph_) {
qWarning() << "Tried to open a processor that was already open";
return false;
}
filter_graph_ = avfilter_graph_alloc();
if (!filter_graph_) {
qCritical() << "Failed to allocate filter graph";
return false;
}
AudioParams from_fixed = FixChannelLayout(from);
AudioParams to_fixed = FixChannelLayout(to);
qDebug() << "AudioProcessor::Open: from sample_rate="
<< from_fixed.sample_rate() << "channels="
<< from_fixed.channel_count() << "layout_mask=0x" << Qt::hex
<< from_fixed.channel_layout().u.mask << "to sample_rate="
<< from_fixed.channel_layout() << "to sample_rate="
<< to_fixed.sample_rate() << "channels=" << to_fixed.channel_count()
<< "layout_mask=0x" << to_fixed.channel_layout().u.mask << Qt::dec;
<< "layout_mask=0x" << to_fixed.channel_layout() << Qt::dec;
// Set up audio buffer args
char filter_args[200];
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from_fixed.format());
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to_fixed.format());
snprintf(
filter_args, 200,
"time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
1, from_fixed.sample_rate(), from_fixed.sample_rate(), from_fmt_,
from_fixed.channel_layout().u.mask);
FBAudioGraphConfig config;
memset(&config, 0, sizeof(config));
config.in_sample_rate = from_fixed.sample_rate();
config.in_channel_layout_mask = from_fixed.channel_layout();
config.in_sample_format =
FFmpegUtils::GetFFmpegSampleFormat(from_fixed.format());
config.in_channels = from_fixed.channel_count();
int r;
config.out_sample_rate = to_fixed.sample_rate();
config.out_channel_layout_mask = to_fixed.channel_layout();
config.out_sample_format =
FFmpegUtils::GetFFmpegSampleFormat(to_fixed.format());
config.out_channels = to_fixed.channel_count();
config.out_is_planar = to_fixed.format().is_planar() ? 1 : 0;
// Create buffersrc (input)
r = avfilter_graph_create_filter(&buffersrc_ctx_,
avfilter_get_by_name("abuffer"), "in",
filter_args, nullptr, filter_graph_);
if (r < 0) {
qCritical() << "Failed to create buffersrc:" << r;
Close();
config.tempo = tempo;
graph_ = fb_audio_graph_create(&config);
if (!graph_) {
qCritical() << "Failed to create audio filter graph";
return false;
}
// Store "previous" filter for linking
AVFilterContext *previous_filter = buffersrc_ctx_;
// Create tempo
bool create_tempo;
if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) {
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
// those boundaries, we need to daisychain more than one together.
double base = (tempo > 1.0) ? 2.0 : 0.5;
double speed_log = log(tempo) / log(base);
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
int whole = std::floor(speed_log);
// Set speed_log to the remainder
speed_log -= whole;
for (int i = 0; i <= whole; i++) {
double filter_tempo = (i == whole) ? std::pow(base, speed_log) :
base;
/*
if (qFuzzyCompare(filter_tempo, 1.0)) {
// This filter would do nothing
continue;
}*/
previous_filter =
CreateTempoFilter(filter_graph_, previous_filter, filter_tempo);
if (!previous_filter) {
qCritical() << "Failed to create audio tempo filter";
Close();
return false;
}
}
}
// Create conversion filter
auto ch1 = from_fixed.channel_layout();
auto ch2 = to_fixed.channel_layout();
if (from_fixed.sample_rate() != to_fixed.sample_rate() ||
av_channel_layout_compare(&ch1, &ch2) ||
from_fixed.format() != to_fixed.format() ||
(to_fixed.format().is_planar() &&
create_tempo)) { // Tempo processor automatically converts to packed,
// so if the desired output is planar, it'll need
// to be converted
snprintf(filter_args, 200,
"sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
av_get_sample_fmt_name(to_fmt_), to_fixed.sample_rate(),
to_fixed.channel_layout().u.mask);
AVFilterContext *c;
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"),
"fmt", filter_args, nullptr,
filter_graph_);
if (r < 0) {
qCritical() << "Failed to create format conversion filter:" << r
<< filter_args;
Close();
return false;
}
r = avfilter_link(previous_filter, 0, c, 0);
if (r < 0) {
qCritical() << "Failed to link filters:" << r;
Close();
return false;
}
previous_filter = c;
}
// Create buffersink (output)
r = avfilter_graph_create_filter(&buffersink_ctx_,
avfilter_get_by_name("abuffersink"), "out",
nullptr, nullptr, filter_graph_);
if (r < 0) {
qCritical() << "Failed to create buffersink:" << r;
Close();
return false;
}
r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0);
if (r < 0) {
qCritical() << "Failed to link filters:" << r;
Close();
return false;
}
char *dump = avfilter_graph_dump(filter_graph_, nullptr);
qDebug() << dump;
av_free(dump);
r = avfilter_graph_config(filter_graph_, nullptr);
if (r < 0) {
qCritical() << "Failed to configure graph:" << r;
Close();
return false;
}
in_frame_ = av_frame_alloc();
if (in_frame_) {
in_frame_->sample_rate = from_fixed.sample_rate();
in_frame_->format = from_fmt_;
in_frame_->ch_layout = from_fixed.channel_layout();
in_frame_->pts = 0;
} else {
qCritical() << "Failed to allocate input frame";
Close();
return false;
}
out_frame_ = av_frame_alloc();
out_frame_ = fb_frame_alloc();
if (!out_frame_) {
qCritical() << "Failed to allocate output frame";
Close();
@@ -258,21 +125,12 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
void AudioProcessor::Close()
{
if (filter_graph_) {
avfilter_graph_free(&filter_graph_);
filter_graph_ = nullptr;
buffersrc_ctx_ = nullptr;
buffersink_ctx_ = nullptr;
}
if (in_frame_) {
av_frame_free(&in_frame_);
in_frame_ = nullptr;
if (graph_) {
fb_audio_graph_free(&graph_);
}
if (out_frame_) {
av_frame_free(&out_frame_);
out_frame_ = nullptr;
fb_frame_free(&out_frame_);
}
}
@@ -287,15 +145,9 @@ int AudioProcessor::Convert(float **in, int nb_in_samples,
int r = 0;
if (in && nb_in_samples) {
// Set frame parameters
in_frame_->nb_samples = nb_in_samples;
for (int i = 0; i < from_.channel_count(); i++) {
in_frame_->data[i] = reinterpret_cast<uint8_t *>(in[i]);
in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples);
}
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_,
AV_BUFFERSRC_FLAG_KEEP_REF);
r = fb_audio_graph_push(
graph_, reinterpret_cast<const uint8_t *const *>(in),
nb_in_samples);
if (r < 0) {
qCritical() << "Failed to add frame to buffersrc:" << r;
return r;
@@ -315,10 +167,10 @@ int AudioProcessor::Convert(float **in, int nb_in_samples,
int byte_offset = 0;
while (true) {
av_frame_unref(out_frame_);
r = av_buffersink_get_frame(buffersink_ctx_, out_frame_);
if (r < 0) {
if (r == AVERROR(EAGAIN)) {
r = fb_audio_graph_pull(graph_, out_frame_);
if (r <= 0) {
if (r == 0) {
// No more output available right now
r = 0;
} else {
// Handle unexpected error
@@ -327,20 +179,19 @@ int AudioProcessor::Convert(float **in, int nb_in_samples,
break;
}
int nb_bytes =
out_frame_->nb_samples * to_.bytes_per_sample_per_channel();
int nb_bytes = fb_frame_get_nb_samples(out_frame_) *
to_.bytes_per_sample_per_channel();
if (to_.format().is_packed()) {
nb_bytes *= to_.channel_count();
}
for (int i = 0; i < nb_channels; i++) {
result[i].resize(byte_offset + nb_bytes);
memcpy(result[i].data() + byte_offset, out_frame_->data[i],
nb_bytes);
memcpy(result[i].data() + byte_offset,
fb_frame_get_data(out_frame_, i), nb_bytes);
}
byte_offset += nb_bytes;
}
av_frame_unref(out_frame_);
}
return r;
@@ -348,31 +199,10 @@ int AudioProcessor::Convert(float **in, int nb_in_samples,
void AudioProcessor::Flush()
{
int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr,
AV_BUFFERSRC_FLAG_KEEP_REF);
int r = fb_audio_graph_push(graph_, nullptr, 0);
if (r < 0) {
qCritical() << "Failed to flush:" << r;
}
}
AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph *graph,
AVFilterContext *link,
const double &tempo)
{
// Set up tempo param, which is taken as a C string
char speed_param[20];
snprintf(speed_param, 20, "%f", tempo);
AVFilterContext *tempo_ctx = nullptr;
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"),
"atempo", speed_param, nullptr,
graph) >= 0 &&
avfilter_link(link, 0, tempo_ctx, 0) == 0) {
return tempo_ctx;
}
return nullptr;
}
}
+4 -18
View File
@@ -26,9 +26,7 @@
#include <olive/core/core.h>
#include <QByteArray>
extern "C" {
#include <libavfilter/avfilter.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "common/define.h"
@@ -52,7 +50,7 @@ public:
bool IsOpen() const
{
return filter_graph_;
return graph_;
}
using Buffer = QVector<QByteArray>;
@@ -70,25 +68,13 @@ public:
}
private:
static AVFilterContext *CreateTempoFilter(AVFilterGraph *graph,
AVFilterContext *link,
const double &tempo);
AVFilterGraph *filter_graph_;
AVFilterContext *buffersrc_ctx_;
AVFilterContext *buffersink_ctx_;
FBAudioGraph *graph_;
AudioParams from_;
AVSampleFormat from_fmt_;
AudioParams to_;
AVSampleFormat to_fmt_;
AVFrame *in_frame_;
AVFrame *out_frame_;
FBFrame *out_frame_;
};
}
+1 -1
View File
@@ -98,7 +98,7 @@ ConformManager::GetConformedFilename(const QString &cache_path,
QString::number(stream.stream()),
QString::number(params.sample_rate()),
QString::number(params.format()),
QString::number(params.channel_layout().u.mask),
QString::number(params.channel_layout()),
QString::number(i));
filenames[i] = QDir(cache_path).filePath(index_fn);
+1 -1
View File
@@ -252,7 +252,7 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration)
{
if (duration != AV_NOPTS_VALUE && duration != 0) {
if (duration != FB_NOPTS_VALUE && duration != 0) {
emit IndexProgress(static_cast<double>(ts) /
static_cast<double>(duration));
}
-4
View File
@@ -22,10 +22,6 @@
#ifndef DECODER_H
#define DECODER_H
extern "C" {
#include <libswresample/swresample.h>
}
#include <QFileInfo>
#include <QMutex>
#include <QObject>
+2 -6
View File
@@ -300,7 +300,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
writer->writeTextElement(
QStringLiteral("channellayout"),
QString::number(audio_params().channel_layout().u.mask));
QString::number(audio_params().channel_layout()));
writer->writeTextElement(
QStringLiteral("format"),
QString::fromStdString(audio_params_.format().to_string()));
@@ -542,12 +542,8 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader)
audio_params_.set_sample_rate(
reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("channellayout")) {
AVChannelLayout av_channel_layout;
av_channel_layout_from_mask(
&av_channel_layout,
audio_params_.set_channel_layout(
reader->readElementText().toLongLong());
audio_params_.set_channel_layout(av_channel_layout);
av_channel_layout_uninit(&av_channel_layout);
} else if (reader->name() == QStringLiteral("format")) {
audio_params_.set_format(SampleFormat::from_string(
reader->readElementText().toStdString()));
File diff suppressed because it is too large Load Diff
+31 -109
View File
@@ -22,17 +22,9 @@
#ifndef FFMPEGDECODER_H
#define FFMPEGDECODER_H
// Fixes weird define issue when including <avfilter.h>
#include <inttypes.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include <QTimer>
#include <QVector>
@@ -45,7 +37,10 @@ namespace olive
{
/**
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
* @brief A Decoder derivative that uses the ffmpeg_bridge library as an Olive decoder
*
* All media access goes through the pure C API of the ffmpeg_bridge shared
* library; this class never sees an FFmpeg structure or function.
*/
class FFmpegDecoder : public Decoder {
Q_OBJECT
@@ -84,88 +79,10 @@ protected:
virtual rational GetAudioStartOffset() const override;
private:
class Instance {
public:
Instance();
~Instance()
{
Close();
}
bool Open(const char *filename, int stream_index);
bool IsOpen() const
{
return fmt_ctx_;
}
void Close();
/**
* @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_)
*
* @return
*
* An FFmpeg error code, or >= 0 on success
*/
int GetFrame(AVPacket *pkt, AVFrame *frame);
const char *GetSubtitleHeader() const;
int GetSubtitle(AVPacket *pkt, AVSubtitle *sub);
int GetPacket(AVPacket *pkt);
void Seek(int64_t timestamp);
AVFormatContext *fmt_ctx() const
{
return fmt_ctx_;
}
AVStream *avstream() const
{
return avstream_;
}
AVCodecContext *codec_ctx()
{
return codec_ctx_;
}
bool hwaccel_enabled() const
{
return hwaccel_enabled_;
}
AVPixelFormat hw_pix_fmt() const
{
return hw_pix_fmt_;
}
private:
static AVHWDeviceType ChooseHardwareDevice();
static AVPixelFormat GetHardwareFormat(AVCodecContext *ctx,
const AVPixelFormat *pix_fmts);
bool InitHardwareAcceleration(const AVCodec *codec);
void CleanupHardwareAcceleration();
AVFormatContext *fmt_ctx_;
AVCodecContext *codec_ctx_;
AVStream *avstream_;
AVDictionary *opts_;
AVBufferRef *hw_device_ctx_;
AVHWDeviceType hw_device_type_;
AVPixelFormat hw_pix_fmt_;
bool hwaccel_enabled_;
};
/**
* @brief Handle an FFmpeg error code
* @brief Handle a bridge error code
*
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* Uses the bridge API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* function also automatically closes the Decoder.
*
* @param error_code
@@ -176,15 +93,10 @@ private:
AVFramePtr TransferHardwareFrame(AVFramePtr f);
static PixelFormat GetNativePixelFormat(AVPixelFormat pix_fmt);
static int GetNativeChannelCount(AVPixelFormat pix_fmt);
static PixelFormat GetNativePixelFormat(int pix_fmt);
static int GetNativeChannelCount(int pix_fmt);
static AVChannelLayout ValidateChannelLayout(AVStream *stream);
static const char *
GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing);
static bool IsPixelFormatGLSLCompatible(AVPixelFormat f);
static bool IsPixelFormatGLSLCompatible(int f);
AVFramePtr GetFrameFromCache(const int64_t &t) const;
@@ -202,17 +114,17 @@ private:
static int MaximumQueueSize();
SwsContext *sws_ctx_;
int sws_src_width_;
int sws_src_height_;
AVPixelFormat sws_src_format_;
int sws_dst_width_;
int sws_dst_height_;
AVPixelFormat sws_dst_format_;
AVColorRange sws_colrange_;
AVColorSpace sws_colspace_;
FBScaler *scaler_;
int scaler_src_width_;
int scaler_src_height_;
int scaler_src_format_;
int scaler_dst_width_;
int scaler_dst_height_;
int scaler_dst_format_;
int scaler_colrange_;
int scaler_colspace_;
AVPacket *working_packet_;
FBPacket *working_packet_;
int64_t second_ts_;
@@ -221,7 +133,17 @@ private:
bool cache_at_zero_;
bool cache_at_eof_;
Instance instance_;
FBDecoder *instance_;
// Stream parameters cached on open (the stream object itself lives
// inside the bridge library)
rational stream_time_base_;
int64_t stream_start_time_;
int64_t stream_duration_;
int64_t format_start_time_;
int input_sample_format_;
int input_sample_rate_;
uint64_t input_channel_layout_mask_;
};
}
File diff suppressed because it is too large Load Diff
+12 -49
View File
@@ -22,19 +22,20 @@
#ifndef FFMPEGENCODER_H
#define FFMPEGENCODER_H
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "codec/encoder.h"
namespace olive
{
/**
* @brief An Encoder derivative that uses the ffmpeg_bridge library for encoding
*
* All encoding work happens inside the ffmpeg_bridge shared library through
* its pure C API; this class only translates EncodingParams into a bridge
* configuration and forwards calls.
*/
class FFmpegEncoder : public Encoder {
Q_OBJECT
public:
@@ -67,54 +68,16 @@ public:
private:
/**
* @brief Handle an FFmpeg error code
*
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* function also automatically closes the Decoder.
*
* @param error_code
* @brief Copy the last error message from the bridge into the encoder error state
*/
void FFmpegError(const QString &context, int error_code);
void SetErrorFromBridge();
bool WriteAVFrame(AVFrame *frame, AVCodecContext *codec_ctx,
AVStream *stream);
static int ExportCodecToBridge(ExportCodec::Codec c);
bool InitializeStream(enum AVMediaType type, AVStream **stream,
AVCodecContext **codec_ctx,
const ExportCodec::Codec &codec);
bool InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx,
const AVCodec *codec);
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx,
const AVCodec *codec);
FBEncoder *encoder_;
void FlushEncoders();
void FlushCodecCtx(AVCodecContext *codec_ctx, AVStream *stream);
bool InitializeResampleContext(const AudioParams &audio);
static const AVCodec *GetEncoder(ExportCodec::Codec c,
SampleFormat aformat);
AVFormatContext *fmt_ctx_;
AVStream *video_stream_;
AVCodecContext *video_codec_ctx_;
AVFilterGraph *video_scale_ctx_;
AVFilterContext *video_buffersrc_ctx_;
AVFilterContext *video_buffersink_ctx_;
PixelFormat video_conversion_fmt_;
AVStream *audio_stream_;
AVCodecContext *audio_codec_ctx_;
SwrContext *audio_resample_ctx_;
AVFrame *audio_frame_;
int audio_max_samples_;
int audio_frame_offset_;
int audio_write_count_;
AVStream *subtitle_stream_;
AVCodecContext *subtitle_codec_ctx_;
bool open_;
};
-1
View File
@@ -17,7 +17,6 @@
target_sources(libolive-editor PRIVATE
cancelableobject.h
channellayout.h
commandlineparser.cpp
commandlineparser.h
crashpadinterface.cpp
+97 -6
View File
@@ -22,25 +22,116 @@
#ifndef AVFRAMEPTR_H
#define AVFRAMEPTR_H
extern "C" {
#include <libavutil/frame.h>
}
#include <stdint.h>
#include <memory>
#include <ffmpeg_bridge/ffmpeg_bridge.h>
namespace olive
{
/**
* @brief C++ adapter around the ffmpeg_bridge frame handle
*
* Mirrors the AVFrame field access the codebase used to perform directly,
* but every operation goes through the pure C bridge API so the editor
* never touches FFmpeg itself. The underlying frame object always lives
* inside the bridge library.
*/
class AVFrame {
public:
AVFrame() :
handle_(fb_frame_alloc())
{
}
explicit AVFrame(FBFrame *handle) :
handle_(handle)
{
}
~AVFrame()
{
if (handle_) {
fb_frame_free(&handle_);
}
}
AVFrame(const AVFrame &) = delete;
AVFrame &operator=(const AVFrame &) = delete;
FBFrame *handle() const { return handle_; }
int width() const { return fb_frame_get_width(handle_); }
void set_width(int w) { fb_frame_set_width(handle_, w); }
int height() const { return fb_frame_get_height(handle_); }
void set_height(int h) { fb_frame_set_height(handle_, h); }
int format() const { return fb_frame_get_format(handle_); }
void set_format(int f) { fb_frame_set_format(handle_, f); }
int64_t pts() const { return fb_frame_get_pts(handle_); }
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
int64_t best_effort_timestamp() const
{
return fb_frame_get_best_effort_timestamp(handle_);
}
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
int color_range() const { return fb_frame_get_color_range(handle_); }
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
int colorspace() const { return fb_frame_get_colorspace(handle_); }
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
uint64_t channel_layout_mask() const
{
return fb_frame_get_channel_layout_mask(handle_);
}
void set_channel_layout_mask(uint64_t m)
{
fb_frame_set_channel_layout_mask(handle_, m);
}
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
int hw_transfer_data(const AVFrame *src)
{
return fb_frame_hw_transfer_data(handle_, src->handle_);
}
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
int make_writable() { return fb_frame_make_writable(handle_); }
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
const uint8_t *data(int plane) const
{
return fb_frame_get_data_const(handle_, plane);
}
void set_data(int plane, uint8_t *d)
{
fb_frame_set_data(handle_, plane, d);
}
int linesize(int plane) const
{
return fb_frame_get_linesize(handle_, plane);
}
void set_linesize(int plane, int l)
{
fb_frame_set_linesize(handle_, plane, l);
}
private:
FBFrame *handle_;
};
using AVFramePtr = std::shared_ptr<AVFrame>;
inline AVFramePtr CreateAVFramePtr(AVFrame *f)
inline AVFramePtr CreateAVFramePtr(FBFrame *f)
{
return std::shared_ptr<AVFrame>(f, [](AVFrame *g) { av_frame_free(&g); });
return std::make_shared<AVFrame>(f);
}
inline AVFramePtr CreateAVFramePtr()
{
return CreateAVFramePtr(av_frame_alloc());
return std::make_shared<AVFrame>();
}
}
-35
View File
@@ -1,35 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef CHANNELLAYOUT_H
#define CHANNELLAYOUT_H
/**
* Channel Layouts header
*
* We don't do much here at the moment, audio is a much simpler beast than video nowadays and FFmpeg seems to cover it
* fairly well.
*/
extern "C" {
#include <libavcodec/avcodec.h>
}
#endif // CHANNELLAYOUT_H
+62 -95
View File
@@ -24,135 +24,110 @@
namespace olive
{
AVPixelFormat
FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt,
PixelFormat maximum)
int FFmpegUtils::GetCompatibleBridgePixelFormat(int pix_fmt,
PixelFormat maximum)
{
AVPixelFormat possible_pix_fmts[4];
int possible_pix_fmts[4];
possible_pix_fmts[0] = AV_PIX_FMT_RGBA;
possible_pix_fmts[0] = FB_PIX_FMT_RGBA;
if (maximum == PixelFormat::U8) {
possible_pix_fmts[1] = AV_PIX_FMT_NONE;
possible_pix_fmts[1] = FB_PIX_FMT_NONE;
} else {
possible_pix_fmts[1] = AV_PIX_FMT_RGBA64;
possible_pix_fmts[1] = FB_PIX_FMT_RGBA64LE;
if (maximum == PixelFormat::F32) {
possible_pix_fmts[2] = AV_PIX_FMT_RGBAF32;
possible_pix_fmts[3] = AV_PIX_FMT_NONE;
possible_pix_fmts[2] = FB_PIX_FMT_RGBAF32LE;
possible_pix_fmts[3] = FB_PIX_FMT_NONE;
} else {
possible_pix_fmts[2] = AV_PIX_FMT_NONE;
possible_pix_fmts[2] = FB_PIX_FMT_NONE;
}
}
return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt, 1,
nullptr);
return fb_find_best_pix_fmt_of_list(possible_pix_fmts, pix_fmt);
}
SampleFormat FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp_fmt)
SampleFormat FFmpegUtils::GetNativeSampleFormat(int smp_fmt)
{
switch (smp_fmt) {
case AV_SAMPLE_FMT_U8:
case FB_SAMPLE_FMT_U8:
return SampleFormat::U8;
case AV_SAMPLE_FMT_S16:
case FB_SAMPLE_FMT_S16:
return SampleFormat::S16;
case AV_SAMPLE_FMT_S32:
case FB_SAMPLE_FMT_S32:
return SampleFormat::S32;
case AV_SAMPLE_FMT_S64:
case FB_SAMPLE_FMT_S64:
return SampleFormat::S64;
case AV_SAMPLE_FMT_FLT:
case FB_SAMPLE_FMT_FLT:
return SampleFormat::F32;
case AV_SAMPLE_FMT_DBL:
case FB_SAMPLE_FMT_DBL:
return SampleFormat::F64;
case AV_SAMPLE_FMT_U8P:
case FB_SAMPLE_FMT_U8P:
return SampleFormat::U8P;
case AV_SAMPLE_FMT_S16P:
case FB_SAMPLE_FMT_S16P:
return SampleFormat::S16P;
case AV_SAMPLE_FMT_S32P:
case FB_SAMPLE_FMT_S32P:
return SampleFormat::S32P;
case AV_SAMPLE_FMT_S64P:
case FB_SAMPLE_FMT_S64P:
return SampleFormat::S64P;
case AV_SAMPLE_FMT_FLTP:
case FB_SAMPLE_FMT_FLTP:
return SampleFormat::F32P;
case AV_SAMPLE_FMT_DBLP:
case FB_SAMPLE_FMT_DBLP:
return SampleFormat::F64P;
case AV_SAMPLE_FMT_NONE:
case AV_SAMPLE_FMT_NB:
default:
break;
}
return SampleFormat::INVALID;
}
AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const SampleFormat &smp_fmt)
int FFmpegUtils::GetFFmpegSampleFormat(const SampleFormat &smp_fmt)
{
switch (smp_fmt) {
case SampleFormat::U8:
return AV_SAMPLE_FMT_U8;
return FB_SAMPLE_FMT_U8;
case SampleFormat::S16:
return AV_SAMPLE_FMT_S16;
return FB_SAMPLE_FMT_S16;
case SampleFormat::S32:
return AV_SAMPLE_FMT_S32;
return FB_SAMPLE_FMT_S32;
case SampleFormat::S64:
return AV_SAMPLE_FMT_S64;
return FB_SAMPLE_FMT_S64;
case SampleFormat::F32:
return AV_SAMPLE_FMT_FLT;
return FB_SAMPLE_FMT_FLT;
case SampleFormat::F64:
return AV_SAMPLE_FMT_DBL;
return FB_SAMPLE_FMT_DBL;
case SampleFormat::U8P:
return AV_SAMPLE_FMT_U8P;
return FB_SAMPLE_FMT_U8P;
case SampleFormat::S16P:
return AV_SAMPLE_FMT_S16P;
return FB_SAMPLE_FMT_S16P;
case SampleFormat::S32P:
return AV_SAMPLE_FMT_S32P;
return FB_SAMPLE_FMT_S32P;
case SampleFormat::S64P:
return AV_SAMPLE_FMT_S64P;
return FB_SAMPLE_FMT_S64P;
case SampleFormat::F32P:
return AV_SAMPLE_FMT_FLTP;
return FB_SAMPLE_FMT_FLTP;
case SampleFormat::F64P:
return AV_SAMPLE_FMT_DBLP;
return FB_SAMPLE_FMT_DBLP;
case SampleFormat::INVALID:
case SampleFormat::COUNT:
break;
}
return AV_SAMPLE_FMT_NONE;
return FB_SAMPLE_FMT_NONE;
}
int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs)
{
switch (cs) {
case AVCOL_SPC_BT709:
return SWS_CS_ITU709;
case AVCOL_SPC_FCC:
return SWS_CS_FCC;
case AVCOL_SPC_BT470BG:
return SWS_CS_ITU624;
case AVCOL_SPC_SMPTE170M:
return SWS_CS_SMPTE170M;
case AVCOL_SPC_SMPTE240M:
return SWS_CS_SMPTE240M;
case AVCOL_SPC_BT2020_NCL:
return SWS_CS_BT2020;
default:
break;
}
return SWS_CS_DEFAULT;
}
AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f)
int FFmpegUtils::ConvertJPEGSpaceToRegularSpace(int f)
{
switch (f) {
case AV_PIX_FMT_YUVJ420P:
return AV_PIX_FMT_YUV420P;
case AV_PIX_FMT_YUVJ422P:
return AV_PIX_FMT_YUV422P;
case AV_PIX_FMT_YUVJ444P:
return AV_PIX_FMT_YUV444P;
case AV_PIX_FMT_YUVJ440P:
return AV_PIX_FMT_YUV440P;
case AV_PIX_FMT_YUVJ411P:
return AV_PIX_FMT_YUV411P;
case FB_PIX_FMT_YUVJ420P:
return FB_PIX_FMT_YUV420P;
case FB_PIX_FMT_YUVJ422P:
return FB_PIX_FMT_YUV422P;
case FB_PIX_FMT_YUVJ444P:
return FB_PIX_FMT_YUV444P;
case FB_PIX_FMT_YUVJ440P:
return FB_PIX_FMT_YUV440P;
case FB_PIX_FMT_YUVJ411P:
return FB_PIX_FMT_YUV411P;
default:
break;
}
@@ -160,25 +135,21 @@ AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f)
return f;
}
AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt,
int channel_layout)
int FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt,
int channel_layout)
{
if (channel_layout == VideoParams::kRGBChannelCount) {
switch (pix_fmt) {
case PixelFormat::U8:
return AV_PIX_FMT_RGB24;
return FB_PIX_FMT_RGB24;
case PixelFormat::U10:
return AV_PIX_FMT_NONE;
return FB_PIX_FMT_NONE;
case PixelFormat::U16:
return AV_PIX_FMT_RGB48;
return FB_PIX_FMT_RGB48LE;
case PixelFormat::F16:
#ifdef HAVE_AV_PIX_FMT_RGBF16
return AV_PIX_FMT_RGBF16;
#else
return AV_PIX_FMT_RGB48;
#endif
return FB_PIX_FMT_RGBF16LE;
case PixelFormat::F32:
return AV_PIX_FMT_RGBF32;
return FB_PIX_FMT_RGBF32LE;
case PixelFormat::INVALID:
case PixelFormat::COUNT:
break;
@@ -186,26 +157,22 @@ AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt,
} else if (channel_layout == VideoParams::kRGBAChannelCount) {
switch (pix_fmt) {
case PixelFormat::U8:
return AV_PIX_FMT_RGBA;
return FB_PIX_FMT_RGBA;
case PixelFormat::U10:
return AV_PIX_FMT_NONE;
return FB_PIX_FMT_NONE;
case PixelFormat::U16:
return AV_PIX_FMT_RGBA64;
return FB_PIX_FMT_RGBA64LE;
case PixelFormat::F16:
#ifdef HAVE_AV_PIX_FMT_RGBAF16
return AV_PIX_FMT_RGBAF16;
#else
return AV_PIX_FMT_RGBA64;
#endif
return FB_PIX_FMT_RGBAF16LE;
case PixelFormat::F32:
return AV_PIX_FMT_RGBAF32;
return FB_PIX_FMT_RGBAF32LE;
case PixelFormat::INVALID:
case PixelFormat::COUNT:
break;
}
}
return AV_PIX_FMT_NONE;
return FB_PIX_FMT_NONE;
}
PixelFormat FFmpegUtils::GetCompatiblePixelFormat(const PixelFormat &pix_fmt)
+25 -27
View File
@@ -22,15 +22,10 @@
#ifndef FFMPEGABSTRACTION_H
#define FFMPEGABSTRACTION_H
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include <olive/core/core.h>
#include "common/avframeptr.h"
#include "render/videoparams.h"
namespace olive
@@ -38,43 +33,46 @@ namespace olive
using namespace core;
/**
* @brief C++ adapter mapping Olive's native enums to bridge pixel/sample
* formats
*
* All "FFmpeg" formats here are actually the opaque FBPixelFormat /
* FBSampleFormat constants of the ffmpeg_bridge library; no FFmpeg header
* or structure is ever seen by the editor.
*/
class FFmpegUtils {
public:
/**
* @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss
* @brief Returns a bridge pixel format that can be used to convert a frame to a data type Olive supports with minimal data loss
*
* Named distinctly from the native PixelFormat overload below: with both
* taking a single argument, an unscoped enum argument would silently
* prefer an int overload over the PixelFormat one.
*/
static AVPixelFormat
GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt,
PixelFormat maximum = PixelFormat::INVALID);
static int GetCompatibleBridgePixelFormat(
int pix_fmt, PixelFormat maximum = PixelFormat::INVALID);
/**
* @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss
* @brief Returns a native pixel format that can be used to convert from a native frame to a bridge frame with minimal data loss
*/
static PixelFormat GetCompatiblePixelFormat(const PixelFormat &pix_fmt);
/**
* @brief Returns an FFmpeg pixel format for a given native pixel format
* @brief Returns a bridge pixel format for a given native pixel format
*/
static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat &pix_fmt,
int channel_layout);
static int GetFFmpegPixelFormat(const PixelFormat &pix_fmt,
int channel_layout);
/**
* @brief Returns a native sample format type for a given AVSampleFormat
* @brief Returns a native sample format type for a given bridge sample format
*/
static SampleFormat GetNativeSampleFormat(const AVSampleFormat &smp_fmt);
static SampleFormat GetNativeSampleFormat(int smp_fmt);
/**
* @brief Returns an FFmpeg sample format type for a given native type
* @brief Returns a bridge sample format type for a given native type
*/
static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat &smp_fmt);
/**
* @brief Returns an SWS_CS_* macro from an AVColorSpace enum member
*
* Why aren't these the same thing anyway? And for that matter, why doesn't FFmpeg provide a
* convenience function to do this conversion for us? Who knows, but here we are.
*/
static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs);
static int GetFFmpegSampleFormat(const SampleFormat &smp_fmt);
/**
* @brief Convert "JPEG"/full-range colorspace to its regular counterpart
@@ -83,7 +81,7 @@ public:
* time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range
* aware), we use this function.
*/
static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f);
static int ConvertJPEGSpaceToRegularSpace(int f);
};
}
+5 -3
View File
@@ -204,7 +204,8 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt,
48000);
SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"),
NodeValue::kInt, AV_CH_LAYOUT_STEREO);
NodeValue::kInt,
QVariant::fromValue(static_cast<int64_t>(kChannelLayoutStereo)));
SetEntryInternal(
QStringLiteral("AudioOutputSampleFormat"), NodeValue::kText,
QString::fromStdString(SampleFormat(SampleFormat::S16).to_string()));
@@ -216,7 +217,8 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"),
NodeValue::kInt, 48000);
SetEntryInternal(QStringLiteral("AudioRecordingChannelLayout"),
NodeValue::kInt, AV_CH_LAYOUT_STEREO);
NodeValue::kInt,
QVariant::fromValue(static_cast<int64_t>(kChannelLayoutStereo)));
SetEntryInternal(
QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kText,
QString::fromStdString(SampleFormat(SampleFormat::S16).to_string()));
@@ -251,7 +253,7 @@ void Config::SetDefaults()
NodeValue::kInt, 48000);
SetEntryInternal(
QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt,
QVariant::fromValue(static_cast<int64_t>(AV_CH_LAYOUT_STEREO)));
QVariant::fromValue(static_cast<int64_t>(kChannelLayoutStereo)));
// Online/offline settings
SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt,
@@ -145,14 +145,12 @@ void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset)
void SequenceDialogParameterTab::SavePresetClicked()
{
AVChannelLayout layout = GetSelectedAudioChannelLayout();
emit SaveParametersAsPreset(SequencePreset(
QString(), GetSelectedVideoWidth(), GetSelectedVideoHeight(),
GetSelectedVideoFrameRate(), GetSelectedVideoPixelAspect(),
GetSelectedVideoInterlacingMode(), GetSelectedAudioSampleRate(), layout,
GetSelectedPreviewResolution(), GetSelectedPreviewFormat(),
GetSelectedPreviewAutoCache()));
av_channel_layout_uninit(&layout);
GetSelectedVideoInterlacingMode(), GetSelectedAudioSampleRate(),
GetSelectedAudioChannelLayout(), GetSelectedPreviewResolution(),
GetSelectedPreviewFormat(), GetSelectedPreviewAutoCache()));
}
void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
@@ -67,7 +67,7 @@ public:
return audio_sample_rate_field_->GetSampleRate();
}
[[nodiscard]] AVChannelLayout GetSelectedAudioChannelLayout() const
[[nodiscard]] uint64_t GetSelectedAudioChannelLayout() const
{
return audio_channels_field_->GetChannelLayout();
}
@@ -114,8 +114,7 @@ SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width,
OLIVE_CONFIG("OfflinePixelFormat").toInt());
const bool default_autocache = false;
QTreeWidgetItem *parent = CreateFolder(name);
AVChannelLayout layout;
av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_STEREO);
const uint64_t layout = kChannelLayoutStereo;
AddStandardItem(parent,
std::make_shared<SequencePreset>(
tr("%1 23.976 FPS").arg(name), width, height,
@@ -146,7 +145,6 @@ SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width,
rational(60000, 1001), VideoParams::kPixelAspectSquare,
VideoParams::kInterlaceNone, 48000, layout, divider,
default_format, default_autocache));
av_channel_layout_uninit(&layout);
return parent;
}
@@ -159,8 +157,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(
const bool default_autocache = false;
QTreeWidgetItem *parent = CreateFolder(name);
preset_tree_->addTopLevelItem(parent);
AVChannelLayout layout;
av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_STEREO);
const uint64_t layout = kChannelLayoutStereo;
AddStandardItem(
parent, std::make_shared<SequencePreset>(
tr("%1 Standard").arg(name), width, height, frame_rate,
@@ -171,7 +168,6 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(
tr("%1 Widescreen").arg(name), width, height, frame_rate,
wide_par, VideoParams::kInterlacedBottomFirst, 48000,
layout, divider, default_format, default_autocache));
av_channel_layout_uninit(&layout);
return parent;
}
+5 -6
View File
@@ -39,7 +39,7 @@ public:
SequencePreset(const QString &name, int width, int height,
const rational &frame_rate, const rational &pixel_aspect,
VideoParams::Interlacing interlacing, int sample_rate,
AVChannelLayout &channel_layout, int preview_divider,
uint64_t channel_layout, int preview_divider,
PixelFormat preview_format, bool preview_autocache)
: width_(width)
, height_(height)
@@ -76,8 +76,7 @@ public:
} else if (reader->name() == QStringLiteral("samplerate")) {
sample_rate_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("chlayout")) {
channel_layout_.u.mask =
reader->readElementText().toULongLong();
channel_layout_ = reader->readElementText().toULongLong();
} else if (reader->name() == QStringLiteral("divider")) {
preview_divider_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("format")) {
@@ -109,7 +108,7 @@ public:
writer->writeTextElement(QStringLiteral("samplerate"),
QString::number(sample_rate_));
writer->writeTextElement(QStringLiteral("chlayout"),
QString::number(channel_layout_.u.mask));
QString::number(channel_layout_));
writer->writeTextElement(QStringLiteral("divider"),
QString::number(preview_divider_));
writer->writeTextElement(QStringLiteral("format"),
@@ -148,7 +147,7 @@ public:
return sample_rate_;
}
AVChannelLayout channel_layout() const
uint64_t channel_layout() const
{
return channel_layout_;
}
@@ -175,7 +174,7 @@ private:
rational pixel_aspect_;
VideoParams::Interlacing interlacing_;
int sample_rate_;
AVChannelLayout channel_layout_;
uint64_t channel_layout_;
int preview_divider_;
PixelFormat preview_format_;
bool preview_autocache_;
-12
View File
@@ -28,10 +28,6 @@
*/
#include "OliveHost.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
}
#include <csignal>
@@ -406,14 +402,6 @@ int main(int argc, char *argv[])
}
#endif
// Register FFmpeg codecs and filters (deprecated in 4.0+)
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
#if LIBAVFILTER_VERSION_INT < AV_VERSION_INT(7, 14, 100)
avfilter_register_all();
#endif
// Enable Google Crashpad if compiled with it
#ifdef USE_CRASHPAD
if (!InitializeCrashpad()) {
+2 -4
View File
@@ -236,12 +236,10 @@ void ViewerOutput::set_default_parameters()
OLIVE_CONFIG("DefaultSequenceInterlacing")
.value<VideoParams::Interlacing>(),
1));
AVChannelLayout layout;
av_channel_layout_from_mask(
&layout, OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong());
SetAudioParams(
AudioParams(OLIVE_CONFIG("DefaultSequenceAudioFrequency").toInt(),
layout, kDefaultSampleFormat));
OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(),
kDefaultSampleFormat));
}
void ViewerOutput::InvalidateCache(const TimeRange &range, const QString &from,
@@ -59,7 +59,7 @@ void TypeSerializer::SaveAudioParams(QXmlStreamWriter *writer,
writer->writeTextElement(QStringLiteral("samplerate"),
QString::number(a.sample_rate()));
writer->writeTextElement(QStringLiteral("channellayout"),
QString::number(a.channel_layout().u.mask));
QString::number(a.channel_layout()));
writer->writeTextElement(QStringLiteral("format"),
QString::fromStdString(a.format().to_string()));
writer->writeTextElement(QStringLiteral("enabled"),
+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);
+168 -159
View File
@@ -51,15 +51,21 @@
#include "ofxhUtilities.h"
#include "ofxGPURender.h"
#include "olive/core/util/color.h"
extern "C" {
#include <libavutil/pixfmt.h>
#include <libavutil/pixdesc.h>
#include <libswscale/swscale.h>
}
#include <ffmpeg_bridge/ffmpeg_bridge.h>
// 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_GRAY16BE = FB_PIX_FMT_GRAY16LE - 1;
constexpr int FB_PIX_FMT_RGB48BE = FB_PIX_FMT_RGB48LE - 1;
constexpr int FB_PIX_FMT_RGBA64BE = FB_PIX_FMT_RGBA64LE - 1;
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;
// 作用:从 OFX Image 属性推导 FFmpeg 像素格式,并返回每像素字节数。
// Purpose: Infer FFmpeg pixel format from OFX image properties and return bytes-per-pixel.
static AVPixelFormat
static int
GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image,
int *bytes_per_pixel)
{
@@ -88,36 +94,29 @@ GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image,
channel_count = 1;
}
AVPixelFormat pix_fmt =
int pix_fmt =
olive::FFmpegUtils::GetFFmpegPixelFormat(pixel_format, channel_count);
if (pix_fmt == AV_PIX_FMT_NONE && channel_count == 1) {
if (pix_fmt == FB_PIX_FMT_NONE && channel_count == 1) {
if (pixel_format == olive::core::PixelFormat::U8) {
pix_fmt = AV_PIX_FMT_GRAY8;
pix_fmt = FB_PIX_FMT_GRAY8;
} else if (pixel_format == olive::core::PixelFormat::U16) {
pix_fmt = AV_PIX_FMT_GRAY16LE;
pix_fmt = FB_PIX_FMT_GRAY16LE;
} else if (pixel_format == olive::core::PixelFormat::F16) {
#ifdef HAVE_AV_PIX_FMT_GRAYF16
pix_fmt = AV_PIX_FMT_GRAYF16;
#else
pix_fmt = AV_PIX_FMT_GRAY16LE;
#endif
pix_fmt = FB_PIX_FMT_GRAYF16LE;
} else if (pixel_format == olive::core::PixelFormat::F32) {
pix_fmt = AV_PIX_FMT_GRAYF32;
pix_fmt = FB_PIX_FMT_GRAYF32LE;
}
}
if (pix_fmt == AV_PIX_FMT_NONE) {
return AV_PIX_FMT_NONE;
if (pix_fmt == FB_PIX_FMT_NONE) {
return FB_PIX_FMT_NONE;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
if (!desc) {
return AV_PIX_FMT_NONE;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
// fb_pix_fmt_bits_per_pixel returns 0 for unknown formats, which also
// covers the old "av_pix_fmt_desc_get returned nullptr" case.
int bits_per_pixel = fb_pix_fmt_bits_per_pixel(pix_fmt);
if (bits_per_pixel <= 0 || bits_per_pixel % 8 != 0) {
return AV_PIX_FMT_NONE;
return FB_PIX_FMT_NONE;
}
*bytes_per_pixel = bits_per_pixel / 8;
@@ -285,7 +284,7 @@ static void ApplyParamOverrides(OFX::Host::ImageEffect::Instance &instance,
}
}
static AVPixelFormat
static int
GetDestinationAVPixelFormat(const olive::VideoParams &params);
// 作用:读取 clip 偏好(像素深度与分量)并更新 VideoParams。
@@ -456,7 +455,7 @@ static int ConversionCost(const olive::VideoParams &src,
// Purpose: Check if params map to a valid AVPixelFormat.
static bool ParamsConvertible(const olive::VideoParams &params)
{
return GetDestinationAVPixelFormat(params) != AV_PIX_FMT_NONE;
return GetDestinationAVPixelFormat(params) != FB_PIX_FMT_NONE;
}
// 作用:在 clip 偏好无效时,选择一个插件支持的输出格式。
@@ -692,8 +691,8 @@ create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image)
}
int bytes_per_pixel = 0;
AVPixelFormat pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel);
if (pix_fmt == AV_PIX_FMT_NONE || bytes_per_pixel <= 0) {
int pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel);
if (pix_fmt == FB_PIX_FMT_NONE || bytes_per_pixel <= 0) {
qWarning().noquote()
<< "OFX output image has unsupported pixel format depth="
<< QString::fromStdString(
@@ -713,20 +712,20 @@ create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image)
src += bounds[1] * row_bytes + bounds[0] * bytes_per_pixel;
olive::AVFramePtr frame = olive::CreateAVFramePtr();
frame->width = width;
frame->height = height;
frame->format = pix_fmt;
frame->set_width(width);
frame->set_height(height);
frame->set_format(pix_fmt);
if (av_frame_get_buffer(frame.get(), 0) < 0) {
if (frame->get_buffer(0) < 0) {
return nullptr;
}
const int copy_bytes = width * bytes_per_pixel;
if (frame->linesize[0] == row_bytes && row_bytes == copy_bytes) {
std::memcpy(frame->data[0], src, copy_bytes * height);
if (frame->linesize(0) == row_bytes && row_bytes == copy_bytes) {
std::memcpy(frame->data(0), src, copy_bytes * height);
} else {
for (int y = 0; y < height; ++y) {
std::memcpy(frame->data[0] + y * frame->linesize[0],
std::memcpy(frame->data(0) + y * frame->linesize(0),
src + y * row_bytes, copy_bytes);
}
}
@@ -797,45 +796,45 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image,
if (needs_conversion) {
// Create source frame with actual format
AVPixelFormat src_fmt = AV_PIX_FMT_NONE;
int src_fmt = FB_PIX_FMT_NONE;
if (src_channel_count == 4) {
if (src_bytes_per_component == 1)
src_fmt = AV_PIX_FMT_RGBA;
src_fmt = FB_PIX_FMT_RGBA;
else if (src_bytes_per_component == 2)
src_fmt = AV_PIX_FMT_RGBA64LE;
src_fmt = FB_PIX_FMT_RGBA64LE;
else if (src_bytes_per_component == 4)
src_fmt = AV_PIX_FMT_RGBAF32LE;
src_fmt = FB_PIX_FMT_RGBAF32LE;
} else if (src_channel_count == 3) {
if (src_bytes_per_component == 1)
src_fmt = AV_PIX_FMT_RGB24;
src_fmt = FB_PIX_FMT_RGB24;
else if (src_bytes_per_component == 2)
src_fmt = AV_PIX_FMT_RGB48LE;
src_fmt = FB_PIX_FMT_RGB48LE;
else if (src_bytes_per_component == 4)
src_fmt = AV_PIX_FMT_RGBF32LE;
src_fmt = FB_PIX_FMT_RGBF32LE;
} else if (src_channel_count == 1) {
if (src_bytes_per_component == 1)
src_fmt = AV_PIX_FMT_GRAY8;
src_fmt = FB_PIX_FMT_GRAY8;
else if (src_bytes_per_component == 2)
src_fmt = AV_PIX_FMT_GRAY16LE;
src_fmt = FB_PIX_FMT_GRAY16LE;
else if (src_bytes_per_component == 4)
src_fmt = AV_PIX_FMT_GRAYF32LE;
src_fmt = FB_PIX_FMT_GRAYF32LE;
}
if (src_fmt != AV_PIX_FMT_NONE) {
if (src_fmt != FB_PIX_FMT_NONE) {
olive::AVFramePtr src_frame = olive::CreateAVFramePtr();
src_frame->width = width;
src_frame->height = height;
src_frame->format = src_fmt;
if (av_frame_get_buffer(src_frame.get(), 0) >= 0) {
src_frame->set_width(width);
src_frame->set_height(height);
src_frame->set_format(src_fmt);
if (src_frame->get_buffer(0) >= 0) {
// Copy source data row by row (or as a single block if strides match)
const int copy_bytes = width * src_bytes_per_pixel;
if (src_frame->linesize[0] == row_bytes &&
if (src_frame->linesize(0) == row_bytes &&
row_bytes == copy_bytes) {
memcpy(src_frame->data[0], src, copy_bytes * height);
memcpy(src_frame->data(0), src, copy_bytes * height);
} else {
for (int y = 0; y < height; ++y) {
memcpy(src_frame->data[0] + y * src_frame->linesize[0],
memcpy(src_frame->data(0) + y * src_frame->linesize(0),
src + y * row_bytes, copy_bytes);
}
}
@@ -853,24 +852,24 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image,
}
// Same format - direct copy
AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params);
if (pix_fmt == AV_PIX_FMT_NONE) {
int pix_fmt = GetDestinationAVPixelFormat(params);
if (pix_fmt == FB_PIX_FMT_NONE) {
return nullptr;
}
olive::AVFramePtr frame = olive::CreateAVFramePtr();
frame->width = width;
frame->height = height;
frame->format = pix_fmt;
frame->set_width(width);
frame->set_height(height);
frame->set_format(pix_fmt);
if (av_frame_get_buffer(frame.get(), 0) < 0) {
if (frame->get_buffer(0) < 0) {
return nullptr;
}
const int copy_bytes =
width * std::min(src_bytes_per_pixel, dst_bytes_per_pixel);
for (int y = 0; y < height; ++y) {
memcpy(frame->data[0] + y * frame->linesize[0], src + y * row_bytes,
memcpy(frame->data(0) + y * frame->linesize(0), src + y * row_bytes,
copy_bytes);
}
@@ -879,24 +878,20 @@ create_avframe_from_ofx_image_with_params(OFX::Host::ImageEffect::Image &image,
// 作用:将 VideoParams 映射为最终输出的 AVPixelFormat。
// Purpose: Map VideoParams to the final AVPixelFormat.
static AVPixelFormat
static int
GetDestinationAVPixelFormat(const olive::VideoParams &params)
{
AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(
int pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(
params.format(), params.channel_count());
if (pix_fmt == AV_PIX_FMT_NONE && params.channel_count() == 1) {
if (pix_fmt == FB_PIX_FMT_NONE && params.channel_count() == 1) {
if (params.format() == olive::core::PixelFormat::U8) {
pix_fmt = AV_PIX_FMT_GRAY8;
pix_fmt = FB_PIX_FMT_GRAY8;
} else if (params.format() == olive::core::PixelFormat::U16) {
pix_fmt = AV_PIX_FMT_GRAY16LE;
pix_fmt = FB_PIX_FMT_GRAY16LE;
} else if (params.format() == olive::core::PixelFormat::F16) {
#ifdef HAVE_AV_PIX_FMT_GRAYF16
pix_fmt = AV_PIX_FMT_GRAYF16;
#else
pix_fmt = AV_PIX_FMT_GRAY16LE;
#endif
pix_fmt = FB_PIX_FMT_GRAYF16LE;
} else if (params.format() == olive::core::PixelFormat::F32) {
pix_fmt = AV_PIX_FMT_GRAYF32;
pix_fmt = FB_PIX_FMT_GRAYF32LE;
}
}
return pix_fmt;
@@ -926,30 +921,25 @@ ReadbackTextureToFrame(olive::TexturePtr texture,
return nullptr;
}
AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params);
if (pix_fmt == AV_PIX_FMT_NONE) {
int pix_fmt = GetDestinationAVPixelFormat(params);
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;
}
if (texture->renderer()) {
const int linesize_pixels = olive::plugin::detail::BytesToPixels(
frame->linesize[0], params);
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;
}
@@ -961,39 +951,48 @@ 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;
}
if (texture->renderer()) {
const int linesize_pixels = olive::plugin::detail::BytesToPixels(
rgba_frame->linesize[0], rgba_params);
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;
}
@@ -1012,49 +1011,49 @@ int olive::plugin::detail::BytesToPixels(int byte_linesize,
}
// 作用:将 AVPixelFormat 映射为 Olive 的 PixelFormat 和通道数(仅常见 packed 格式)。
static void GetOliveFormatFromAV(AVPixelFormat fmt,
static void GetOliveFormatFromAV(int fmt,
olive::core::PixelFormat *out_fmt, int *out_ch)
{
switch (fmt) {
case AV_PIX_FMT_GRAY8:
case FB_PIX_FMT_GRAY8:
*out_fmt = olive::core::PixelFormat::U8;
*out_ch = 1;
return;
case AV_PIX_FMT_RGB24:
case FB_PIX_FMT_RGB24:
*out_fmt = olive::core::PixelFormat::U8;
*out_ch = 3;
return;
case AV_PIX_FMT_RGBA:
case FB_PIX_FMT_RGBA:
*out_fmt = olive::core::PixelFormat::U8;
*out_ch = 4;
return;
case AV_PIX_FMT_GRAY16LE:
case AV_PIX_FMT_GRAY16BE:
case FB_PIX_FMT_GRAY16LE:
case FB_PIX_FMT_GRAY16BE:
*out_fmt = olive::core::PixelFormat::U16;
*out_ch = 1;
return;
case AV_PIX_FMT_RGB48LE:
case AV_PIX_FMT_RGB48BE:
case FB_PIX_FMT_RGB48LE:
case FB_PIX_FMT_RGB48BE:
*out_fmt = olive::core::PixelFormat::U16;
*out_ch = 3;
return;
case AV_PIX_FMT_RGBA64LE:
case AV_PIX_FMT_RGBA64BE:
case FB_PIX_FMT_RGBA64LE:
case FB_PIX_FMT_RGBA64BE:
*out_fmt = olive::core::PixelFormat::U16;
*out_ch = 4;
return;
case AV_PIX_FMT_GRAYF32LE:
case AV_PIX_FMT_GRAYF32BE:
case FB_PIX_FMT_GRAYF32LE:
case FB_PIX_FMT_GRAYF32BE:
*out_fmt = olive::core::PixelFormat::F32;
*out_ch = 1;
return;
case AV_PIX_FMT_RGBF32LE:
case AV_PIX_FMT_RGBF32BE:
case FB_PIX_FMT_RGBF32LE:
case FB_PIX_FMT_RGBF32BE:
*out_fmt = olive::core::PixelFormat::F32;
*out_ch = 3;
return;
case AV_PIX_FMT_RGBAF32LE:
case AV_PIX_FMT_RGBAF32BE:
case FB_PIX_FMT_RGBAF32LE:
case FB_PIX_FMT_RGBAF32BE:
*out_fmt = olive::core::PixelFormat::F32;
*out_ch = 4;
return;
@@ -1077,45 +1076,55 @@ ConvertFrameIfNeeded(olive::AVFramePtr src,
return nullptr;
}
AVPixelFormat dst_fmt = GetDestinationAVPixelFormat(dst_params);
if (dst_fmt == AV_PIX_FMT_NONE) {
int dst_fmt = GetDestinationAVPixelFormat(dst_params);
if (dst_fmt == FB_PIX_FMT_NONE) {
return src;
}
// Same format & size, no conversion needed
if (src->format == dst_fmt && src->width == dst_params.width() &&
src->height == dst_params.height()) {
if (src->format() == dst_fmt && src->width() == dst_params.width() &&
src->height() == dst_params.height()) {
return src;
}
olive::AVFramePtr dst = olive::CreateAVFramePtr();
dst->format = dst_fmt;
dst->width = dst_params.width();
dst->height = dst_params.height();
if (av_frame_get_buffer(dst.get(), 0) < 0) {
dst->set_format(dst_fmt);
dst->set_width(dst_params.width());
dst->set_height(dst_params.height());
if (dst->get_buffer(0) < 0) {
qWarning().noquote()
<< "[WARN] av_frame_get_buffer failed for dst_fmt=" << dst_fmt;
return src;
}
// Try FFmpeg sws_scale first
SwsContext *sws_ctx = sws_getContext(
src->width, src->height, static_cast<AVPixelFormat>(src->format),
dst->width, dst->height, dst_fmt, SWS_POINT, nullptr, nullptr, nullptr);
if (sws_ctx) {
int ret = sws_scale(sws_ctx, src->data, src->linesize, 0, src->height,
dst->data, dst->linesize);
sws_freeContext(sws_ctx);
FBScaler *scaler = fb_scaler_create(
src->width(), src->height(), src->format(),
dst->width(), dst->height(), dst_fmt, FB_SCALER_POINT);
if (scaler) {
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] = src->data(i);
src_linesize[i] = src->linesize(i);
dst_data[i] = dst->data(i);
dst_linesize[i] = dst->linesize(i);
}
int ret = fb_scaler_scale_slices(scaler, src_data, src_linesize,
src->height(), dst_data, dst_linesize);
fb_scaler_free(&scaler);
if (ret > 0) {
return dst;
}
}
// sws_scale failed (e.g. RGBAF32 not supported). Use GPU if renderer available.
if (renderer && src->data[0]) {
if (renderer && src->data(0)) {
olive::core::PixelFormat src_fmt;
int src_ch;
GetOliveFormatFromAV(static_cast<AVPixelFormat>(src->format), &src_fmt,
GetOliveFormatFromAV(src->format(), &src_fmt,
&src_ch);
if (src_fmt != olive::core::PixelFormat::INVALID && src_ch > 0) {
// Ensure renderer's OpenGL context is current before GPU operations.
@@ -1125,13 +1134,13 @@ ConvertFrameIfNeeded(olive::AVFramePtr src,
gl_renderer->EnsureContextCurrent(__FUNCTION__);
}
olive::VideoParams src_vp(src->width, src->height, src_fmt, src_ch);
olive::VideoParams src_vp(src->width(), src->height(), src_fmt, src_ch);
int src_bpp = olive::VideoParams::GetBytesPerPixel(src_fmt, src_ch);
int src_linesize_pixels =
(src_bpp > 0) ? src->linesize[0] / src_bpp : src->width;
(src_bpp > 0) ? src->linesize(0) / src_bpp : src->width();
olive::TexturePtr src_tex = renderer->CreateTexture(
src_vp, src->data[0], src_linesize_pixels);
src_vp, src->data(0), src_linesize_pixels);
if (src_tex) {
olive::TexturePtr dst_tex = renderer->CreateTexture(dst_params);
if (dst_tex) {
@@ -1145,8 +1154,8 @@ ConvertFrameIfNeeded(olive::AVFramePtr src,
// Download result back to AVFrame
int dst_bpp = dst_params.GetBytesPerPixel();
int dst_linesize_pixels =
(dst_bpp > 0) ? dst->linesize[0] / dst_bpp : dst->width;
dst_tex->Download(dst->data[0], dst_linesize_pixels);
(dst_bpp > 0) ? dst->linesize(0) / dst_bpp : dst->width();
dst_tex->Download(dst->data(0), dst_linesize_pixels);
return dst;
}
}
@@ -1155,7 +1164,7 @@ ConvertFrameIfNeeded(olive::AVFramePtr src,
qWarning().noquote()
<< "[WARN] ConvertFrameIfNeeded failed (sws_scale + GPU both unavailable). "
<< "Returning unconverted source. src_fmt=" << src->format
<< "Returning unconverted source. src_fmt=" << src->format()
<< " dst_fmt=" << dst_fmt;
return src;
}
@@ -1208,39 +1217,39 @@ ConvertTextureForParams(olive::TexturePtr src,
// CPU fallback: readback, sws_scale, re-upload
olive::AVFramePtr frame = src->frame();
if (!frame || !frame->data[0]) {
if (!frame || !frame->data(0)) {
frame = ReadbackTextureToFrame(src, src_params);
}
if (!frame || !frame->data[0]) {
if (!frame || !frame->data(0)) {
return nullptr;
}
olive::AVFramePtr converted =
ConvertFrameIfNeeded(frame, dst_params, nullptr);
if (!converted || !converted->data[0]) {
if (!converted || !converted->data(0)) {
return nullptr;
}
if (converted->linesize[0] <= 0) {
if (converted->linesize(0) <= 0) {
return nullptr;
}
olive::TexturePtr dst;
if (auto *renderer = src->renderer()) {
int linesize_pixels =
LinesizeToPixels(dst_params, converted->linesize[0]);
LinesizeToPixels(dst_params, converted->linesize(0));
if (linesize_pixels <= 0) {
linesize_pixels = dst_params.effective_width();
}
dst = renderer->CreateTexture(dst_params, converted->data[0],
dst = renderer->CreateTexture(dst_params, converted->data(0),
linesize_pixels);
} else {
dst = std::make_shared<olive::Texture>(dst_params);
int linesize_pixels =
LinesizeToPixels(dst_params, converted->linesize[0]);
LinesizeToPixels(dst_params, converted->linesize(0));
if (linesize_pixels <= 0) {
linesize_pixels = dst_params.effective_width();
}
dst->Upload(converted->data[0], linesize_pixels);
dst->Upload(converted->data(0), linesize_pixels);
}
if (dst) {
dst->handleFrame(converted);
@@ -1533,7 +1542,7 @@ void olive::plugin::PluginRenderer::RenderPlugin(
return true;
}
AVFramePtr frame = tex->frame();
return frame && frame->data[0];
return frame && frame->data(0);
};
std::map<std::string, TexturePtr> input_textures;
std::map<std::string, OliveClipInstance *> input_clips;
@@ -1845,19 +1854,19 @@ void olive::plugin::PluginRenderer::RenderPlugin(
}
AVFramePtr converted =
ConvertFrameIfNeeded(frame_ptr, destination_params, renderer_);
const AVPixelFormat expected_fmt =
const int expected_fmt =
GetDestinationAVPixelFormat(destination_params);
destination->handleFrame(converted);
if (destination->renderer() && converted && converted->data[0] &&
(expected_fmt == AV_PIX_FMT_NONE ||
converted->format == expected_fmt)) {
if (destination->renderer() && converted && converted->data(0) &&
(expected_fmt == FB_PIX_FMT_NONE ||
converted->format() == expected_fmt)) {
int linesize_pixels =
LinesizeToPixels(destination_params, converted->linesize[0]);
LinesizeToPixels(destination_params, converted->linesize(0));
if (linesize_pixels <= 0) {
linesize_pixels = destination_params.effective_width();
}
destination->Upload(converted->data[0], linesize_pixels);
} else if (destination->renderer() && converted && converted->data[0]) {
destination->Upload(converted->data(0), linesize_pixels);
} else if (destination->renderer() && converted && converted->data(0)) {
qWarning().noquote()
<< "OFX output pixel format mismatch for plugin="
<< PluginIdForInstance(instance);
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -758,7 +758,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
return true;
}
AVFramePtr frame = tex->frame();
return frame && frame->data[0];
return frame && frame->data(0);
};
TexturePtr src = nullptr;
+2 -4
View File
@@ -21,9 +21,7 @@
#include "videoparams.h"
extern "C" {
#include <libavutil/avutil.h>
}
#include <cstdint>
#include <QCoreApplication>
#include <QtMath>
@@ -365,7 +363,7 @@ int VideoParams::GetScaledDimension(int dim, int divider)
int64_t VideoParams::get_time_in_timebase_units(const rational &time) const
{
if (time_base_.isNull()) {
return AV_NOPTS_VALUE;
return INT64_MIN; // AV_NOPTS_VALUE
}
return Timecode::time_to_timestamp(time, time_base_) + start_time_;
+5 -5
View File
@@ -31,15 +31,15 @@ QString HumanStrings::SampleRateToString(const int &sample_rate)
QString HumanStrings::ChannelLayoutToString(const uint64_t &layout)
{
switch (layout) {
case AV_CH_LAYOUT_MONO:
case kChannelLayoutMono:
return QCoreApplication::translate("AudioParams", "Mono");
case AV_CH_LAYOUT_STEREO:
case kChannelLayoutStereo:
return QCoreApplication::translate("AudioParams", "Stereo");
case AV_CH_LAYOUT_2_1:
case kChannelLayout2_1:
return QCoreApplication::translate("AudioParams", "2.1");
case AV_CH_LAYOUT_5POINT1:
case kChannelLayout5Point1:
return QCoreApplication::translate("AudioParams", "5.1");
case AV_CH_LAYOUT_7POINT1:
case kChannelLayout7Point1:
return QCoreApplication::translate("AudioParams", "7.1");
default:
return QCoreApplication::translate("AudioParams", "Unknown (0x%1)")
@@ -24,9 +24,6 @@
#include <olive/core/core.h>
#include <QComboBox>
extern "C" {
#include <libavformat/avformat.h>
}
#include "ui/humanstrings.h"
namespace olive
@@ -46,12 +43,9 @@ public:
QVariant::fromValue(ch_layout));
}
}
[[nodiscard]] AVChannelLayout GetChannelLayout() const
[[nodiscard]] uint64_t GetChannelLayout() const
{
AVChannelLayout audio_channel_layout_;
av_channel_layout_from_mask(&audio_channel_layout_,
this->currentData().toULongLong());
return audio_channel_layout_;
return this->currentData().toULongLong();
}
void SetChannelLayout(uint64_t ch)
{
@@ -62,15 +56,6 @@ public:
}
}
}
void SetChannelLayout(const AVChannelLayout &ch)
{
for (int i = 0; i < this->count(); i++) {
if (this->itemData(i).toULongLong() == ch.u.mask) {
this->setCurrentIndex(i);
break;
}
}
}
public slots:
private:
};
+2 -2
View File
@@ -603,10 +603,10 @@ void ViewerWidget::UpdateAudioProcessor()
qDebug() << "ViewerWidget::UpdateAudioProcessor: from sample_rate="
<< ap.sample_rate() << "channels=" << ap.channel_count()
<< "layout_mask=0x" << Qt::hex << ap.channel_layout().u.mask
<< "layout_mask=0x" << Qt::hex << ap.channel_layout()
<< "to sample_rate=" << packed.sample_rate()
<< "channels=" << packed.channel_count()
<< "layout_mask=0x" << packed.channel_layout().u.mask
<< "layout_mask=0x" << packed.channel_layout()
<< Qt::dec;
audio_processor_.Open(
+12 -6
View File
@@ -93,10 +93,16 @@ set_target_properties(ffmpeg_bridge PROPERTIES
if (WIN32)
set_target_properties(ffmpeg_bridge PROPERTIES PREFIX "")
# Windows has no RPATH: the DLL must sit next to the executables
install(TARGETS ffmpeg_bridge
RUNTIME DESTINATION bin
LIBRARY DESTINATION bin
ARCHIVE DESTINATION ffmpeg_bridge/lib
)
else()
install(TARGETS ffmpeg_bridge
RUNTIME DESTINATION ffmpeg_bridge/bin
LIBRARY DESTINATION ffmpeg_bridge/bin
ARCHIVE DESTINATION ffmpeg_bridge/lib
)
endif ()
install(TARGETS ffmpeg_bridge
RUNTIME DESTINATION ffmpeg_bridge/bin
LIBRARY DESTINATION ffmpeg_bridge/bin
ARCHIVE DESTINATION ffmpeg_bridge/lib
)
@@ -87,6 +87,7 @@ typedef enum FBPixelFormat {
FB_PIX_FMT_YUVJ444P = 14,
FB_PIX_FMT_RGBA = 26,
FB_PIX_FMT_GRAY16LE = 30,
FB_PIX_FMT_YUV440P = 31,
FB_PIX_FMT_YUVJ440P = 32,
FB_PIX_FMT_RGB48LE = 35,
FB_PIX_FMT_YUV420P10LE = 62,
@@ -254,6 +255,8 @@ FB_API void fb_frame_free(FBFrame **frame);
FB_API void fb_frame_unref(FBFrame *frame);
/** Allocate the frame's buffer(s) from its width/height/format fields. */
FB_API int fb_frame_get_buffer(FBFrame *frame, int align);
/** Ensure the frame's data is writable (mirrors av_frame_make_writable). */
FB_API int fb_frame_make_writable(FBFrame *frame);
FB_API int fb_frame_copy_props(FBFrame *dst, const FBFrame *src);
/** Transfer data between a hardware frame and a software frame. */
FB_API int fb_frame_hw_transfer_data(FBFrame *dst, const FBFrame *src);
@@ -351,6 +354,7 @@ FB_API void fb_decoder_seek(FBDecoder *decoder, int64_t timestamp);
FB_API int fb_decoder_get_stream_info(const FBDecoder *decoder,
FBStreamInfo *out);
FB_API int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder);
FB_API int64_t fb_decoder_get_format_duration(const FBDecoder *decoder);
FB_API int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder,
FBFrame *frame, int *num,
@@ -467,6 +471,9 @@ FB_API int fb_resampler_get_out_samples(FBResampler *resampler,
FB_API int fb_resampler_convert(FBResampler *resampler, uint8_t **out,
int out_count, const uint8_t **in,
int in_count);
/** Same as fb_resampler_convert but takes the input directly from a frame. */
FB_API int fb_resampler_convert_frame(FBResampler *resampler, uint8_t **out,
int out_count, const FBFrame *in_frame);
/* ------------------------------------------------------------------------- */
/* Audio filter graph (abuffer/aformat/atempo/abuffersink wrapper) */
+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)