diff --git a/INSTALL.md b/INSTALL.md index 69425d97e..93dece974 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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 diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index aa92fbe35..f42c542ee 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -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; diff --git a/src/audio/backend/openal.cpp b/src/audio/backend/openal.cpp index 9da26584a..467e8acf5 100644 --- a/src/audio/backend/openal.cpp +++ b/src/audio/backend/openal.cpp @@ -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(); } diff --git a/src/audio/backend/openal.h b/src/audio/backend/openal.h index 40a3eef91..f32959d6c 100644 --- a/src/audio/backend/openal.h +++ b/src/audio/backend/openal.h @@ -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 outSources; + QList peerSources; qreal gain; qreal gainFactor; qreal minInGain = -30; diff --git a/src/audio/backend/openal2.cpp b/src/audio/backend/openal2.cpp index fc1f7742c..5aaab17f3 100644 --- a/src/audio/backend/openal2.cpp +++ b/src/audio/backend/openal2.cpp @@ -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(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(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( - alcGetProcAddress(dev, "alcLoopbackOpenDeviceSOFT")); + GET_PROC_ADDR(dev, alcLoopbackOpenDeviceSOFT); checkAlcError(dev); if (!alcLoopbackOpenDeviceSOFT) { qDebug() << "Failed to load alcLoopbackOpenDeviceSOFT function!"; return false; } - alcIsRenderFormatSupportedSOFT = reinterpret_cast( - alcGetProcAddress(dev, "alcIsRenderFormatSupportedSOFT")); + GET_PROC_ADDR(dev, alcIsRenderFormatSupportedSOFT); checkAlcError(dev); if (!alcIsRenderFormatSupportedSOFT) { qDebug() << "Failed to load alcIsRenderFormatSupportedSOFT function!"; return false; } - alGetSourcedvSOFT = - reinterpret_cast(alcGetProcAddress(dev, "alGetSourcedvSOFT")); + GET_PROC_ADDR(dev, alGetSourcedvSOFT); checkAlcError(dev); if (!alGetSourcedvSOFT) { qDebug() << "Failed to load alGetSourcedvSOFT function!"; return false; } - alcRenderSamplesSOFT = reinterpret_cast( - 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(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(std::numeric_limits::min(), qRound(buf[i] * inputGainFactor()), - std::numeric_limits::max()); + int ampPCM = qBound(std::numeric_limits::min(), + qRound(buf[i] * OpenAL::inputGainFactor()), + std::numeric_limits::max()); buf[i] = static_cast(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(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(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)); -} diff --git a/src/audio/backend/openal2.h b/src/audio/backend/openal2.h index 28d0eaf9c..d8b614397 100644 --- a/src/audio/backend/openal2.h +++ b/src/audio/backend/openal2.h @@ -21,6 +21,7 @@ #ifndef OPENAL2_H #define OPENAL2_H +#include "openal.h" #include "src/audio/audio.h" #include @@ -40,105 +41,41 @@ extern "C" { #include } -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 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; }; diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index cea87eea6..22800b6eb 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -562,5 +562,3 @@ void AVForm::retranslateUi() { Ui::AVForm::retranslateUi(this); } - -