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:
+4
-61
@@ -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
@@ -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})
|
||||
|
||||
+47
-217
@@ -21,10 +21,7 @@
|
||||
|
||||
#include "audioprocessor.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
}
|
||||
#include <cstring>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@@ -34,41 +31,28 @@ 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 ¶ms)
|
||||
{
|
||||
AudioParams result = params;
|
||||
const AVChannelLayout &layout = params.channel_layout();
|
||||
|
||||
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 (needs_fix) {
|
||||
if (params.channel_layout() == 0) {
|
||||
int channels = params.channel_count();
|
||||
if (channels <= 0) {
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
qWarning() << "AudioProcessor: fixing invalid/unspecified channel layout"
|
||||
qWarning() << "AudioProcessor: fixing unspecified channel layout"
|
||||
<< "(channels=" << params.channel_count() << ") -> default"
|
||||
<< channels << "channel layout";
|
||||
|
||||
AVChannelLayout fallback;
|
||||
av_channel_layout_default(&fallback, channels);
|
||||
result.set_channel_layout(fallback);
|
||||
av_channel_layout_uninit(&fallback);
|
||||
result.set_channel_layout(fb_channel_layout_default(channels));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -76,8 +60,7 @@ static AudioParams FixChannelLayout(const AudioParams ¶ms)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
|
||||
extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
@@ -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()));
|
||||
|
||||
+453
-883
File diff suppressed because it is too large
Load Diff
@@ -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_)
|
||||
* @brief Handle a bridge error code
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+198
-855
File diff suppressed because it is too large
Load Diff
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
target_sources(libolive-editor PRIVATE
|
||||
cancelableobject.h
|
||||
channellayout.h
|
||||
commandlineparser.cpp
|
||||
commandlineparser.h
|
||||
crashpadinterface.cpp
|
||||
|
||||
+97
-6
@@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+60
-93
@@ -24,135 +24,110 @@
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AVPixelFormat
|
||||
FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt,
|
||||
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 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)
|
||||
|
||||
+24
-26
@@ -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,
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
@@ -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 ¶ms)
|
||||
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);
|
||||
|
||||
@@ -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 ¶ms);
|
||||
|
||||
// 作用:读取 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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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:
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -93,10 +93,16 @@ set_target_properties(ffmpeg_bridge PROPERTIES
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(ffmpeg_bridge PROPERTIES PREFIX "")
|
||||
endif ()
|
||||
|
||||
# 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 ()
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,11 +590,17 @@ int fb_encoder_write_audio(FBEncoder *e, const uint8_t *const *channel_data,
|
||||
e->SetError("Failed to allocate sample array", r);
|
||||
return r;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user