diff --git a/Oak_Icon.svg b/Oak_Icon.svg
new file mode 100644
index 000000000..59c9e23ab
--- /dev/null
+++ b/Oak_Icon.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Oak_Logo.svg b/Oak_Logo.svg
new file mode 100644
index 000000000..b68c8c240
--- /dev/null
+++ b/Oak_Logo.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp
index dfc78455f..5dc929e53 100644
--- a/app/audio/audiomanager.cpp
+++ b/app/audio/audiomanager.cpp
@@ -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
diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp
index 3e1ed542c..12ec4d3f9 100644
--- a/app/node/project/footage/footage.cpp
+++ b/app/node/project/footage/footage.cpp
@@ -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;
}
diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp
index 1c096b1b4..246f92cca 100644
--- a/app/render/renderprocessor.cpp
+++ b/app/render/renderprocessor.cpp
@@ -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(),
diff --git a/app/render/worker/workermain.cpp b/app/render/worker/workermain.cpp
index 3cc9d78d6..c01fa5e9c 100644
--- a/app/render/worker/workermain.cpp
+++ b/app/render/worker/workermain.cpp
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
#include
@@ -32,6 +33,11 @@
#include
#include
+#ifdef Q_OS_LINUX
+#include
+#include
+#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) ||