From 5bed4396ea9d11d677b09a29f2eb83dc8dc09f45 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 5 May 2022 16:32:17 -0700 Subject: [PATCH] use audioprocessor to match device's output --- app/audio/audiomanager.cpp | 15 +++++-- app/audio/audiomanager.h | 5 ++- app/audio/audioprocessor.h | 3 ++ app/config/config.cpp | 4 ++ .../preferences/tabs/preferencesaudiotab.cpp | 40 +++++++++++++++++++ .../preferences/tabs/preferencesaudiotab.h | 4 ++ .../standardcombos/sampleformatcombobox.h | 18 +++++++++ app/widget/viewer/viewer.cpp | 28 +++++++++---- app/widget/viewer/viewer.h | 3 +- 9 files changed, 107 insertions(+), 13 deletions(-) diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index fe791b3d5..3817f285a 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -80,10 +80,11 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con return paContinue; } -void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples) +bool AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples, QString *error) { if (output_device_ == paNoDevice) { - return; + if (error) *error = tr("No output device is set"); + return false; } if (output_params_ != params || output_stream_ == nullptr) { @@ -93,7 +94,13 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam PaStreamParameters p = GetPortAudioParams(params, output_device_); - Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); + PaError r = Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); + if (r != paNoError) { + // Unhandled error + //qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r); + if (error) *error = Pa_GetErrorText(r); + return false; + } output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1)); } @@ -103,6 +110,8 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam if (!Pa_IsStreamActive(output_stream_)) { Pa_StartStream(output_stream_); } + + return true; } void AudioManager::ClearBufferedOutput() diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 20d1be75b..2916d29d2 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -27,6 +27,7 @@ #include #include "audiovisualwaveform.h" +#include "audio/audioprocessor.h" #include "common/define.h" #include "codec/ffmpeg/ffmpegencoder.h" #include "render/audioparams.h" @@ -52,7 +53,7 @@ public: void SetOutputNotifyInterval(int n); - void PushToOutput(const AudioParams ¶ms, const QByteArray& samples); + bool PushToOutput(const AudioParams ¶ms, const QByteArray& samples, QString *error = nullptr); void ClearBufferedOutput(); @@ -86,6 +87,8 @@ public: signals: void OutputNotify(); + void OutputParamsChanged(); + private: AudioManager(); diff --git a/app/audio/audioprocessor.h b/app/audio/audioprocessor.h index 534cca9b1..38826c707 100644 --- a/app/audio/audioprocessor.h +++ b/app/audio/audioprocessor.h @@ -51,6 +51,9 @@ public: void Flush(); + const AudioParams &from() const { return from_; } + const AudioParams &to() const { return to_; } + private: static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo); diff --git a/app/config/config.cpp b/app/config/config.cpp index d114b0a10..0aaaa6253 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -121,6 +121,10 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000); + SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); + SetEntryInternal(QStringLiteral("AudioOutputSampleFormat"), NodeValue::kInt, AudioParams::kFormatSigned16Packed); + SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV); SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM); SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000); diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index c8760ae8a..7d2edc892 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -69,6 +69,40 @@ PreferencesAudioTab::PreferencesAudioTab() audio_output_devices_ = new QComboBox(); output_layout->addWidget(audio_output_devices_, row, 1); + + row++; + + { + int output_row = 0; + + QGroupBox *output_param_group = new QGroupBox(tr("Advanced")); + output_layout->addWidget(output_param_group, row, 0, 1, 2); + + QGridLayout *output_param_layout = new QGridLayout(output_param_group); + + output_param_layout->addWidget(new QLabel(tr("Sample Rate:")), output_row, 0); + + output_rate_combo_ = new SampleRateComboBox(); + output_rate_combo_->SetSampleRate(OLIVE_CONFIG("AudioOutputSampleRate").toInt()); + output_param_layout->addWidget(output_rate_combo_, output_row, 1); + + output_row++; + + output_param_layout->addWidget(new QLabel(tr("Channel Layout:")), output_row, 0); + + output_ch_layout_combo_ = new ChannelLayoutComboBox(); + output_ch_layout_combo_->SetChannelLayout(OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong()); + output_param_layout->addWidget(output_ch_layout_combo_, output_row, 1); + + output_row++; + + output_param_layout->addWidget(new QLabel(tr("Sample Format:")), output_row, 0); + + output_fmt_combo_ = new SampleFormatComboBox(); + output_fmt_combo_->SetPackedFormats(); + output_fmt_combo_->SetSampleFormat(static_cast(OLIVE_CONFIG("AudioOutputSampleFormat").toInt())); + output_param_layout->addWidget(output_fmt_combo_, output_row, 1); + } } row = 0; @@ -146,12 +180,18 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command) AudioManager::instance()->SetOutputDevice(output_device); AudioManager::instance()->SetInputDevice(input_device); + OLIVE_CONFIG("AudioOutputSampleRate") = output_rate_combo_->GetSampleRate(); + OLIVE_CONFIG("AudioOutputChannelLayout") = QVariant::fromValue(output_ch_layout_combo_->GetChannelLayout()); + OLIVE_CONFIG("AudioOutputSampleFormat") = output_fmt_combo_->GetSampleFormat(); + OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat(); OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec(); OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate(); OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout()); OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue()); OLIVE_CONFIG("AudioRecordingSampleFormat") = record_options_->sample_format_combobox()->GetSampleFormat(); + + emit AudioManager::instance()->OutputParamsChanged(); } void PreferencesAudioTab::RefreshBackends() diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index 8fccd4276..550b6fa9e 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -61,6 +61,10 @@ private: */ QPushButton* refresh_devices_btn_; + SampleRateComboBox *output_rate_combo_; + ChannelLayoutComboBox *output_ch_layout_combo_; + SampleFormatComboBox *output_fmt_combo_; + ExportFormatComboBox *record_format_combo_; ExportAudioTab *record_options_; diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index a48e9016a..d52a20709 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -57,6 +57,24 @@ public: } } + void SetPackedFormats() + { + AudioParams::Format tmp = AudioParams::kFormatInvalid; + + if (attempt_to_restore_format_) { + tmp = GetSampleFormat(); + } + + clear(); + for (int i=AudioParams::kPackedStart; i(i)); + } + + if (attempt_to_restore_format_) { + SetSampleFormat(tmp); + } + } + AudioParams::Format GetSampleFormat() const { return static_cast(this->currentData().toInt()); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 5218e6698..3cac18a5e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -141,6 +141,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); + connect(AudioManager::instance(), &AudioManager::OutputParamsChanged, this, &ViewerWidget::UpdateAudioProcessor); } ViewerWidget::~ViewerWidget() @@ -457,12 +458,16 @@ void ViewerWidget::DisarmRecording() void ViewerWidget::UpdateAudioProcessor() { - audio_processor_.Close(); + if (GetConnectedNode()) { + audio_processor_.Close(); - AudioParams ap = GetConnectedNode()->GetAudioParams(); - AudioParams packed = ap; - packed.set_format(AudioParams::GetPackedEquivalent(ap.format())); - audio_processor_.Open(ap, packed, (playback_speed_ == 0) ? 1 : std::abs(playback_speed_)); + AudioParams ap = GetConnectedNode()->GetAudioParams(); + AudioParams packed(OLIVE_CONFIG("AudioOutputSampleRate").toInt(), + OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong(), + static_cast(OLIVE_CONFIG("AudioOutputSampleFormat").toInt())); + + audio_processor_.Open(ap, packed, (playback_speed_ == 0) ? 1 : std::abs(playback_speed_)); + } } void ViewerWidget::CloseAudioProcessor() @@ -520,7 +525,7 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() prequeued_audio_.append(pack); } else { // Push directly to audio manager - AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), pack); + AudioManager::instance()->PushToOutput(audio_processor_.to(), pack); } } } else { @@ -560,9 +565,12 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing() if (r >= 0) { if (!buf.empty()) { + QString error; const QByteArray &packed = buf.at(0); AudioManager::instance()->ClearBufferedOutput(); - AudioManager::instance()->PushToOutput(samples->audio_params(), packed); + if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), packed, &error)) { + Core::instance()->ShowStatusBarMessage(tr("Audio scrubbing failed: %1").arg(error)); + } AudioMonitor::PushBytesOnAll(packed); } } else { @@ -872,7 +880,11 @@ void ViewerWidget::FinishPlayPreprocess() // Start audio waveform playback if (!prequeued_audio_.isEmpty()) { - AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), prequeued_audio_); + QString error; + if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), prequeued_audio_, &error)) { + QMessageBox::critical(this, tr("Audio Error"), tr("Failed to start audio: %1\n\n" + "Please check your audio preferences and try again.").arg(error)); + } prequeued_audio_.clear(); AudioMonitor::StartWaveformOnAll(&GetConnectedNode()->audio_playback_cache()->visual(), diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index be515ff4d..ba7f7be56 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -211,7 +211,6 @@ private: void DisarmRecording(); - void UpdateAudioProcessor(); void CloseAudioProcessor(); QStackedWidget* stack_; @@ -319,6 +318,8 @@ private slots: void ForceRequeueFromCurrentTime(); + void UpdateAudioProcessor(); + }; }