mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
feat(audio): split the audio interface from the backend library
This paves the way to support multiple audio backends
This commit is contained in:
parent
496f854897
commit
28c2298ad9
@ -179,6 +179,8 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|||||||
set(${PROJECT_NAME}_SOURCES
|
set(${PROJECT_NAME}_SOURCES
|
||||||
src/audio/audio.cpp
|
src/audio/audio.cpp
|
||||||
src/audio/audio.h
|
src/audio/audio.h
|
||||||
|
src/audio/backend/openal.cpp
|
||||||
|
src/audio/backend/openal.h
|
||||||
src/chatlog/chatlinecontent.cpp
|
src/chatlog/chatlinecontent.cpp
|
||||||
src/chatlog/chatlinecontent.h
|
src/chatlog/chatlinecontent.h
|
||||||
src/chatlog/chatlinecontentproxy.cpp
|
src/chatlog/chatlinecontentproxy.cpp
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2014-2015 by The qTox Project Contributors
|
Copyright © 2014-2017 by The qTox Project Contributors
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
@ -18,73 +18,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "src/core/core.h"
|
#include "src/audio/backend/openal.h"
|
||||||
#include "src/core/coreav.h"
|
|
||||||
#include "src/persistence/settings.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
|
||||||
#include <QMutexLocker>
|
|
||||||
#include <QPointer>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QWaitCondition>
|
|
||||||
#include <QtMath>
|
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Audio::Private
|
|
||||||
*
|
|
||||||
* @brief Encapsulates private audio framework from public qTox Audio API.
|
|
||||||
*/
|
|
||||||
class Audio::Private
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Private()
|
|
||||||
: minInputGain{-30.0}
|
|
||||||
, maxInputGain{30.0}
|
|
||||||
, gain{0.0}
|
|
||||||
, gainFactor{0.0}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static const ALchar* inDeviceNames()
|
|
||||||
{
|
|
||||||
return alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const ALchar* outDeviceNames()
|
|
||||||
{
|
|
||||||
return (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE)
|
|
||||||
? alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER)
|
|
||||||
: alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal inputGain() const
|
|
||||||
{
|
|
||||||
return gain;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal inputGainFactor() const
|
|
||||||
{
|
|
||||||
return gainFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setInputGain(qreal dB)
|
|
||||||
{
|
|
||||||
gain = qBound(minInputGain, dB, maxInputGain);
|
|
||||||
gainFactor = qPow(10.0, (gain / 20.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
qreal minInputGain;
|
|
||||||
qreal maxInputGain;
|
|
||||||
|
|
||||||
private:
|
|
||||||
qreal gain;
|
|
||||||
qreal gainFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Audio
|
* @class Audio
|
||||||
*
|
*
|
||||||
@ -120,605 +59,114 @@ private:
|
|||||||
* @var Audio::AUDIO_CHANNELS
|
* @var Audio::AUDIO_CHANNELS
|
||||||
* @brief Ideally, we'd auto-detect, but that's a sane default
|
* @brief Ideally, we'd auto-detect, but that's a sane default
|
||||||
*
|
*
|
||||||
* @var BUFFER_COUNT
|
* @fn qreal Audio::outputVolume() const
|
||||||
* @brief Number of buffers to use per audio source
|
* @brief Returns the current output volume (between 0 and 1)
|
||||||
|
*
|
||||||
|
* @fn void Audio::setOutputVolume(qreal volume)
|
||||||
|
* @brief Set the master output volume.
|
||||||
|
*
|
||||||
|
* @param[in] volume the master volume (between 0 and 1)
|
||||||
|
*
|
||||||
|
* @fn qreal Audio::minInputGain() const
|
||||||
|
* @brief The minimum gain value for an input device.
|
||||||
|
*
|
||||||
|
* @return minimum gain value in dB
|
||||||
|
*
|
||||||
|
* @fn void Audio::setMinInputGain(qreal dB)
|
||||||
|
* @brief Set the minimum allowed gain value in dB.
|
||||||
|
*
|
||||||
|
* @note Default is -30dB; usually you don't need to alter this value;
|
||||||
|
*
|
||||||
|
* @fn qreal Audio::maxInputGain() const
|
||||||
|
* @brief The maximum gain value for an input device.
|
||||||
|
*
|
||||||
|
* @return maximum gain value in dB
|
||||||
|
*
|
||||||
|
* @fn void Audio::setMaxInputGain(qreal dB)
|
||||||
|
* @brief Set the maximum allowed gain value in dB.
|
||||||
|
*
|
||||||
|
* @note Default is 30dB; usually you don't need to alter this value.
|
||||||
|
*
|
||||||
|
* @fn void Audio::subscribeInput()
|
||||||
|
* @brief Subscribe to capture sound from the opened input device.
|
||||||
|
*
|
||||||
|
* If the input device is not open, it will be opened before capturing.
|
||||||
|
*
|
||||||
|
* @fn void Audio::unsubscribeInput()
|
||||||
|
* @brief Unsubscribe from capturing from an opened input device.
|
||||||
|
*
|
||||||
|
* If the input device has no more subscriptions, it will be closed.
|
||||||
|
*
|
||||||
|
* @fn void Audio::playMono16Sound(const QString& path)
|
||||||
|
* @brief Play a 44100Hz mono 16bit PCM sound from a file
|
||||||
|
*
|
||||||
|
* @param[in] path the path to the sound file
|
||||||
|
*
|
||||||
|
* @fn void Audio::playMono16Sound(const QByteArray& data)
|
||||||
|
* @brief Play a 44100Hz mono 16bit PCM sound
|
||||||
|
*
|
||||||
|
* @param[in] data 44100Hz mono 16bit PCM data in host byte order
|
||||||
|
*
|
||||||
|
* @fn void Audio::playAudioBuffer(uint sourceId, const int16_t* data, int samples,
|
||||||
|
* unsigned channels, int sampleRate)
|
||||||
|
* @brief adds a number of audio frames to the play buffer
|
||||||
|
*
|
||||||
|
* @param[in] sourceId id obtained by subscribeOutput(uint &)
|
||||||
|
* @param[in] data 16bit mono or stereo PCM data with alternating channel
|
||||||
|
* mapping for stereo (LRLR)
|
||||||
|
* @param[in] samples number of samples per channel
|
||||||
|
* @param[in] channels number of channels, currently 1 or 2 is supported
|
||||||
|
* @param[in] sampleRate sample rate in Hertz
|
||||||
|
*
|
||||||
|
* @fn bool Audio::isOutputReady() const
|
||||||
|
* @brief check if the output is ready to play audio
|
||||||
|
*
|
||||||
|
* @return true if the output device is open, false otherwise
|
||||||
|
*
|
||||||
|
* @fn QStringList Audio::outDeviceNames()
|
||||||
|
* @brief Get the names of available output devices
|
||||||
|
*
|
||||||
|
* @return list of output devices
|
||||||
|
*
|
||||||
|
* @fn QStringList Audio::inDeviceNames()
|
||||||
|
* @brief Get the names of available input devices
|
||||||
|
*
|
||||||
|
* @return list of input devices
|
||||||
|
*
|
||||||
|
* @fn void Audio::subscribeOutput(uint& sid)
|
||||||
|
* @brief register a new output source
|
||||||
|
*
|
||||||
|
* param[out] sid contains the sourceId if source creation was successful,
|
||||||
|
* unchanged otherwise
|
||||||
|
*
|
||||||
|
* @fn void Audio::unsubscribeOutput(uint& sid)
|
||||||
|
* @brief unregisters an output source
|
||||||
|
*
|
||||||
|
* param[out] sid contains 0 if source deletion was successful,
|
||||||
|
* unchanged otherwise
|
||||||
|
*
|
||||||
|
* @fn void Audio::startLoop()
|
||||||
|
* @brief starts looping the sound played with playMono16Sound()
|
||||||
|
*
|
||||||
|
* @fn void Audio::stopLoop()
|
||||||
|
* @brief stops looping the sound played with playMono16Sound()
|
||||||
|
*
|
||||||
|
* @fn qreal Audio::inputGain() const
|
||||||
|
* @brief get the current input gain
|
||||||
|
*
|
||||||
|
* @return current input gain in dB
|
||||||
|
*
|
||||||
|
* @fn void Audio::setInputGain(qreal dB)
|
||||||
|
* @brief set the input gain
|
||||||
|
*
|
||||||
|
* @param[in] dB the new input gain in dB
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static const unsigned int BUFFER_COUNT = 16;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the singleton instance.
|
* @brief Returns the singleton instance.
|
||||||
*/
|
*/
|
||||||
Audio& Audio::getInstance()
|
Audio& Audio::getInstance()
|
||||||
{
|
{
|
||||||
static Audio instance;
|
static OpenAL instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Audio::Audio()
|
|
||||||
: d{new Private}
|
|
||||||
, audioThread{new QThread}
|
|
||||||
, alInDev{nullptr}
|
|
||||||
, inSubscriptions{0}
|
|
||||||
, alOutDev{nullptr}
|
|
||||||
, alOutContext{nullptr}
|
|
||||||
, alMainSource{0}
|
|
||||||
, alMainBuffer{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, &Audio::doCapture);
|
|
||||||
captureTimer.setInterval(AUDIO_FRAME_DURATION / 2);
|
|
||||||
captureTimer.setSingleShot(false);
|
|
||||||
captureTimer.start();
|
|
||||||
connect(&playMono16Timer, &QTimer::timeout, this, &Audio::playMono16SoundCleanup);
|
|
||||||
playMono16Timer.setSingleShot(true);
|
|
||||||
|
|
||||||
audioThread->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Audio::~Audio()
|
|
||||||
{
|
|
||||||
audioThread->exit();
|
|
||||||
audioThread->wait();
|
|
||||||
cleanupInput();
|
|
||||||
cleanupOutput();
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::checkAlError() noexcept
|
|
||||||
{
|
|
||||||
const ALenum al_err = alGetError();
|
|
||||||
if (al_err != AL_NO_ERROR)
|
|
||||||
qWarning("OpenAL error: %d", al_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::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 Audio::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 Audio::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 Audio::minInputGain() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
return d->minInputGain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the minimum allowed gain value in dB.
|
|
||||||
*
|
|
||||||
* @note Default is -30dB; usually you don't need to alter this value;
|
|
||||||
*/
|
|
||||||
void Audio::setMinInputGain(qreal dB)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
d->minInputGain = dB;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The maximum gain value for an input device.
|
|
||||||
*
|
|
||||||
* @return maximum gain value in dB
|
|
||||||
*/
|
|
||||||
qreal Audio::maxInputGain() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
return d->maxInputGain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the maximum allowed gain value in dB.
|
|
||||||
*
|
|
||||||
* @note Default is 30dB; usually you don't need to alter this value.
|
|
||||||
*/
|
|
||||||
void Audio::setMaxInputGain(qreal dB)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
d->maxInputGain = dB;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The dB gain value.
|
|
||||||
*
|
|
||||||
* @return the gain value in dB
|
|
||||||
*/
|
|
||||||
qreal Audio::inputGain() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
return d->inputGain();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the input gain dB level.
|
|
||||||
*/
|
|
||||||
void Audio::setInputGain(qreal dB)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
d->setInputGain(dB);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::reinitInput(const QString& inDevDesc)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
cleanupInput();
|
|
||||||
initInput(inDevDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Audio::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 Audio::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 Audio::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 Audio::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 Audio::autoInitOutput()
|
|
||||||
{
|
|
||||||
return alOutDev ? true : initOutput(Settings::getInstance().getOutDev());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Audio::initInput(const QString& deviceName)
|
|
||||||
{
|
|
||||||
if (!Settings::getInstance().getAudioInDevEnabled())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
qDebug() << "Opening audio input" << deviceName;
|
|
||||||
assert(!alInDev);
|
|
||||||
|
|
||||||
// TODO: Try to actually detect if our audio source is stereo
|
|
||||||
int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
|
||||||
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 QByteArray qDevName = deviceName.toUtf8();
|
|
||||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
|
||||||
alInDev = alcCaptureOpenDevice(tmpDevName, sampleRate, stereoFlag, bufSize);
|
|
||||||
|
|
||||||
// Restart the capture if necessary
|
|
||||||
if (!alInDev) {
|
|
||||||
qWarning() << "Failed to initialize audio input device:" << deviceName;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
d->setInputGain(Settings::getInstance().getAudioInGainDecibel());
|
|
||||||
|
|
||||||
qDebug() << "Opened audio input" << deviceName;
|
|
||||||
alcCaptureStart(alInDev);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Open an audio output device
|
|
||||||
*/
|
|
||||||
bool Audio::initOutput(const QString& deviceName)
|
|
||||||
{
|
|
||||||
outSources.clear();
|
|
||||||
|
|
||||||
outputInitialized = false;
|
|
||||||
if (!Settings::getInstance().getAudioOutDevEnabled())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
qDebug() << "Opening audio output" << deviceName;
|
|
||||||
assert(!alOutDev);
|
|
||||||
|
|
||||||
const QByteArray qDevName = deviceName.toUtf8();
|
|
||||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
|
||||||
alOutDev = alcOpenDevice(tmpDevName);
|
|
||||||
|
|
||||||
if (!alOutDev) {
|
|
||||||
qWarning() << "Cannot open output audio device" << deviceName;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Opened audio output" << deviceName;
|
|
||||||
alOutContext = alcCreateContext(alOutDev, nullptr);
|
|
||||||
checkAlcError(alOutDev);
|
|
||||||
|
|
||||||
if (!alcMakeContextCurrent(alOutContext)) {
|
|
||||||
qWarning() << "Cannot create output audio context";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
alGenSources(1, &alMainSource);
|
|
||||||
checkAlError();
|
|
||||||
|
|
||||||
// init master volume
|
|
||||||
alListenerf(AL_GAIN, Settings::getInstance().getOutVolume() * 0.01f);
|
|
||||||
checkAlError();
|
|
||||||
|
|
||||||
Core* core = Core::getInstance();
|
|
||||||
if (core) {
|
|
||||||
// reset each call's audio source
|
|
||||||
core->getAv()->invalidateCallSources();
|
|
||||||
}
|
|
||||||
|
|
||||||
outputInitialized = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Play a 44100Hz mono 16bit PCM sound from a file
|
|
||||||
*/
|
|
||||||
void Audio::playMono16Sound(const QString& path)
|
|
||||||
{
|
|
||||||
QFile sndFile(path);
|
|
||||||
sndFile.open(QIODevice::ReadOnly);
|
|
||||||
playMono16Sound(QByteArray(sndFile.readAll()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Play a 44100Hz mono 16bit PCM sound
|
|
||||||
*/
|
|
||||||
void Audio::playMono16Sound(const QByteArray& data)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
|
|
||||||
if (!autoInitOutput())
|
|
||||||
return;
|
|
||||||
|
|
||||||
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 Audio::playAudioBuffer(ALuint alSource, const int16_t* data, int samples, unsigned channels,
|
|
||||||
int sampleRate)
|
|
||||||
{
|
|
||||||
assert(channels == 1 || channels == 2);
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
|
|
||||||
if (!(alOutDev && outputInitialized))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ALuint bufids[BUFFER_COUNT];
|
|
||||||
ALint processed = 0, queued = 0;
|
|
||||||
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
|
|
||||||
alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued);
|
|
||||||
alSourcei(alSource, 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(alSource, 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(alSource, 1, bufids);
|
|
||||||
|
|
||||||
ALint state;
|
|
||||||
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
|
|
||||||
if (state != AL_PLAYING) {
|
|
||||||
alSourcePlay(alSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Close active audio input device.
|
|
||||||
*/
|
|
||||||
void Audio::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 Audio::cleanupOutput()
|
|
||||||
{
|
|
||||||
outputInitialized = false;
|
|
||||||
|
|
||||||
if (alOutDev) {
|
|
||||||
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);
|
|
||||||
alOutContext = nullptr;
|
|
||||||
|
|
||||||
qDebug() << "Closing audio output";
|
|
||||||
if (alcCloseDevice(alOutDev))
|
|
||||||
alOutDev = nullptr;
|
|
||||||
else
|
|
||||||
qWarning("Failed to close output.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Called after a mono16 sound stopped playing
|
|
||||||
*/
|
|
||||||
void Audio::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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Called on the captureTimer events to capture audio
|
|
||||||
*/
|
|
||||||
void Audio::doCapture()
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&audioLock);
|
|
||||||
|
|
||||||
if (!alInDev || !inSubscriptions)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ALint curSamples = 0;
|
|
||||||
alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples);
|
|
||||||
if (curSamples < AUDIO_FRAME_SAMPLE_COUNT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int16_t buf[AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS];
|
|
||||||
alcCaptureSamples(alInDev, buf, AUDIO_FRAME_SAMPLE_COUNT);
|
|
||||||
|
|
||||||
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i) {
|
|
||||||
// gain amplification with clipping to 16-bit boundaries
|
|
||||||
int ampPCM =
|
|
||||||
qBound<int>(std::numeric_limits<int16_t>::min(), qRound(buf[i] * d->inputGainFactor()),
|
|
||||||
std::numeric_limits<int16_t>::max());
|
|
||||||
|
|
||||||
buf[i] = static_cast<int16_t>(ampPCM);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit frameAvailable(buf, AUDIO_FRAME_SAMPLE_COUNT, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns true if the output device is open
|
|
||||||
*/
|
|
||||||
bool Audio::isOutputReady() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
return alOutDev && outputInitialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList Audio::outDeviceNames()
|
|
||||||
{
|
|
||||||
QStringList list;
|
|
||||||
const ALchar* pDeviceList = Private::outDeviceNames();
|
|
||||||
|
|
||||||
if (pDeviceList) {
|
|
||||||
while (*pDeviceList) {
|
|
||||||
int len = static_cast<int>(strlen(pDeviceList));
|
|
||||||
list << QString::fromUtf8(pDeviceList, len);
|
|
||||||
pDeviceList += len + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList Audio::inDeviceNames()
|
|
||||||
{
|
|
||||||
QStringList list;
|
|
||||||
const ALchar* pDeviceList = Private::inDeviceNames();
|
|
||||||
|
|
||||||
if (pDeviceList) {
|
|
||||||
while (*pDeviceList) {
|
|
||||||
int len = static_cast<int>(strlen(pDeviceList));
|
|
||||||
list << QString::fromUtf8(pDeviceList, len);
|
|
||||||
pDeviceList += len + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::subscribeOutput(ALuint& sid)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
|
|
||||||
if (!autoInitOutput()) {
|
|
||||||
qWarning("Failed to subscribe to audio output device.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alcMakeContextCurrent(alOutContext)) {
|
|
||||||
qWarning("Failed to activate output context.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
alGenSources(1, &sid);
|
|
||||||
assert(sid);
|
|
||||||
outSources << sid;
|
|
||||||
|
|
||||||
qDebug() << "Audio source" << sid << "created. Sources active:" << outSources.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::unsubscribeOutput(ALuint& sid)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
|
|
||||||
outSources.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:" << outSources.size();
|
|
||||||
} else {
|
|
||||||
qWarning() << "Trying to delete invalid audio source" << sid;
|
|
||||||
}
|
|
||||||
|
|
||||||
sid = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outSources.isEmpty())
|
|
||||||
cleanupOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::startLoop()
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&audioLock);
|
|
||||||
alSourcei(alMainSource, AL_LOOPING, AL_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2014-2015 by The qTox Project Contributors
|
Copyright © 2014-2017 by The qTox Project Contributors
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
@ -30,26 +30,10 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
|
||||||
#include <OpenAL/al.h>
|
|
||||||
#include <OpenAL/alc.h>
|
|
||||||
#else
|
|
||||||
#include <AL/al.h>
|
|
||||||
#include <AL/alc.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef ALC_ALL_DEVICES_SPECIFIER
|
|
||||||
// compatibility with older versions of OpenAL
|
|
||||||
#include <AL/alext.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class Audio : public QObject
|
class Audio : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
class Private;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum class Sound
|
enum class Sound
|
||||||
{
|
{
|
||||||
@ -73,85 +57,50 @@ public:
|
|||||||
}
|
}
|
||||||
static Audio& getInstance();
|
static Audio& getInstance();
|
||||||
|
|
||||||
qreal outputVolume() const;
|
virtual qreal outputVolume() const = 0;
|
||||||
void setOutputVolume(qreal volume);
|
virtual void setOutputVolume(qreal volume) = 0;
|
||||||
|
|
||||||
qreal minInputGain() const;
|
virtual qreal minInputGain() const = 0;
|
||||||
void setMinInputGain(qreal dB);
|
virtual void setMinInputGain(qreal dB) = 0;
|
||||||
|
|
||||||
qreal maxInputGain() const;
|
virtual qreal maxInputGain() const = 0;
|
||||||
void setMaxInputGain(qreal dB);
|
virtual void setMaxInputGain(qreal dB) = 0;
|
||||||
|
|
||||||
qreal inputGain() const;
|
virtual qreal inputGain() const = 0;
|
||||||
void setInputGain(qreal dB);
|
virtual void setInputGain(qreal dB) = 0;
|
||||||
|
|
||||||
void reinitInput(const QString& inDevDesc);
|
virtual void reinitInput(const QString& inDevDesc) = 0;
|
||||||
bool reinitOutput(const QString& outDevDesc);
|
virtual bool reinitOutput(const QString& outDevDesc) = 0;
|
||||||
|
|
||||||
bool isOutputReady() const;
|
virtual bool isOutputReady() const = 0;
|
||||||
|
|
||||||
static QStringList outDeviceNames();
|
virtual QStringList outDeviceNames() = 0;
|
||||||
static QStringList inDeviceNames();
|
virtual QStringList inDeviceNames() = 0;
|
||||||
|
|
||||||
void subscribeOutput(ALuint& sid);
|
virtual void subscribeOutput(uint& sourceId) = 0;
|
||||||
void unsubscribeOutput(ALuint& sid);
|
virtual void unsubscribeOutput(uint& sourceId) = 0;
|
||||||
|
|
||||||
void subscribeInput();
|
virtual void subscribeInput() = 0;
|
||||||
void unsubscribeInput();
|
virtual void unsubscribeInput() = 0;
|
||||||
|
|
||||||
void startLoop();
|
virtual void startLoop() = 0;
|
||||||
void stopLoop();
|
virtual void stopLoop() = 0;
|
||||||
void playMono16Sound(const QByteArray& data);
|
virtual void playMono16Sound(const QByteArray& data) = 0;
|
||||||
void playMono16Sound(const QString& path);
|
virtual void playMono16Sound(const QString& path) = 0;
|
||||||
|
|
||||||
void playAudioBuffer(ALuint alSource, const int16_t* data, int samples, unsigned channels,
|
virtual void playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels,
|
||||||
int sampleRate);
|
int sampleRate) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Public default audio settings
|
// Public default audio settings
|
||||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
|
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
|
||||||
static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
|
static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
|
||||||
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
|
static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
|
||||||
static constexpr uint32_t AUDIO_CHANNELS = 2;
|
static constexpr uint32_t AUDIO_CHANNELS = 2;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels,
|
void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels,
|
||||||
uint32_t sampling_rate);
|
uint32_t sampling_rate);
|
||||||
|
|
||||||
private:
|
|
||||||
Audio();
|
|
||||||
~Audio();
|
|
||||||
|
|
||||||
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 doCapture();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Private* d;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QThread* audioThread;
|
|
||||||
mutable QMutex audioLock;
|
|
||||||
|
|
||||||
ALCdevice* alInDev;
|
|
||||||
quint32 inSubscriptions;
|
|
||||||
QTimer captureTimer, playMono16Timer;
|
|
||||||
|
|
||||||
ALCdevice* alOutDev;
|
|
||||||
ALCcontext* alOutContext;
|
|
||||||
ALuint alMainSource;
|
|
||||||
ALuint alMainBuffer;
|
|
||||||
bool outputInitialized;
|
|
||||||
|
|
||||||
QList<ALuint> outSources;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AUDIO_H
|
#endif // AUDIO_H
|
||||||
|
634
src/audio/backend/openal.cpp
Normal file
634
src/audio/backend/openal.cpp
Normal file
@ -0,0 +1,634 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2014-2017 by The qTox Project Contributors
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
qTox is libre software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
qTox is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "openal.h"
|
||||||
|
#include "src/core/core.h"
|
||||||
|
#include "src/core/coreav.h"
|
||||||
|
#include "src/persistence/settings.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QWaitCondition>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OpenAL
|
||||||
|
* @brief Provides the OpenAL audio backend
|
||||||
|
*
|
||||||
|
* @var BUFFER_COUNT
|
||||||
|
* @brief Number of buffers to use per audio source
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const unsigned int BUFFER_COUNT = 16;
|
||||||
|
|
||||||
|
OpenAL::OpenAL() :
|
||||||
|
audioThread{new QThread}
|
||||||
|
, alInDev{nullptr}
|
||||||
|
, inSubscriptions{0}
|
||||||
|
, alOutDev{nullptr}
|
||||||
|
, alOutContext{nullptr}
|
||||||
|
, alMainSource{0}
|
||||||
|
, alMainBuffer{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, &OpenAL::doCapture);
|
||||||
|
captureTimer.setInterval(AUDIO_FRAME_DURATION / 2);
|
||||||
|
captureTimer.setSingleShot(false);
|
||||||
|
captureTimer.start();
|
||||||
|
connect(&playMono16Timer, &QTimer::timeout, this, &OpenAL::playMono16SoundCleanup);
|
||||||
|
playMono16Timer.setSingleShot(true);
|
||||||
|
|
||||||
|
audioThread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenAL::~OpenAL()
|
||||||
|
{
|
||||||
|
audioThread->exit();
|
||||||
|
audioThread->wait();
|
||||||
|
cleanupInput();
|
||||||
|
cleanupOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::checkAlError() noexcept
|
||||||
|
{
|
||||||
|
const ALenum al_err = alGetError();
|
||||||
|
if (al_err != AL_NO_ERROR)
|
||||||
|
qWarning("OpenAL error: %d", al_err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::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 OpenAL::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 OpenAL::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 OpenAL::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 OpenAL::setMinInputGain(qreal dB)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
minInGain = dB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The maximum gain value for an input device.
|
||||||
|
*
|
||||||
|
* @return maximum gain value in dB
|
||||||
|
*/
|
||||||
|
qreal OpenAL::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 OpenAL::setMaxInputGain(qreal dB)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
maxInGain = dB;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::reinitInput(const QString& inDevDesc)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
cleanupInput();
|
||||||
|
initInput(inDevDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenAL::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 OpenAL::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 OpenAL::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 OpenAL::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 OpenAL::autoInitOutput()
|
||||||
|
{
|
||||||
|
return alOutDev ? true : initOutput(Settings::getInstance().getOutDev());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenAL::initInput(const QString& deviceName)
|
||||||
|
{
|
||||||
|
if (!Settings::getInstance().getAudioInDevEnabled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qDebug() << "Opening audio input" << deviceName;
|
||||||
|
assert(!alInDev);
|
||||||
|
|
||||||
|
// TODO: Try to actually detect if our audio source is stereo
|
||||||
|
int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
||||||
|
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 QByteArray qDevName = deviceName.toUtf8();
|
||||||
|
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||||
|
alInDev = alcCaptureOpenDevice(tmpDevName, sampleRate, stereoFlag, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Open an audio output device
|
||||||
|
*/
|
||||||
|
bool OpenAL::initOutput(const QString& deviceName)
|
||||||
|
{
|
||||||
|
outSources.clear();
|
||||||
|
|
||||||
|
outputInitialized = false;
|
||||||
|
if (!Settings::getInstance().getAudioOutDevEnabled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qDebug() << "Opening audio output" << deviceName;
|
||||||
|
assert(!alOutDev);
|
||||||
|
|
||||||
|
const QByteArray qDevName = deviceName.toUtf8();
|
||||||
|
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||||
|
alOutDev = alcOpenDevice(tmpDevName);
|
||||||
|
|
||||||
|
if (!alOutDev) {
|
||||||
|
qWarning() << "Cannot open output audio device" << deviceName;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Opened audio output" << deviceName;
|
||||||
|
alOutContext = alcCreateContext(alOutDev, nullptr);
|
||||||
|
checkAlcError(alOutDev);
|
||||||
|
|
||||||
|
if (!alcMakeContextCurrent(alOutContext)) {
|
||||||
|
qWarning() << "Cannot create output audio context";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alGenSources(1, &alMainSource);
|
||||||
|
checkAlError();
|
||||||
|
|
||||||
|
// init master volume
|
||||||
|
alListenerf(AL_GAIN, Settings::getInstance().getOutVolume() * 0.01f);
|
||||||
|
checkAlError();
|
||||||
|
|
||||||
|
Core* core = Core::getInstance();
|
||||||
|
if (core) {
|
||||||
|
// reset each call's audio source
|
||||||
|
core->getAv()->invalidateCallSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 OpenAL::playMono16Sound(const QString& path)
|
||||||
|
{
|
||||||
|
QFile sndFile(path);
|
||||||
|
sndFile.open(QIODevice::ReadOnly);
|
||||||
|
playMono16Sound(QByteArray(sndFile.readAll()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Play a 44100Hz mono 16bit PCM sound
|
||||||
|
*/
|
||||||
|
void OpenAL::playMono16Sound(const QByteArray& data)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
|
||||||
|
if (!autoInitOutput())
|
||||||
|
return;
|
||||||
|
|
||||||
|
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 OpenAL::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;
|
||||||
|
|
||||||
|
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 OpenAL::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 OpenAL::cleanupOutput()
|
||||||
|
{
|
||||||
|
outputInitialized = false;
|
||||||
|
|
||||||
|
if (alOutDev) {
|
||||||
|
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);
|
||||||
|
alOutContext = nullptr;
|
||||||
|
|
||||||
|
qDebug() << "Closing audio output";
|
||||||
|
if (alcCloseDevice(alOutDev))
|
||||||
|
alOutDev = nullptr;
|
||||||
|
else
|
||||||
|
qWarning("Failed to close output.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called after a mono16 sound stopped playing
|
||||||
|
*/
|
||||||
|
void OpenAL::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 Called on the captureTimer events to capture audio
|
||||||
|
*/
|
||||||
|
void OpenAL::doCapture()
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&audioLock);
|
||||||
|
|
||||||
|
if (!alInDev || !inSubscriptions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ALint curSamples = 0;
|
||||||
|
alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples);
|
||||||
|
if (curSamples < AUDIO_FRAME_SAMPLE_COUNT)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int16_t buf[AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS];
|
||||||
|
alcCaptureSamples(alInDev, buf, AUDIO_FRAME_SAMPLE_COUNT);
|
||||||
|
|
||||||
|
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i) {
|
||||||
|
// gain amplification with clipping to 16-bit boundaries
|
||||||
|
int ampPCM =
|
||||||
|
qBound<int>(std::numeric_limits<int16_t>::min(), qRound(buf[i] * inputGainFactor()),
|
||||||
|
std::numeric_limits<int16_t>::max());
|
||||||
|
|
||||||
|
buf[i] = static_cast<int16_t>(ampPCM);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit Audio::frameAvailable(buf, AUDIO_FRAME_SAMPLE_COUNT, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns true if the output device is open
|
||||||
|
*/
|
||||||
|
bool OpenAL::isOutputReady() const
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
return alOutDev && outputInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList OpenAL::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 OpenAL::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 OpenAL::subscribeOutput(uint& sid)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
|
||||||
|
if (!autoInitOutput()) {
|
||||||
|
qWarning("Failed to subscribe to audio output device.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alcMakeContextCurrent(alOutContext)) {
|
||||||
|
qWarning("Failed to activate output context.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alGenSources(1, &sid);
|
||||||
|
assert(sid);
|
||||||
|
outSources << sid;
|
||||||
|
|
||||||
|
qDebug() << "Audio source" << sid << "created. Sources active:" << outSources.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::unsubscribeOutput(uint& sid)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
|
||||||
|
outSources.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:" << outSources.size();
|
||||||
|
} else {
|
||||||
|
qWarning() << "Trying to delete invalid audio source" << sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
sid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outSources.isEmpty())
|
||||||
|
cleanupOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::startLoop()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&audioLock);
|
||||||
|
alSourcei(alMainSource, AL_LOOPING, AL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::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 OpenAL::inputGain() const
|
||||||
|
{
|
||||||
|
return gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal OpenAL::inputGainFactor() const
|
||||||
|
{
|
||||||
|
return gainFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL::setInputGain(qreal dB)
|
||||||
|
{
|
||||||
|
gain = qBound(minInGain, dB, maxInGain);
|
||||||
|
gainFactor = qPow(10.0, (gain / 20.0));
|
||||||
|
}
|
127
src/audio/backend/openal.h
Normal file
127
src/audio/backend/openal.h
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2014-2017 by The qTox Project Contributors
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
qTox is libre software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
qTox is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef OPENAL_H
|
||||||
|
#define OPENAL_H
|
||||||
|
|
||||||
|
#include "src/audio/audio.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <OpenAL/al.h>
|
||||||
|
#include <OpenAL/alc.h>
|
||||||
|
#else
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ALC_ALL_DEVICES_SPECIFIER
|
||||||
|
// compatibility with older versions of OpenAL
|
||||||
|
#include <AL/alext.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class OpenAL : public Audio
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenAL();
|
||||||
|
~OpenAL();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 doCapture();
|
||||||
|
qreal inputGainFactor() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QThread* audioThread;
|
||||||
|
mutable QMutex audioLock;
|
||||||
|
|
||||||
|
ALCdevice* alInDev;
|
||||||
|
quint32 inSubscriptions;
|
||||||
|
QTimer captureTimer, playMono16Timer;
|
||||||
|
|
||||||
|
ALCdevice* alOutDev;
|
||||||
|
ALCcontext* alOutContext;
|
||||||
|
ALuint alMainSource;
|
||||||
|
ALuint alMainBuffer;
|
||||||
|
bool outputInitialized;
|
||||||
|
|
||||||
|
QList<ALuint> outSources;
|
||||||
|
qreal gain;
|
||||||
|
qreal gainFactor;
|
||||||
|
qreal minInGain = -30;
|
||||||
|
qreal maxInGain = 30;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // OPENAL_H
|
@ -424,7 +424,7 @@ int AVForm::getModeSize(VideoMode mode)
|
|||||||
void AVForm::getAudioInDevices()
|
void AVForm::getAudioInDevices()
|
||||||
{
|
{
|
||||||
QStringList deviceNames;
|
QStringList deviceNames;
|
||||||
deviceNames << tr("Disabled") << Audio::inDeviceNames();
|
deviceNames << tr("Disabled") << Audio::getInstance().inDeviceNames();
|
||||||
|
|
||||||
inDevCombobox->blockSignals(true);
|
inDevCombobox->blockSignals(true);
|
||||||
inDevCombobox->clear();
|
inDevCombobox->clear();
|
||||||
@ -443,7 +443,7 @@ void AVForm::getAudioInDevices()
|
|||||||
void AVForm::getAudioOutDevices()
|
void AVForm::getAudioOutDevices()
|
||||||
{
|
{
|
||||||
QStringList deviceNames;
|
QStringList deviceNames;
|
||||||
deviceNames << tr("Disabled") << Audio::outDeviceNames();
|
deviceNames << tr("Disabled") << Audio::getInstance().outDeviceNames();
|
||||||
|
|
||||||
outDevCombobox->blockSignals(true);
|
outDevCombobox->blockSignals(true);
|
||||||
outDevCombobox->clear();
|
outDevCombobox->clear();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user