Fix Linux audio backend preference and worker footage render crash
- On Linux, automatically prefer PipeWire/JACK/PulseAudio over ALSA even when a saved ALSA device name exists in config. - Restore Footage length after deserialization so worker snapshots have valid stream lengths and video playback can advance. - Guard ResolveDecoderFromInput and ProcessAudioFootage against a null decoder cache to prevent worker crashes on direct Footage -> ViewerOutput connections. - Keep Linux signal backtrace handler in the render worker. - Add Oak project SVG logos.
This commit is contained in:
@@ -281,12 +281,26 @@ void AudioManager::StopRecording()
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
static bool IsPreferredLinuxAudioHostApi(const PaHostApiInfo *info)
|
||||
{
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString name = QString::fromLatin1(info->name);
|
||||
return name.contains(QStringLiteral("PipeWire"), Qt::CaseInsensitive) ||
|
||||
name.contains(QStringLiteral("JACK"), Qt::CaseInsensitive) ||
|
||||
name.contains(QStringLiteral("PulseAudio"), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
static PaDeviceIndex GetPreferredLinuxAudioDevice(bool is_output_device)
|
||||
{
|
||||
// Prefer PipeWire, then PulseAudio. Both provide mixing; plain ALSA/JACK
|
||||
// defaults often fail to share the device on modern Linux desktops.
|
||||
// Prefer sound servers that provide mixing and desktop integration
|
||||
// (PipeWire, JACK, PulseAudio) over plain ALSA defaults, which often
|
||||
// fail to share the device on modern Linux desktops.
|
||||
const QStringList preferred_host_apis = {
|
||||
QStringLiteral("PipeWire"),
|
||||
QStringLiteral("JACK"),
|
||||
QStringLiteral("PulseAudio"),
|
||||
};
|
||||
|
||||
@@ -325,21 +339,60 @@ PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s,
|
||||
bool is_output_device)
|
||||
{
|
||||
PaDeviceIndex exact_match = paNoDevice;
|
||||
|
||||
if (!s.isEmpty()) {
|
||||
for (PaDeviceIndex i = 0, end = Pa_GetDeviceCount(); i < end; i++) {
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((is_output_device && device->maxOutputChannels) ||
|
||||
(!is_output_device && device->maxInputChannels)) &&
|
||||
!s.compare(device->name)) {
|
||||
return i;
|
||||
exact_match = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Even if the user/config picked a device by name, upgrade to a preferred
|
||||
// host API (PipeWire/JACK/PulseAudio) when one is available. This avoids
|
||||
// getting stuck on an ALSA device that cannot share the hardware.
|
||||
if (exact_match != paNoDevice) {
|
||||
const PaDeviceInfo *matched_info = Pa_GetDeviceInfo(exact_match);
|
||||
if (matched_info) {
|
||||
const PaHostApiInfo *host_api =
|
||||
Pa_GetHostApiInfo(matched_info->hostApi);
|
||||
if (IsPreferredLinuxAudioHostApi(host_api)) {
|
||||
// Keep an explicit choice that already uses a preferred API.
|
||||
return exact_match;
|
||||
}
|
||||
|
||||
// Upgrade a non-preferred (e.g. ALSA) match to a preferred backend
|
||||
// when one is available.
|
||||
PaDeviceIndex preferred =
|
||||
GetPreferredLinuxAudioDevice(is_output_device);
|
||||
if (preferred != paNoDevice) {
|
||||
qInfo() << "Overriding saved audio device" << s
|
||||
<< "with preferred Linux audio device"
|
||||
<< Pa_GetDeviceInfo(preferred)->name;
|
||||
return preferred;
|
||||
}
|
||||
|
||||
// No preferred backend available; keep the saved device.
|
||||
return exact_match;
|
||||
}
|
||||
}
|
||||
|
||||
return GetPreferredLinuxAudioDevice(is_output_device);
|
||||
#else
|
||||
if (exact_match != paNoDevice) {
|
||||
return exact_match;
|
||||
}
|
||||
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() :
|
||||
Pa_GetDefaultInputDevice();
|
||||
#endif
|
||||
|
||||
@@ -584,6 +584,11 @@ bool Footage::LoadCustom(QXmlStreamReader *reader, SerializedData *data)
|
||||
}
|
||||
}
|
||||
|
||||
// The cached lengths are not serialized. Recompute them from the stream
|
||||
// parameters that were just loaded so that worker processes and any code
|
||||
// that reads GetLength() before InvalidateCache() runs sees valid values.
|
||||
VerifyLength();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -317,6 +317,12 @@ RenderProcessor::ResolveDecoderFromInput(const QString &decoder_id,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!decoder_cache_) {
|
||||
qWarning() << "Cannot resolve decoder for" << stream.filename()
|
||||
<< "without a decoder cache";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMutexLocker locker(decoder_cache_->mutex());
|
||||
|
||||
DecoderPair decoder = decoder_cache_->value(stream);
|
||||
@@ -599,6 +605,12 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination,
|
||||
const FootageJob *stream,
|
||||
const TimeRange &input_time)
|
||||
{
|
||||
// The worker process has no decoder cache and does not decode audio. Bail
|
||||
// out gracefully rather than letting ResolveDecoderFromInput crash.
|
||||
if (!decoder_cache_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(
|
||||
stream->decoder(),
|
||||
Decoder::CodecStream(stream->filename(),
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <csignal>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@@ -32,6 +33,11 @@
|
||||
#include <QOpenGLContext>
|
||||
#include <QSurfaceFormat>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
@@ -59,6 +65,18 @@ void HideWorkerDockIcon();
|
||||
namespace
|
||||
{
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
void PrintBacktrace(int sig)
|
||||
{
|
||||
void *array[50];
|
||||
size_t size = backtrace(array, 50);
|
||||
fprintf(stderr, "worker: caught signal %d, backtrace:\n", sig);
|
||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||
fflush(stderr);
|
||||
_exit(128 + sig);
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr int kProtocolVersion = 1;
|
||||
constexpr int kDefaultWidth = 1920;
|
||||
constexpr int kDefaultHeight = 1080;
|
||||
@@ -606,6 +624,12 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
std::signal(SIGSEGV, PrintBacktrace);
|
||||
std::signal(SIGABRT, PrintBacktrace);
|
||||
std::signal(SIGFPE, PrintBacktrace);
|
||||
#endif
|
||||
|
||||
QFile in;
|
||||
QFile out;
|
||||
if (!in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered) ||
|
||||
|
||||
Reference in New Issue
Block a user