mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
refactor(audio): fix some remarks from review
This commit is contained in:
parent
8c9e82ee50
commit
7f3887f67c
|
@ -51,7 +51,7 @@
|
|||
| [qrencode] | >= 3.0.3 | |
|
||||
| [sqlcipher] | >= 3.2.0 | |
|
||||
| [pkg-config] | >= 0.28 | |
|
||||
| [filteraudio] | >= 0.0.1 | |
|
||||
| [filteraudio] | >= 0.0.1 | optional dependency |
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
|
|
|
@ -169,6 +169,7 @@
|
|||
*/
|
||||
Audio& Audio::getInstance()
|
||||
{
|
||||
// TODO: replace backend selection by inversion of control
|
||||
#ifdef USE_FILTERAUDIO
|
||||
static bool initialized = false;
|
||||
static bool Backend2 = false;
|
||||
|
|
|
@ -249,8 +249,14 @@ bool OpenAL::autoInitOutput()
|
|||
|
||||
bool OpenAL::initInput(const QString& deviceName)
|
||||
{
|
||||
if (!Settings::getInstance().getAudioInDevEnabled())
|
||||
return initInput(deviceName, AUDIO_CHANNELS);
|
||||
}
|
||||
|
||||
bool OpenAL::initInput(const QString& deviceName, uint32_t channels)
|
||||
{
|
||||
if (!Settings::getInstance().getAudioInDevEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Opening audio input" << deviceName;
|
||||
assert(!alInDev);
|
||||
|
@ -260,7 +266,7 @@ bool OpenAL::initInput(const QString& deviceName)
|
|||
const uint32_t sampleRate = AUDIO_SAMPLE_RATE;
|
||||
const uint16_t frameDuration = AUDIO_FRAME_DURATION;
|
||||
const uint32_t chnls = AUDIO_CHANNELS;
|
||||
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
|
||||
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * channels;
|
||||
|
||||
const QByteArray qDevName = deviceName.toUtf8();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||
|
@ -285,7 +291,7 @@ bool OpenAL::initInput(const QString& deviceName)
|
|||
*/
|
||||
bool OpenAL::initOutput(const QString& deviceName)
|
||||
{
|
||||
outSources.clear();
|
||||
peerSources.clear();
|
||||
|
||||
outputInitialized = false;
|
||||
if (!Settings::getInstance().getAudioOutDevEnabled())
|
||||
|
@ -442,17 +448,19 @@ void OpenAL::cleanupOutput()
|
|||
alMainBuffer = 0;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(nullptr))
|
||||
if (!alcMakeContextCurrent(nullptr)) {
|
||||
qWarning("Failed to clear audio context.");
|
||||
}
|
||||
|
||||
alcDestroyContext(alOutContext);
|
||||
alOutContext = nullptr;
|
||||
|
||||
qDebug() << "Closing audio output";
|
||||
if (alcCloseDevice(alOutDev))
|
||||
if (alcCloseDevice(alOutDev)) {
|
||||
alOutDev = nullptr;
|
||||
else
|
||||
} else {
|
||||
qWarning("Failed to close output.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,23 +565,18 @@ void OpenAL::subscribeOutput(uint& sid)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(alOutContext)) {
|
||||
qWarning("Failed to activate output context.");
|
||||
return;
|
||||
}
|
||||
|
||||
alGenSources(1, &sid);
|
||||
assert(sid);
|
||||
outSources << sid;
|
||||
peerSources << sid;
|
||||
|
||||
qDebug() << "Audio source" << sid << "created. Sources active:" << outSources.size();
|
||||
qDebug() << "Audio source" << sid << "created. Sources active:" << peerSources.size();
|
||||
}
|
||||
|
||||
void OpenAL::unsubscribeOutput(uint& sid)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
outSources.removeAll(sid);
|
||||
peerSources.removeAll(sid);
|
||||
|
||||
if (sid) {
|
||||
if (alIsSource(sid)) {
|
||||
|
@ -588,7 +591,7 @@ void OpenAL::unsubscribeOutput(uint& sid)
|
|||
alDeleteBuffers(processed, bufids);
|
||||
delete[] bufids;
|
||||
alDeleteSources(1, &sid);
|
||||
qDebug() << "Audio source" << sid << "deleted. Sources active:" << outSources.size();
|
||||
qDebug() << "Audio source" << sid << "deleted. Sources active:" << peerSources.size();
|
||||
} else {
|
||||
qWarning() << "Trying to delete invalid audio source" << sid;
|
||||
}
|
||||
|
@ -596,7 +599,7 @@ void OpenAL::unsubscribeOutput(uint& sid)
|
|||
sid = 0;
|
||||
}
|
||||
|
||||
if (outSources.isEmpty())
|
||||
if (peerSources.isEmpty())
|
||||
cleanupOutput();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ class OpenAL : public Audio
|
|||
|
||||
public:
|
||||
OpenAL();
|
||||
~OpenAL();
|
||||
virtual ~OpenAL();
|
||||
|
||||
qreal outputVolume() const;
|
||||
void setOutputVolume(qreal volume);
|
||||
|
@ -82,21 +82,26 @@ public:
|
|||
void playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels,
|
||||
int sampleRate);
|
||||
|
||||
private:
|
||||
protected:
|
||||
static void checkAlError() noexcept;
|
||||
static void checkAlcError(ALCdevice* device) noexcept;
|
||||
|
||||
qreal inputGainFactor() const;
|
||||
virtual void cleanupInput();
|
||||
virtual void cleanupOutput();
|
||||
|
||||
bool autoInitInput();
|
||||
bool autoInitOutput();
|
||||
bool initInput(const QString& deviceName);
|
||||
bool initOutput(const QString& outDevDescr);
|
||||
void cleanupInput();
|
||||
void cleanupOutput();
|
||||
void playMono16SoundCleanup();
|
||||
void doCapture();
|
||||
qreal inputGainFactor() const;
|
||||
|
||||
bool initInput(const QString& deviceName, uint32_t channels);
|
||||
|
||||
private:
|
||||
virtual bool initInput(const QString& deviceName);
|
||||
virtual bool initOutput(const QString& outDevDescr);
|
||||
void playMono16SoundCleanup();
|
||||
void doCapture();
|
||||
|
||||
protected:
|
||||
QThread* audioThread;
|
||||
mutable QMutex audioLock;
|
||||
|
||||
|
@ -110,7 +115,7 @@ private:
|
|||
ALuint alMainBuffer;
|
||||
bool outputInitialized;
|
||||
|
||||
QList<ALuint> outSources;
|
||||
QList<ALuint> peerSources;
|
||||
qreal gain;
|
||||
qreal gainFactor;
|
||||
qreal minInGain = -30;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2014-2017 by The qTox Project Contributors
|
||||
Copyright © 2017 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
|
@ -40,6 +40,30 @@ extern "C" {
|
|||
* @class OpenAL
|
||||
* @brief Provides the OpenAL audio backend
|
||||
*
|
||||
* The following graphic describes the audio rendering pipeline with echo canceling
|
||||
*
|
||||
* | alProxyContext | | alOutContext |
|
||||
* peerSources[] | | |
|
||||
* \ | | |
|
||||
* -> alProxyDev -> filter_audio -> alProxySource -> alOutDev -> Soundcard
|
||||
* /
|
||||
* alMainSource
|
||||
*
|
||||
* Without echo cancelling the pipeline is simplified through alProxyDev = alOutDev
|
||||
* and alProxyContext = alOutContext
|
||||
*
|
||||
* | alProxyContext |
|
||||
* peerSources[] |
|
||||
* \ |
|
||||
* -> alOutDev -> Soundcard
|
||||
* /
|
||||
* alMainSource
|
||||
*
|
||||
* To keep all functions in writing to the correct context, all functions changing
|
||||
* the context MUST exit with alProxyContext as active context and MUST not be
|
||||
* interrupted. For this to work, all functions of the base class modifying the
|
||||
* context have to be overriden.
|
||||
*
|
||||
* @var BUFFER_COUNT
|
||||
* @brief Number of buffers to use per audio source
|
||||
*/
|
||||
|
@ -47,235 +71,25 @@ extern "C" {
|
|||
static const unsigned int BUFFER_COUNT = 16;
|
||||
static const unsigned int PROXY_BUFFER_COUNT = 4;
|
||||
|
||||
#define GET_PROC_ADDR(dev, name) name = reinterpret_cast<LP##name>(alcGetProcAddress(dev, #name))
|
||||
|
||||
typedef LPALCLOOPBACKOPENDEVICESOFT LPalcLoopbackOpenDeviceSOFT;
|
||||
typedef LPALCISRENDERFORMATSUPPORTEDSOFT LPalcIsRenderFormatSupportedSOFT;
|
||||
typedef LPALGETSOURCEDVSOFT LPalGetSourcedvSOFT;
|
||||
typedef LPALCRENDERSAMPLESSOFT LPalcRenderSamplesSOFT;
|
||||
|
||||
|
||||
OpenAL2::OpenAL2()
|
||||
: audioThread{new QThread}
|
||||
, alInDev{nullptr}
|
||||
, inSubscriptions{0}
|
||||
, alOutDev{nullptr}
|
||||
, alOutContext{nullptr}
|
||||
, alProxyDev{nullptr}
|
||||
: alProxyDev{nullptr}
|
||||
, alProxyContext{nullptr}
|
||||
, alMainSource{0}
|
||||
, alMainBuffer{0}
|
||||
, alProxySource{0}
|
||||
, alProxyBuffer{0}
|
||||
, outputInitialized{false}
|
||||
{
|
||||
// initialize OpenAL error stack
|
||||
alGetError();
|
||||
alcGetError(nullptr);
|
||||
|
||||
audioThread->setObjectName("qTox Audio");
|
||||
QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
|
||||
|
||||
moveToThread(audioThread);
|
||||
|
||||
connect(&captureTimer, &QTimer::timeout, this, &OpenAL2::doAudio);
|
||||
captureTimer.setInterval(AUDIO_FRAME_DURATION / 2);
|
||||
captureTimer.setSingleShot(false);
|
||||
captureTimer.start();
|
||||
connect(&playMono16Timer, &QTimer::timeout, this, &OpenAL2::playMono16SoundCleanup);
|
||||
playMono16Timer.setSingleShot(true);
|
||||
|
||||
audioThread->start();
|
||||
}
|
||||
|
||||
OpenAL2::~OpenAL2()
|
||||
{
|
||||
audioThread->exit();
|
||||
audioThread->wait();
|
||||
cleanupInput();
|
||||
cleanupOutput();
|
||||
}
|
||||
|
||||
void OpenAL2::checkAlError() noexcept
|
||||
{
|
||||
const ALenum al_err = alGetError();
|
||||
if (al_err != AL_NO_ERROR)
|
||||
qWarning("OpenAL error: %d", al_err);
|
||||
}
|
||||
|
||||
void OpenAL2::checkAlcError(ALCdevice* device) noexcept
|
||||
{
|
||||
const ALCenum alc_err = alcGetError(device);
|
||||
if (alc_err)
|
||||
qWarning("OpenAL error: %d", alc_err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the current output volume (between 0 and 1)
|
||||
*/
|
||||
qreal OpenAL2::outputVolume() const
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
ALfloat volume = 0.0;
|
||||
|
||||
if (alOutDev) {
|
||||
alGetListenerf(AL_GAIN, &volume);
|
||||
checkAlError();
|
||||
}
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the master output volume.
|
||||
*
|
||||
* @param[in] volume the master volume (between 0 and 1)
|
||||
*/
|
||||
void OpenAL2::setOutputVolume(qreal volume)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
volume = std::max(0.0, std::min(volume, 1.0));
|
||||
|
||||
alListenerf(AL_GAIN, static_cast<ALfloat>(volume));
|
||||
checkAlError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The minimum gain value for an input device.
|
||||
*
|
||||
* @return minimum gain value in dB
|
||||
*/
|
||||
qreal OpenAL2::minInputGain() const
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
return minInGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the minimum allowed gain value in dB.
|
||||
*
|
||||
* @note Default is -30dB; usually you don't need to alter this value;
|
||||
*/
|
||||
void OpenAL2::setMinInputGain(qreal dB)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
minInGain = dB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The maximum gain value for an input device.
|
||||
*
|
||||
* @return maximum gain value in dB
|
||||
*/
|
||||
qreal OpenAL2::maxInputGain() const
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
return maxInGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the maximum allowed gain value in dB.
|
||||
*
|
||||
* @note Default is 30dB; usually you don't need to alter this value.
|
||||
*/
|
||||
void OpenAL2::setMaxInputGain(qreal dB)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
maxInGain = dB;
|
||||
}
|
||||
|
||||
void OpenAL2::reinitInput(const QString& inDevDesc)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
cleanupInput();
|
||||
initInput(inDevDesc);
|
||||
}
|
||||
|
||||
bool OpenAL2::reinitOutput(const QString& outDevDesc)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
cleanupOutput();
|
||||
return initOutput(outDevDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Subscribe to capture sound from the opened input device.
|
||||
*
|
||||
* If the input device is not open, it will be opened before capturing.
|
||||
*/
|
||||
void OpenAL2::subscribeInput()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!autoInitInput()) {
|
||||
qWarning("Failed to subscribe to audio input device.");
|
||||
return;
|
||||
}
|
||||
|
||||
++inSubscriptions;
|
||||
qDebug() << "Subscribed to audio input device [" << inSubscriptions << "subscriptions ]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe from capturing from an opened input device.
|
||||
*
|
||||
* If the input device has no more subscriptions, it will be closed.
|
||||
*/
|
||||
void OpenAL2::unsubscribeInput()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!inSubscriptions)
|
||||
return;
|
||||
|
||||
inSubscriptions--;
|
||||
qDebug() << "Unsubscribed from audio input device [" << inSubscriptions
|
||||
<< "subscriptions left ]";
|
||||
|
||||
if (!inSubscriptions)
|
||||
cleanupInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize audio input device, if not initialized.
|
||||
*
|
||||
* @return true, if device was initialized; false otherwise
|
||||
*/
|
||||
bool OpenAL2::autoInitInput()
|
||||
{
|
||||
return alInDev ? true : initInput(Settings::getInstance().getInDev());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize audio output device, if not initialized.
|
||||
*
|
||||
* @return true, if device was initialized; false otherwise
|
||||
*/
|
||||
bool OpenAL2::autoInitOutput()
|
||||
{
|
||||
return alOutDev ? true : initOutput(Settings::getInstance().getOutDev());
|
||||
}
|
||||
|
||||
bool OpenAL2::initInput(const QString& deviceName)
|
||||
{
|
||||
if (!Settings::getInstance().getAudioInDevEnabled())
|
||||
return false;
|
||||
|
||||
qDebug() << "Opening audio input" << deviceName;
|
||||
assert(!alInDev);
|
||||
|
||||
const ALCsizei bufSize = AUDIO_FRAME_SAMPLE_COUNT * 4 * 2;
|
||||
|
||||
const QByteArray qDevName = deviceName.toUtf8();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||
alInDev = alcCaptureOpenDevice(tmpDevName, AUDIO_SAMPLE_RATE, AL_FORMAT_MONO16, bufSize);
|
||||
|
||||
// Restart the capture if necessary
|
||||
if (!alInDev) {
|
||||
qWarning() << "Failed to initialize audio input device:" << deviceName;
|
||||
return false;
|
||||
}
|
||||
|
||||
setInputGain(Settings::getInstance().getAudioInGainDecibel());
|
||||
|
||||
qDebug() << "Opened audio input" << deviceName;
|
||||
alcCaptureStart(alInDev);
|
||||
|
||||
return true;
|
||||
return OpenAL::initInput(deviceName, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,32 +100,28 @@ bool OpenAL2::initInput(const QString& deviceName)
|
|||
bool OpenAL2::loadOpenALExtensions(ALCdevice* dev)
|
||||
{
|
||||
// load OpenAL extension functions
|
||||
alcLoopbackOpenDeviceSOFT = reinterpret_cast<LPALCLOOPBACKOPENDEVICESOFT>(
|
||||
alcGetProcAddress(dev, "alcLoopbackOpenDeviceSOFT"));
|
||||
GET_PROC_ADDR(dev, alcLoopbackOpenDeviceSOFT);
|
||||
checkAlcError(dev);
|
||||
if (!alcLoopbackOpenDeviceSOFT) {
|
||||
qDebug() << "Failed to load alcLoopbackOpenDeviceSOFT function!";
|
||||
return false;
|
||||
}
|
||||
|
||||
alcIsRenderFormatSupportedSOFT = reinterpret_cast<LPALCISRENDERFORMATSUPPORTEDSOFT>(
|
||||
alcGetProcAddress(dev, "alcIsRenderFormatSupportedSOFT"));
|
||||
GET_PROC_ADDR(dev, alcIsRenderFormatSupportedSOFT);
|
||||
checkAlcError(dev);
|
||||
if (!alcIsRenderFormatSupportedSOFT) {
|
||||
qDebug() << "Failed to load alcIsRenderFormatSupportedSOFT function!";
|
||||
return false;
|
||||
}
|
||||
|
||||
alGetSourcedvSOFT =
|
||||
reinterpret_cast<LPALGETSOURCEDVSOFT>(alcGetProcAddress(dev, "alGetSourcedvSOFT"));
|
||||
GET_PROC_ADDR(dev, alGetSourcedvSOFT);
|
||||
checkAlcError(dev);
|
||||
if (!alGetSourcedvSOFT) {
|
||||
qDebug() << "Failed to load alGetSourcedvSOFT function!";
|
||||
return false;
|
||||
}
|
||||
|
||||
alcRenderSamplesSOFT = reinterpret_cast<LPALCRENDERSAMPLESSOFT>(
|
||||
alcGetProcAddress(alOutDev, "alcRenderSamplesSOFT"));
|
||||
GET_PROC_ADDR(dev, alcRenderSamplesSOFT);
|
||||
checkAlcError(dev);
|
||||
if (!alcRenderSamplesSOFT) {
|
||||
qDebug() << "Failed to load alcRenderSamplesSOFT function!";
|
||||
|
@ -402,8 +212,9 @@ bool OpenAL2::initOutput(const QString& deviceName)
|
|||
peerSources.clear();
|
||||
|
||||
outputInitialized = false;
|
||||
if (!Settings::getInstance().getAudioOutDevEnabled())
|
||||
if (!Settings::getInstance().getAudioOutDevEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Opening audio output" << deviceName;
|
||||
assert(!alOutDev);
|
||||
|
@ -449,143 +260,28 @@ bool OpenAL2::initOutput(const QString& deviceName)
|
|||
core->getAv()->invalidateCallSources();
|
||||
}
|
||||
|
||||
// ensure alProxyContext is active
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
outputInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Play a 44100Hz mono 16bit PCM sound from a file
|
||||
*
|
||||
* @param[in] path the path to the sound file
|
||||
*/
|
||||
void OpenAL2::playMono16Sound(const QString& path)
|
||||
{
|
||||
QFile sndFile(path);
|
||||
sndFile.open(QIODevice::ReadOnly);
|
||||
playMono16Sound(QByteArray(sndFile.readAll()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Play a 44100Hz mono 16bit PCM sound
|
||||
*/
|
||||
void OpenAL2::playMono16Sound(const QByteArray& data)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!autoInitOutput())
|
||||
return;
|
||||
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
if (!alMainBuffer)
|
||||
alGenBuffers(1, &alMainBuffer);
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_PLAYING) {
|
||||
alSourceStop(alMainSource);
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
}
|
||||
|
||||
alBufferData(alMainBuffer, AL_FORMAT_MONO16, data.constData(), data.size(), 44100);
|
||||
alSourcei(alMainSource, AL_BUFFER, static_cast<ALint>(alMainBuffer));
|
||||
alSourcePlay(alMainSource);
|
||||
|
||||
int durationMs = data.size() * 1000 / 2 / 44100;
|
||||
playMono16Timer.start(durationMs + 50);
|
||||
}
|
||||
|
||||
void OpenAL2::playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels,
|
||||
int sampleRate)
|
||||
{
|
||||
assert(channels == 1 || channels == 2);
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!(alOutDev && outputInitialized))
|
||||
return;
|
||||
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
|
||||
ALuint bufids[BUFFER_COUNT];
|
||||
ALint processed = 0, queued = 0;
|
||||
alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed);
|
||||
alGetSourcei(sourceId, AL_BUFFERS_QUEUED, &queued);
|
||||
alSourcei(sourceId, AL_LOOPING, AL_FALSE);
|
||||
|
||||
if (processed == 0) {
|
||||
if (queued >= BUFFER_COUNT) {
|
||||
// reached limit, drop audio
|
||||
return;
|
||||
}
|
||||
// create new buffer if none got free and we're below the limit
|
||||
alGenBuffers(1, bufids);
|
||||
} else {
|
||||
// unqueue all processed buffers
|
||||
alSourceUnqueueBuffers(sourceId, processed, bufids);
|
||||
// delete all but the first buffer, reuse first for new data
|
||||
alDeleteBuffers(processed - 1, bufids + 1);
|
||||
}
|
||||
|
||||
alBufferData(bufids[0], (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data,
|
||||
samples * 2 * channels, sampleRate);
|
||||
alSourceQueueBuffers(sourceId, 1, bufids);
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(sourceId, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
alSourcePlay(sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close active audio input device.
|
||||
*/
|
||||
void OpenAL2::cleanupInput()
|
||||
{
|
||||
if (!alInDev)
|
||||
return;
|
||||
|
||||
qDebug() << "Closing audio input";
|
||||
alcCaptureStop(alInDev);
|
||||
if (alcCaptureCloseDevice(alInDev) == ALC_TRUE)
|
||||
alInDev = nullptr;
|
||||
else
|
||||
qWarning() << "Failed to close input";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close active audio output device
|
||||
*/
|
||||
void OpenAL2::cleanupOutput()
|
||||
{
|
||||
outputInitialized = false;
|
||||
|
||||
if (alProxyDev) {
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
|
||||
alSourceStop(alMainSource);
|
||||
alDeleteSources(1, &alMainSource);
|
||||
|
||||
if (alMainBuffer) {
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(nullptr))
|
||||
qWarning("Failed to clear audio context.");
|
||||
|
||||
alcDestroyContext(alOutContext);
|
||||
alProxyContext = nullptr;
|
||||
|
||||
qDebug() << "Closing audio output";
|
||||
if (alcCloseDevice(alProxyDev))
|
||||
alProxyDev = nullptr;
|
||||
else
|
||||
qWarning("Failed to close output.");
|
||||
}
|
||||
OpenAL::cleanupOutput();
|
||||
|
||||
if (echoCancelSupported) {
|
||||
alcMakeContextCurrent(alOutContext);
|
||||
alSourceStop(alProxySource);
|
||||
// TODO: delete buffers
|
||||
ALint processed = 0;
|
||||
ALuint bufids[PROXY_BUFFER_COUNT];
|
||||
alGetSourcei(alProxySource, AL_BUFFERS_PROCESSED, &processed);
|
||||
alSourceUnqueueBuffers(alProxySource, processed, bufids);
|
||||
alDeleteBuffers(processed, bufids);
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(alOutContext);
|
||||
alOutContext = nullptr;
|
||||
alcCloseDevice(alOutDev);
|
||||
|
@ -596,25 +292,6 @@ void OpenAL2::cleanupOutput()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called after a mono16 sound stopped playing
|
||||
*/
|
||||
void OpenAL2::playMono16SoundCleanup()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_STOPPED) {
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
} else {
|
||||
// the audio didn't finish, try again later
|
||||
playMono16Timer.start(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle audio output
|
||||
*/
|
||||
|
@ -626,22 +303,22 @@ void OpenAL2::doOutput()
|
|||
alGetSourcei(alProxySource, AL_BUFFERS_PROCESSED, &processed);
|
||||
alGetSourcei(alProxySource, AL_BUFFERS_QUEUED, &queued);
|
||||
|
||||
// qDebug() << "Speedtest processed: " << processed << " queued: " << queued;
|
||||
|
||||
if (processed > 0) {
|
||||
// unqueue all processed buffers
|
||||
alSourceUnqueueBuffers(alProxySource, 1, bufids);
|
||||
alSourceUnqueueBuffers(alProxySource, processed, bufids);
|
||||
// delete all but the first buffer, reuse first for new data
|
||||
alDeleteBuffers(processed - 1, bufids + 1);
|
||||
} else if (queued < PROXY_BUFFER_COUNT) {
|
||||
// create new buffer until the maximum is reached
|
||||
alGenBuffers(1, bufids);
|
||||
} else {
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
return;
|
||||
}
|
||||
|
||||
ALdouble latency[2] = {0};
|
||||
alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency);
|
||||
checkAlError();
|
||||
// qDebug() << "Playback latency: " << latency[1] << "offset: " << latency[0];
|
||||
|
||||
ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT] = {0};
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
|
@ -662,7 +339,7 @@ void OpenAL2::doOutput()
|
|||
}
|
||||
|
||||
// do echo cancel
|
||||
int retVal = pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT);
|
||||
pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT);
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alProxySource, AL_SOURCE_STATE, &state);
|
||||
|
@ -670,6 +347,7 @@ void OpenAL2::doOutput()
|
|||
qDebug() << "Proxy source underflow detected";
|
||||
alSourcePlay(alProxySource);
|
||||
}
|
||||
alcMakeContextCurrent(alProxyContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -693,9 +371,9 @@ void OpenAL2::doInput()
|
|||
|
||||
// gain amplification with clipping to 16-bit boundaries
|
||||
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT; ++i) {
|
||||
int ampPCM =
|
||||
qBound<int>(std::numeric_limits<int16_t>::min(), qRound(buf[i] * inputGainFactor()),
|
||||
std::numeric_limits<int16_t>::max());
|
||||
int ampPCM = qBound<int>(std::numeric_limits<int16_t>::min(),
|
||||
qRound(buf[i] * OpenAL::inputGainFactor()),
|
||||
std::numeric_limits<int16_t>::max());
|
||||
|
||||
buf[i] = static_cast<int16_t>(ampPCM);
|
||||
}
|
||||
|
@ -723,135 +401,3 @@ void OpenAL2::doAudio()
|
|||
doInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the output device is open
|
||||
*/
|
||||
bool OpenAL2::isOutputReady() const
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
return alOutDev && outputInitialized;
|
||||
}
|
||||
|
||||
QStringList OpenAL2::outDeviceNames()
|
||||
{
|
||||
QStringList list;
|
||||
const ALchar* pDeviceList = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE)
|
||||
? alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER)
|
||||
: alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
|
||||
if (pDeviceList) {
|
||||
while (*pDeviceList) {
|
||||
int len = static_cast<int>(strlen(pDeviceList));
|
||||
list << QString::fromUtf8(pDeviceList, len);
|
||||
pDeviceList += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList OpenAL2::inDeviceNames()
|
||||
{
|
||||
QStringList list;
|
||||
const ALchar* pDeviceList = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
|
||||
if (pDeviceList) {
|
||||
while (*pDeviceList) {
|
||||
int len = static_cast<int>(strlen(pDeviceList));
|
||||
list << QString::fromUtf8(pDeviceList, len);
|
||||
pDeviceList += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void OpenAL2::subscribeOutput(uint& sid)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!autoInitOutput()) {
|
||||
qWarning("Failed to subscribe to audio output device.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(alProxyContext)) {
|
||||
qWarning("Failed to activate output context.");
|
||||
return;
|
||||
}
|
||||
|
||||
alGenSources(1, &sid);
|
||||
assert(sid);
|
||||
peerSources << sid;
|
||||
|
||||
qDebug() << "Audio source" << sid << "created. Sources active:" << peerSources.size();
|
||||
}
|
||||
|
||||
void OpenAL2::unsubscribeOutput(uint& sid)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
peerSources.removeAll(sid);
|
||||
|
||||
if (sid) {
|
||||
if (alIsSource(sid)) {
|
||||
// stop playing, marks all buffers as processed
|
||||
alSourceStop(sid);
|
||||
// unqueue all buffers from the source
|
||||
ALint processed = 0;
|
||||
alGetSourcei(sid, AL_BUFFERS_PROCESSED, &processed);
|
||||
ALuint* bufids = new ALuint[processed];
|
||||
alSourceUnqueueBuffers(sid, processed, bufids);
|
||||
// delete all buffers
|
||||
alDeleteBuffers(processed, bufids);
|
||||
delete[] bufids;
|
||||
alDeleteSources(1, &sid);
|
||||
qDebug() << "Audio source" << sid << "deleted. Sources active:" << peerSources.size();
|
||||
} else {
|
||||
qWarning() << "Trying to delete invalid audio source" << sid;
|
||||
}
|
||||
|
||||
sid = 0;
|
||||
}
|
||||
|
||||
if (peerSources.isEmpty())
|
||||
cleanupOutput();
|
||||
}
|
||||
|
||||
void OpenAL2::startLoop()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_TRUE);
|
||||
}
|
||||
|
||||
void OpenAL2::stopLoop()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
|
||||
alSourceStop(alMainSource);
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_STOPPED) {
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
qreal OpenAL2::inputGain() const
|
||||
{
|
||||
return gain;
|
||||
}
|
||||
|
||||
qreal OpenAL2::inputGainFactor() const
|
||||
{
|
||||
return gainFactor;
|
||||
}
|
||||
|
||||
void OpenAL2::setInputGain(qreal dB)
|
||||
{
|
||||
gain = qBound(minInGain, dB, maxInGain);
|
||||
gainFactor = qPow(10.0, (gain / 20.0));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#ifndef OPENAL2_H
|
||||
#define OPENAL2_H
|
||||
|
||||
#include "openal.h"
|
||||
#include "src/audio/audio.h"
|
||||
|
||||
#include <atomic>
|
||||
|
@ -40,105 +41,41 @@ extern "C" {
|
|||
#include <filter_audio.h>
|
||||
}
|
||||
|
||||
class OpenAL2 : public Audio
|
||||
// needed because Ubuntu 14.04 lacks the AL_SOFT_source_latency extension
|
||||
#ifndef AL_SOFT_source_latency
|
||||
#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200
|
||||
#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201
|
||||
extern "C" typedef void(AL_APIENTRY* LPALGETSOURCEDVSOFT)(ALuint, ALenum, const ALdouble*);
|
||||
#endif
|
||||
|
||||
class OpenAL2 : public OpenAL
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenAL2();
|
||||
~OpenAL2();
|
||||
|
||||
qreal outputVolume() const;
|
||||
void setOutputVolume(qreal volume);
|
||||
|
||||
qreal minInputGain() const;
|
||||
void setMinInputGain(qreal dB);
|
||||
|
||||
qreal maxInputGain() const;
|
||||
void setMaxInputGain(qreal dB);
|
||||
|
||||
qreal inputGain() const;
|
||||
void setInputGain(qreal dB);
|
||||
|
||||
void reinitInput(const QString& inDevDesc);
|
||||
bool reinitOutput(const QString& outDevDesc);
|
||||
|
||||
bool isOutputReady() const;
|
||||
|
||||
QStringList outDeviceNames();
|
||||
QStringList inDeviceNames();
|
||||
|
||||
void subscribeOutput(uint& sourceId);
|
||||
void unsubscribeOutput(uint& sourceId);
|
||||
|
||||
void subscribeInput();
|
||||
void unsubscribeInput();
|
||||
|
||||
void startLoop();
|
||||
void stopLoop();
|
||||
void playMono16Sound(const QByteArray& data);
|
||||
void playMono16Sound(const QString& path);
|
||||
|
||||
void playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels,
|
||||
int sampleRate);
|
||||
|
||||
signals:
|
||||
void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels,
|
||||
uint32_t sampling_rate);
|
||||
|
||||
private:
|
||||
static void checkAlError() noexcept;
|
||||
static void checkAlcError(ALCdevice* device) noexcept;
|
||||
|
||||
bool autoInitInput();
|
||||
bool autoInitOutput();
|
||||
bool initInput(const QString& deviceName);
|
||||
bool initOutput(const QString& outDevDescr);
|
||||
void cleanupInput();
|
||||
void cleanupOutput();
|
||||
void playMono16SoundCleanup();
|
||||
void doAudio();
|
||||
qreal inputGainFactor() const;
|
||||
void doInput();
|
||||
void doOutput();
|
||||
bool loadOpenALExtensions(ALCdevice* dev);
|
||||
bool initOutputEchoCancel();
|
||||
|
||||
private:
|
||||
QThread* audioThread;
|
||||
mutable QMutex audioLock;
|
||||
|
||||
ALCdevice* alInDev;
|
||||
quint32 inSubscriptions;
|
||||
QTimer captureTimer, playMono16Timer;
|
||||
|
||||
ALCdevice* alOutDev;
|
||||
ALCcontext* alOutContext;
|
||||
ALCdevice* alProxyDev;
|
||||
ALCcontext* alProxyContext;
|
||||
ALuint alMainSource;
|
||||
ALuint alMainBuffer;
|
||||
ALuint alProxySource;
|
||||
ALuint alProxyBuffer;
|
||||
bool outputInitialized;
|
||||
bool echoCancelSupported = false;
|
||||
|
||||
QList<ALuint> peerSources;
|
||||
qreal gain;
|
||||
qreal gainFactor;
|
||||
qreal minInGain = -30;
|
||||
qreal maxInGain = 30;
|
||||
Filter_Audio* filterer = nullptr;
|
||||
LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT = nullptr;
|
||||
LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT = nullptr;
|
||||
|
||||
// needed because Ubuntu 14.04 lacks the AL_SOFT_source_latency extension
|
||||
#ifndef AL_SOFT_source_latency
|
||||
#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200
|
||||
#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201
|
||||
typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*);
|
||||
#endif
|
||||
|
||||
LPALGETSOURCEDVSOFT alGetSourcedvSOFT = nullptr;
|
||||
LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT = nullptr;
|
||||
};
|
||||
|
|
|
@ -562,5 +562,3 @@ void AVForm::retranslateUi()
|
|||
{
|
||||
Ui::AVForm::retranslateUi(this);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user