#include "soundio/sounddeviceportaudio.h"
#include <float.h>
#include <QRegularExpression>
#include <QThread>
#include <QtDebug>
#include "control/controlobject.h"
#include "control/controlproxy.h"
#include "soundio/sounddevice.h"
#include "soundio/soundmanager.h"
#include "soundio/soundmanagerutil.h"
#include "util/denormalsarezero.h"
#include "util/fifo.h"
#include "util/math.h"
#include "util/sample.h"
#include "util/timer.h"
#include "util/trace.h"
#include "vinylcontrol/defs_vinylcontrol.h"
#include "waveform/visualplayposition.h"
#ifdef __LINUX__
// for PaAlsa_EnableRealtimeScheduling
#include <pa_linux_alsa.h>
#endif
namespace {
// Buffer for drift correction 1 full, 1 for r/w, 1 empty
constexpr int kDriftReserve = 1;
// Buffer for drift correction 1 full, 1 for r/w, 1 empty
constexpr int kFifoSize = 2 * kDriftReserve + 1;
constexpr int kCpuUsageUpdateRate = 30; // in 1/s, fits to display frame rate
// We warn only at invalid timing 3, since the first two
// callbacks can be always wrong due to a setup/open jitter
const int m_invalidTimeInfoWarningCount = 3;
int paV19Callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *soundDevice) {
return ((SoundDevicePortAudio*) soundDevice)->callbackProcess(
(SINT) framesPerBuffer, (CSAMPLE*) outputBuffer,
(const CSAMPLE*) inputBuffer, timeInfo, statusFlags);
}
int paV19CallbackDrift(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *soundDevice) {
return ((SoundDevicePortAudio*) soundDevice)->callbackProcessDrift(
(SINT) framesPerBuffer, (CSAMPLE*) outputBuffer,
(const CSAMPLE*) inputBuffer, timeInfo, statusFlags);
}
int paV19CallbackClkRef(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *soundDevice) {
return ((SoundDevicePortAudio*) soundDevice)->callbackProcessClkRef(
(SINT) framesPerBuffer, (CSAMPLE*) outputBuffer,
(const CSAMPLE*) inputBuffer, timeInfo, statusFlags);
}
const QRegularExpression kAlsaHwDeviceRegex("(.*) \\((plug)?(hw:(\\d)+(,(\\d)+))?\\)");
} // anonymous namespace
SoundDevicePortAudio::SoundDevicePortAudio(UserSettingsPointer config,
SoundManager* sm,
const PaDeviceInfo* deviceInfo,
PaHostApiTypeId deviceTypeId,
unsigned int devIndex)
: SoundDevice(config, sm),
m_pStream(nullptr),
m_deviceInfo(deviceInfo),
m_deviceTypeId(deviceTypeId),
m_outputFifo(nullptr),
m_inputFifo(nullptr),
m_outputDrift(false),
m_inputDrift(false),
m_bSetThreadPriority(false),
m_framesSinceAudioLatencyUsageUpdate(0),
m_syncBuffers(2),
m_invalidTimeInfoCount(0),
m_lastCallbackEntrytoDacSecs(0) {
// Setting parent class members:
m_hostAPI = Pa_GetHostApiInfo(deviceInfo->hostApi)->name;
m_dSampleRate = deviceInfo->defaultSampleRate;
if (m_deviceTypeId == paALSA) {
// PortAudio gives the device name including the ALSA hw device. The
// ALSA hw device is an only somewhat reliable identifier; it may change
// when an audio interface is unplugged or Linux is restarted. Separating
// the name from the hw device allows for making the use of both pieces
// of information in SoundManagerConfig::readFromDisk to minimize how
// often users need to reconfigure their sound hardware.
QRegularExpressionMatch match = kAlsaHwDeviceRegex.match(deviceInfo->name);
if (match.hasMatch()) {
m_deviceId.name = match.captured(1);
m_deviceId.alsaHwDevice = match.captured(3);
} else {
// Special ALSA devices like "default" and "pulse" do not match the regex
m_d