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) ||