Fix invalid channel layout (0x0) in audio processor and add playback diagnostics

- Add FixChannelLayout() helper to AudioProcessor::Open() to fall back to a
  default native channel layout when the input/output layout is unspecified,
  custom, or has a zero mask. This prevents FFmpeg's abuffer/aformat filters
  from rejecting 'channel_layouts=0x0' on Linux.
- Log audio processor open parameters, viewer audio queue state, and
  AudioManager::PushToOutput device/stream status to help diagnose silent
  playback on Linux.
This commit is contained in:
2026-07-13 17:46:15 +08:00
parent c6310d104d
commit 7cde0b0f1a
3 changed files with 112 additions and 17 deletions
+15 -1
View File
@@ -93,6 +93,11 @@ int InputCallback(const void *input, void *output, unsigned long frameCount,
bool AudioManager::PushToOutput(const AudioParams &params,
const QByteArray &samples, QString *error)
{
qDebug() << "AudioManager::PushToOutput: device=" << output_device_
<< "sample_rate=" << params.sample_rate()
<< "channels=" << params.channel_count()
<< "bytes=" << samples.size();
if (output_device_ == paNoDevice) {
if (error)
*error = tr("No output device is set");
@@ -113,10 +118,14 @@ bool AudioManager::PushToOutput(const AudioParams &params,
if (r != paNoError) {
// Unhandled error
//qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r);
qCritical() << "AudioManager::PushToOutput: Pa_OpenStream failed:"
<< Pa_GetErrorText(r);
if (error)
*error = Pa_GetErrorText(r);
return false;
}
qDebug() << "AudioManager::PushToOutput: opened stream with"
<< params.channel_count() << "channels";
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
}
@@ -124,7 +133,9 @@ bool AudioManager::PushToOutput(const AudioParams &params,
output_buffer_->write(samples);
if (!Pa_IsStreamActive(output_stream_)) {
Pa_StartStream(output_stream_);
PaError r = Pa_StartStream(output_stream_);
qDebug() << "AudioManager::PushToOutput: Pa_StartStream returned"
<< r << Pa_GetErrorText(r);
}
return true;
@@ -365,6 +376,9 @@ AudioManager::AudioManager()
PaDeviceIndex output_device = FindConfigDeviceByName(true);
PaDeviceIndex input_device = FindConfigDeviceByName(false);
qDebug() << "AudioManager: selected output device index=" << output_device
<< "input device index=" << input_device;
SetOutputDevice(output_device);
SetInputDevice(input_device);
+66 -15
View File
@@ -33,6 +33,47 @@ extern "C" {
namespace olive
{
/**
* @brief Ensure an AudioParams has a usable native channel layout mask.
*
* FFmpeg's abuffer/aformat filters reject channel_layout=0x0 / unspecified
* layouts (e.g. when the user config or a source stream reports a mask of 0).
* If the provided layout is not a valid native mask, fall back to a default
* layout derived from the channel count (stereo when unknown).
*/
static AudioParams FixChannelLayout(const AudioParams &params)
{
AudioParams result = params;
const AVChannelLayout &layout = params.channel_layout();
bool needs_fix = false;
if (!av_channel_layout_check(&layout)) {
needs_fix = true;
} else if (layout.order != AV_CHANNEL_ORDER_NATIVE) {
needs_fix = true;
} else if (layout.u.mask == 0) {
needs_fix = true;
}
if (needs_fix) {
int channels = params.channel_count();
if (channels <= 0) {
channels = 2;
}
qWarning() << "AudioProcessor: fixing invalid/unspecified channel layout"
<< "(channels=" << params.channel_count() << ") -> default"
<< channels << "channel layout";
AVChannelLayout fallback;
av_channel_layout_default(&fallback, channels);
result.set_channel_layout(fallback);
av_channel_layout_uninit(&fallback);
}
return result;
}
AudioProcessor::AudioProcessor()
{
filter_graph_ = nullptr;
@@ -59,16 +100,25 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
return false;
}
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format());
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format());
AudioParams from_fixed = FixChannelLayout(from);
AudioParams to_fixed = FixChannelLayout(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().u.mask << "to sample_rate="
<< to_fixed.sample_rate() << "channels=" << to_fixed.channel_count()
<< "layout_mask=0x" << to_fixed.channel_layout().u.mask << Qt::dec;
// Set up audio buffer args
char filter_args[200];
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from_fixed.format());
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to_fixed.format());
snprintf(
filter_args, 200,
"time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
1, from.sample_rate(), from.sample_rate(), from_fmt_,
from.channel_layout().u.mask);
1, from_fixed.sample_rate(), from_fixed.sample_rate(), from_fmt_,
from_fixed.channel_layout().u.mask);
int r;
@@ -120,18 +170,19 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
}
// Create conversion filter
auto ch1 = from.channel_layout();
auto ch2 = to.channel_layout();
if (from.sample_rate() != to.sample_rate() ||
av_channel_layout_compare(&ch1, &ch2) || from.format() != to.format() ||
(to.format().is_planar() &&
auto ch1 = from_fixed.channel_layout();
auto ch2 = to_fixed.channel_layout();
if (from_fixed.sample_rate() != to_fixed.sample_rate() ||
av_channel_layout_compare(&ch1, &ch2) ||
from_fixed.format() != to_fixed.format() ||
(to_fixed.format().is_planar() &&
create_tempo)) { // Tempo processor automatically converts to packed,
// so if the desired output is planar, it'll need
// to be converted
snprintf(filter_args, 200,
"sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
av_get_sample_fmt_name(to_fmt_), to.sample_rate(),
to.channel_layout().u.mask);
av_get_sample_fmt_name(to_fmt_), to_fixed.sample_rate(),
to_fixed.channel_layout().u.mask);
AVFilterContext *c;
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"),
@@ -182,9 +233,9 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
in_frame_ = av_frame_alloc();
if (in_frame_) {
in_frame_->sample_rate = from.sample_rate();
in_frame_->sample_rate = from_fixed.sample_rate();
in_frame_->format = from_fmt_;
in_frame_->ch_layout = from.channel_layout();
in_frame_->ch_layout = from_fixed.channel_layout();
in_frame_->pts = 0;
} else {
qCritical() << "Failed to allocate input frame";
@@ -199,8 +250,8 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
return false;
}
from_ = from;
to_ = to;
from_ = from_fixed;
to_ = to_fixed;
return true;
}