Refactor PortAudio backend (#7444)

Refactors the PortAudio backend to fix issues with DirectSound and MME crackling and not loading properly, as well as to improve code quality and maintainability.

---------

Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
This commit is contained in:
Sotonye Atemie
2026-02-01 14:23:44 -05:00
committed by GitHub
parent f504204ef1
commit c14a327d31
6 changed files with 321 additions and 486 deletions

View File

@@ -113,6 +113,11 @@ protected:
m_sampleRate = _new_sr;
}
void setChannels(const ch_cnt_t channels)
{
m_channels = channels;
}
AudioEngine* audioEngine()
{
return m_audioEngine;

View File

@@ -25,136 +25,91 @@
#ifndef LMMS_AUDIO_PORTAUDIO_H
#define LMMS_AUDIO_PORTAUDIO_H
#include <QObject>
#include "lmmsconfig.h"
#include "ComboBoxModel.h"
#ifdef LMMS_HAVE_PORTAUDIO
# include <portaudio.h>
#include <QComboBox>
#include <QFormLayout>
#include <QString>
#include <QWidget>
#include <portaudio.h>
# include "AudioDevice.h"
# include "AudioDeviceSetupWidget.h"
#include "AudioDevice.h"
#include "AudioDeviceSetupWidget.h"
# if defined paNeverDropInput || defined paNonInterleaved
# define PORTAUDIO_V19
# else
# define PORTAUDIO_V18
# endif
namespace lmms {
#endif
namespace lmms
namespace detail {
class PortAudioInitializationGuard
{
class AudioPortAudioSetupUtil : public QObject
{
Q_OBJECT
public slots:
void updateBackends();
void updateDevices();
void updateChannels();
public:
ComboBoxModel m_backendModel;
ComboBoxModel m_deviceModel;
PortAudioInitializationGuard()
: m_error(Pa_Initialize())
{
if (m_error != paNoError) { throw std::runtime_error{"PortAudio: could not initialize"}; }
}
~PortAudioInitializationGuard()
{
if (m_error == paNoError) { Pa_Terminate(); }
}
PortAudioInitializationGuard(const PortAudioInitializationGuard&) = delete;
PortAudioInitializationGuard(PortAudioInitializationGuard&&) = delete;
PortAudioInitializationGuard& operator=(const PortAudioInitializationGuard&) = delete;
PortAudioInitializationGuard& operator=(PortAudioInitializationGuard&&) = delete;
private:
PaError m_error = paNoError;
};
#ifdef LMMS_HAVE_PORTAUDIO
namespace gui
{
class ComboBox;
}
} // namespace detail
class AudioPortAudio : public AudioDevice
{
public:
AudioPortAudio( bool & _success_ful, AudioEngine* audioEngine );
AudioPortAudio(bool& successful, AudioEngine* engine);
~AudioPortAudio() override;
inline static QString name()
{
return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", "PortAudio" );
}
AudioPortAudio(const AudioPortAudio&) = delete;
AudioPortAudio(AudioPortAudio&&) = delete;
AudioPortAudio& operator=(const AudioPortAudio&) = delete;
AudioPortAudio& operator=(AudioPortAudio&&) = delete;
int process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer);
class setupWidget : public gui::AudioDeviceSetupWidget
{
public:
setupWidget( QWidget * _parent );
~setupWidget() override;
void saveSettings() override;
void show() override;
private:
gui::ComboBox * m_backend;
gui::ComboBox * m_device;
AudioPortAudioSetupUtil m_setupUtil;
} ;
private:
void startProcessing() override;
void stopProcessing() override;
#ifdef PORTAUDIO_V19
static int _process_callback( const void *_inputBuffer, void * _outputBuffer,
unsigned long _framesPerBuffer,
const PaStreamCallbackTimeInfo * _timeInfo,
PaStreamCallbackFlags _statusFlags,
void *arg );
static auto name() -> QString { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "PortAudio"); }
#else
private:
static int processCallback(const void* input, void* output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData);
#define paContinue 0
#define paComplete 1
#define Pa_GetDeviceCount Pa_CountDevices
#define Pa_GetDefaultInputDevice Pa_GetDefaultInputDeviceID
#define Pa_GetDefaultOutputDevice Pa_GetDefaultOutputDeviceID
#define Pa_IsStreamActive Pa_StreamActive
detail::PortAudioInitializationGuard m_initGuard;
static int _process_callback( void * _inputBuffer, void * _outputBuffer,
unsigned long _framesPerBuffer, PaTimestamp _outTime, void * _arg );
PaStream* m_paStream = nullptr;
std::vector<SampleFrame> m_outBuf;
std::size_t m_outBufPos = 0;
};
} // namespace lmms
namespace lmms::gui {
class AudioPortAudioSetupWidget : public AudioDeviceSetupWidget
{
public:
AudioPortAudioSetupWidget(QWidget* parent);
using PaTime = double;
using PaDeviceIndex = PaDeviceID;
void show() override;
void saveSettings() override;
using PaStreamParameters = struct
{
PaDeviceIndex device;
int channelCount;
PaSampleFormat sampleFormat;
PaTime suggestedLatency;
void *hostApiSpecificStreamInfo;
} PaStreamParameters;
#endif // PORTAUDIO_V19
PaStream * m_paStream;
PaStreamParameters m_outputParameters;
PaStreamParameters m_inputParameters;
bool m_wasPAInitError;
SampleFrame* m_outBuf;
std::size_t m_outBufPos;
fpp_t m_outBufSize;
bool m_stopped;
} ;
private:
class DeviceSelectorWidget;
QComboBox* m_backendComboBox = nullptr;
DeviceSelectorWidget* m_inputDevice = nullptr;
DeviceSelectorWidget* m_outputDevice = nullptr;
};
} // namespace lmms::gui
#endif // LMMS_HAVE_PORTAUDIO
} // namespace lmms
#endif // LMMS_AUDIO_PORTAUDIO_H