Files
Mike-Solar 28c4426236 build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).

- oak-render-worker now links liboakengine instead of the whole
  libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
  objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
  macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
  ../app but backends now live in engine/; a stale pre-split liboakgl
  in the build tree got dlopened instead, re-initialized and later
  destroyed the interposed engine statics (full-suite segfault at
  DialogSequenceParameterTab, found via gdb watchpoint)
2026-07-20 03:23:28 +08:00

209 lines
5.0 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "audioprocessor.h"
#include <cstring>
#include <QDebug>
#include "common/ffmpegutils.h"
namespace olive
{
/**
* @brief Ensure an AudioParams has a usable channel layout mask.
*
* 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 fix_channel_layout(const AudioParams &params)
{
AudioParams result = params;
if (params.channel_layout() == 0) {
int channels = params.channel_count();
if (channels <= 0) {
channels = 2;
}
qWarning() << "AudioProcessor: fixing unspecified channel layout"
<< "(channels=" << params.channel_count() << ") -> default"
<< channels << "channel layout";
result.set_channel_layout(fb_channel_layout_default(channels));
}
return result;
}
AudioProcessor::AudioProcessor()
{
graph_ = nullptr;
out_frame_ = nullptr;
}
AudioProcessor::~AudioProcessor()
{
close();
}
bool AudioProcessor::open(const AudioParams &from, const AudioParams &to,
double tempo)
{
if (graph_) {
qWarning() << "Tried to open a processor that was already open";
return false;
}
AudioParams from_fixed = fix_channel_layout(from);
AudioParams to_fixed = fix_channel_layout(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() << "to sample_rate="
<< to_fixed.sample_rate() << "channels=" << to_fixed.channel_count()
<< "layout_mask=0x" << to_fixed.channel_layout() << Qt::dec;
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::get_f_fmpeg_sample_format(from_fixed.format());
config.in_channels = from_fixed.channel_count();
config.out_sample_rate = to_fixed.sample_rate();
config.out_channel_layout_mask = to_fixed.channel_layout();
config.out_sample_format =
FFmpegUtils::get_f_fmpeg_sample_format(to_fixed.format());
config.out_channels = to_fixed.channel_count();
config.out_is_planar = to_fixed.format().is_planar() ? 1 : 0;
config.tempo = tempo;
graph_ = fb_audio_graph_create(&config);
if (!graph_) {
qCritical() << "Failed to create audio filter graph";
return false;
}
out_frame_ = fb_frame_alloc();
if (!out_frame_) {
qCritical() << "Failed to allocate output frame";
close();
return false;
}
from_ = from_fixed;
to_ = to_fixed;
return true;
}
void AudioProcessor::close()
{
if (graph_) {
fb_audio_graph_free(&graph_);
}
if (out_frame_) {
fb_frame_free(&out_frame_);
}
}
int AudioProcessor::convert(float **in, int nb_in_samples,
AudioProcessor::Buffer *output)
{
if (!is_open()) {
qCritical() << "Tried to convert on closed processor";
return -1;
}
int r = 0;
if (in && nb_in_samples) {
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;
}
}
if (output) {
int nb_channels = to_.channel_count();
if (to_.format().is_packed()) {
nb_channels = 1;
}
AudioProcessor::Buffer &result = *output;
result.resize(nb_channels);
int byte_offset = 0;
while (true) {
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
qCritical() << "Failed to pull from buffersink:" << r;
}
break;
}
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,
fb_frame_get_data(out_frame_, i), nb_bytes);
}
byte_offset += nb_bytes;
}
}
return r;
}
void AudioProcessor::flush()
{
int r = fb_audio_graph_push(graph_, nullptr, 0);
if (r < 0) {
qCritical() << "Failed to flush:" << r;
}
}
}