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:
2026-07-14 22:33:30 +08:00
parent 5c8ce17c7e
commit 3be4294e15
6 changed files with 101 additions and 3 deletions
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66.455 51.542 122.65 122.65" width="122.65px" height="122.65px"><g id="object-5" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 2.1316282072803006e-14)"><ellipse style="stroke: rgb(0, 0, 0); stroke-opacity: 0; stroke-width: 1; fill: rgb(66, 85, 54);" cx="127.78" cy="112.867" rx="61.325" ry="61.325" id="object-0"/><path style="stroke: rgb(0, 0, 0); fill: rgb(255, 255, 255); stroke-opacity: 0;" d="M 108.938 74.01 C 108.938 74.01 113.768 68.927 118.024 82.364 C 121.327 92.79 115.453 105.155 115.682 113.196 C 115.91 121.237 118.274 123.233 120.894 118.269 C 123.514 113.3 124.882 118.57 124.173 121.487 C 122.633 129.57 124.625 140.553 129.134 142.503 C 133.29 144.299 131.944 133.101 135.159 132.349 C 138.375 131.597 140.886 147.886 148.395 150.681 C 155.904 153.473 151.817 140.929 156.62 142.54 C 161.424 144.151 164.989 149.094 164.414 142.723 C 164.141 139.7 174.057 143.197 174.711 139.18 C 175.368 135.164 170.235 133.443 167.764 132.764 C 165.294 132.08 159.277 131.997 162.836 128.218 C 166.396 124.438 170.625 127.358 169.484 124.518 C 168.345 121.682 162.321 120.361 155.396 121.455 C 148.469 122.55 147.929 116.789 155.752 114.889 C 163.58 112.991 163.743 108.958 159.69 106.402 C 151.469 101.212 144.084 104.757 144.084 104.757 C 137.838 107.27 133.22 104.676 133.22 104.676 C 130.051 101.066 137.477 99.577 137.477 99.577 C 144.088 96.205 126.636 93.773 126.103 95.792 C 126.698 98.659 133.676 123.858 162.109 138.304 C 137.825 128.994 124.241 105.31 123.109 94.564 C 121.974 83.819 122.782 83.712 120.621 77.554 C 118.463 71.401 112.057 65.149 108.576 67.735 C 105.093 70.32 107.833 73.159 108.938 74.01 Z" id="object-1"/><g transform="matrix(0.602933, 0.180211, -0.197794, 0.661764, -17.312515, -134.532112)" style="" id="object-2"><path style="fill: rgb(255, 255, 255); stroke-linejoin: round; stroke-linecap: round; stroke: rgb(0, 0, 0); stroke-opacity: 0;" d="M 270.267 278.934 C 266.312 278.408 266.571 273.461 267.985 269.688 C 269.398 265.915 272.668 262.24 278.489 261.413 C 284.311 260.586 293.348 260.551 298.552 261.732 C 303.756 262.913 306.571 267.285 307.36 269.857 C 308.765 274.433 308.05 278.22 304.634 278.474 C 307.168 274.498 267.373 273.799 270.267 278.934 Z M 292.463 263.467 C 292.138 263.811 291.897 264.226 291.758 264.682 C 291.088 266.896 293.041 268.971 295.273 268.417 C 297.506 267.861 298.345 265.095 296.781 263.434 L 292.463 263.467 Z M 278.933 263.135 C 278.606 263.479 278.365 263.894 278.227 264.35 C 277.557 266.564 279.509 268.639 281.743 268.085 C 283.976 267.529 284.814 264.763 283.25 263.102 L 278.933 263.135 Z M 273.394 269.573 C 273.068 269.917 272.827 270.333 272.689 270.788 C 272.017 273.002 273.971 275.077 276.204 274.523 C 278.437 273.968 279.275 271.201 277.712 269.54 L 273.394 269.573 Z M 285.356 268.443 C 285.03 268.787 284.788 269.202 284.651 269.658 C 283.979 271.872 285.933 273.947 288.165 273.393 C 290.399 272.837 291.236 270.071 289.674 268.41 L 285.356 268.443 Z M 297.696 269.416 C 297.37 269.76 297.129 270.175 296.991 270.631 C 296.32 272.845 298.273 274.92 300.505 274.366 C 302.739 273.81 303.577 271.044 302.014 269.383 L 297.696 269.416 Z"/><path style="stroke-linecap: round; fill: rgb(255, 255, 255); stroke: rgba(0, 0, 0, 0);" d="M 284.958 259.909 C 284.958 259.909 288.733 255.134 288.962 243.138 C 288.837 240.077 295.322 241.33 293.475 243.938 C 293.475 243.938 288.957 254.426 291.014 259.967 C 289.324 259.86 286.602 259.756 284.958 259.909 Z"/><path style="stroke-linecap: round; stroke-linejoin: round; fill: rgb(255, 255, 255); stroke: rgba(0, 0, 0, 0);" d="M 298.487 278.631 C 300.359 278.819 301.941 278.956 303.292 279.322 C 306.638 289.03 302.05 300.652 295.979 303.36 C 289.908 306.069 288.414 308.556 288.414 308.556 C 286.802 305.679 279.395 303.087 279.395 303.087 C 268.763 296.601 271.685 279.707 271.685 279.707 C 271.645 278.997 277.254 276.179 293.159 277.43 C 298.858 287.987 293.518 299.915 293.518 299.915 C 304.123 290.289 298.534 278.729 298.487 278.631 Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

+2
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

+56 -3
View File
@@ -281,12 +281,26 @@ void AudioManager::StopRecording()
} }
#ifdef Q_OS_LINUX #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) static PaDeviceIndex GetPreferredLinuxAudioDevice(bool is_output_device)
{ {
// Prefer PipeWire, then PulseAudio. Both provide mixing; plain ALSA/JACK // Prefer sound servers that provide mixing and desktop integration
// defaults often fail to share the device on modern Linux desktops. // (PipeWire, JACK, PulseAudio) over plain ALSA defaults, which often
// fail to share the device on modern Linux desktops.
const QStringList preferred_host_apis = { const QStringList preferred_host_apis = {
QStringLiteral("PipeWire"), QStringLiteral("PipeWire"),
QStringLiteral("JACK"),
QStringLiteral("PulseAudio"), QStringLiteral("PulseAudio"),
}; };
@@ -325,21 +339,60 @@ PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, PaDeviceIndex AudioManager::FindDeviceByName(const QString &s,
bool is_output_device) bool is_output_device)
{ {
PaDeviceIndex exact_match = paNoDevice;
if (!s.isEmpty()) { if (!s.isEmpty()) {
for (PaDeviceIndex i = 0, end = Pa_GetDeviceCount(); i < end; i++) { for (PaDeviceIndex i = 0, end = Pa_GetDeviceCount(); i < end; i++) {
const PaDeviceInfo *device = Pa_GetDeviceInfo(i); const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
if (!device) {
continue;
}
if (((is_output_device && device->maxOutputChannels) || if (((is_output_device && device->maxOutputChannels) ||
(!is_output_device && device->maxInputChannels)) && (!is_output_device && device->maxInputChannels)) &&
!s.compare(device->name)) { !s.compare(device->name)) {
return i; exact_match = i;
break;
} }
} }
} }
#ifdef Q_OS_LINUX #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); return GetPreferredLinuxAudioDevice(is_output_device);
#else #else
if (exact_match != paNoDevice) {
return exact_match;
}
return is_output_device ? Pa_GetDefaultOutputDevice() : return is_output_device ? Pa_GetDefaultOutputDevice() :
Pa_GetDefaultInputDevice(); Pa_GetDefaultInputDevice();
#endif #endif
+5
View File
@@ -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; return true;
} }
+12
View File
@@ -317,6 +317,12 @@ RenderProcessor::ResolveDecoderFromInput(const QString &decoder_id,
return nullptr; return nullptr;
} }
if (!decoder_cache_) {
qWarning() << "Cannot resolve decoder for" << stream.filename()
<< "without a decoder cache";
return nullptr;
}
QMutexLocker locker(decoder_cache_->mutex()); QMutexLocker locker(decoder_cache_->mutex());
DecoderPair decoder = decoder_cache_->value(stream); DecoderPair decoder = decoder_cache_->value(stream);
@@ -599,6 +605,12 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination,
const FootageJob *stream, const FootageJob *stream,
const TimeRange &input_time) 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( DecoderPtr decoder = ResolveDecoderFromInput(
stream->decoder(), stream->decoder(),
Decoder::CodecStream(stream->filename(), Decoder::CodecStream(stream->filename(),
+24
View File
@@ -20,6 +20,7 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <csignal>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -32,6 +33,11 @@
#include <QOpenGLContext> #include <QOpenGLContext>
#include <QSurfaceFormat> #include <QSurfaceFormat>
#ifdef Q_OS_LINUX
#include <execinfo.h>
#include <unistd.h>
#endif
#include "common/qtutils.h" #include "common/qtutils.h"
#include "config/config.h" #include "config/config.h"
#include "core.h" #include "core.h"
@@ -59,6 +65,18 @@ void HideWorkerDockIcon();
namespace 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 kProtocolVersion = 1;
constexpr int kDefaultWidth = 1920; constexpr int kDefaultWidth = 1920;
constexpr int kDefaultHeight = 1080; 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 in;
QFile out; QFile out;
if (!in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered) || if (!in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered) ||