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)
209 lines
5.0 KiB
C++
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 ¶ms)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|