1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

introduce a non-blocking AudioPlayer

Also moved private stuff to private class.
This commit is contained in:
Nils Fenner 2015-11-21 12:40:24 +01:00
parent 1fb9bce78c
commit b44ef6c596
No known key found for this signature in database
GPG Key ID: 9591A163FF9BE04C
2 changed files with 172 additions and 134 deletions

View File

@ -32,9 +32,10 @@
#include "src/core/coreav.h" #include "src/core/coreav.h"
#include <QDebug> #include <QDebug>
#include <QThread>
#include <QMutexLocker>
#include <QFile> #include <QFile>
#include <QMutexLocker>
#include <QThread>
#include <QWaitCondition>
#include <cassert> #include <cassert>
@ -55,17 +56,88 @@ public:
, alContext(nullptr) , alContext(nullptr)
, inputVolume(1.f) , inputVolume(1.f)
, outputVolume(1.f) , outputVolume(1.f)
, audioThread(new QThread())
, inputInitialized(false)
, outputInitialized(false)
{ {
audioThread->setObjectName("qTox Audio");
QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
} }
void initInput(const QString& inDevDescr);
bool initOutput(const QString& outDevDescr);
void cleanupInput();
void cleanupOutput();
public: public:
ALCdevice* alInDev; ALCdevice* alInDev;
ALCdevice* alOutDev; ALCdevice* alOutDev;
ALCcontext* alContext; ALCcontext* alContext;
// predefined sources
ALuint alMainSource; ALuint alMainSource;
qreal inputVolume; qreal inputVolume;
qreal outputVolume; qreal outputVolume;
QThread* audioThread;
QMutex audioLock;
Audio::PtrList inputSubscriptions;
Audio::PtrList outputSubscriptions;
bool inputInitialized;
bool outputInitialized;
};
/**
@class AudioPlayer
@brief Non-blocking audio player.
The audio data is played from start to finish (no streaming).
*/
class AudioPlayer : public QThread
{
public:
AudioPlayer(AudioPrivate* _q, const QByteArray& data)
: q(_q)
{
assert(q);
alGenBuffers(1, &buffer);
alBufferData(buffer, AL_FORMAT_MONO16, data.constData(), data.size(), 44100);
alSourcei(q->alMainSource, AL_BUFFER, buffer);
}
~AudioPlayer()
{
QMutexLocker(&q->audioLock);
alDeleteBuffers(1, &buffer);
if (q->outputSubscriptions.isEmpty())
q->cleanupOutput();
else
qDebug("Audio output not closed -> there are pending subscriptions.");
}
void run()
{
alSourceRewind(q->alMainSource);
alSourcePlay(q->alMainSource);
QMutexLocker locker(&playLock);
ALint state = AL_PLAYING;
while (state == AL_PLAYING) {
alGetSourcei(q->alMainSource, AL_SOURCE_STATE, &state);
waitPlaying.wait(&playLock, 2000);
}
}
public:
QMutex playLock;
QWaitCondition waitPlaying;
ALuint buffer;
private:
AudioPrivate* q;
}; };
Audio* Audio::instance{nullptr}; Audio* Audio::instance{nullptr};
@ -85,22 +157,15 @@ Audio& Audio::getInstance()
Audio::Audio() Audio::Audio()
: d(new AudioPrivate) : d(new AudioPrivate)
, audioThread(new QThread())
, mInputInitialized(false)
, mOutputInitialized(false)
{ {
audioThread->setObjectName("qTox Audio");
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
} }
Audio::~Audio() Audio::~Audio()
{ {
audioThread->exit(); d->audioThread->exit();
audioThread->wait(); d->audioThread->wait();
if (audioThread->isRunning()) d->cleanupInput();
audioThread->terminate(); d->cleanupOutput();
cleanupInput();
cleanupOutput();
} }
/** /**
@ -108,10 +173,10 @@ Start the audio thread for capture and playback.
*/ */
void Audio::startAudioThread() void Audio::startAudioThread()
{ {
moveToThread(audioThread); moveToThread(d->audioThread);
if (!audioThread->isRunning()) if (!d->audioThread->isRunning())
audioThread->start(); d->audioThread->start();
else else
qWarning("Audio thread already started -> ignored."); qWarning("Audio thread already started -> ignored.");
@ -122,7 +187,7 @@ Returns the current output volume, between 0 and 1
*/ */
qreal Audio::outputVolume() qreal Audio::outputVolume()
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
return d->outputVolume; return d->outputVolume;
} }
@ -131,7 +196,7 @@ The volume must be between 0 and 1
*/ */
void Audio::setOutputVolume(qreal volume) void Audio::setOutputVolume(qreal volume)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
d->outputVolume = volume; d->outputVolume = volume;
alSourcef(d->alMainSource, AL_GAIN, volume); alSourcef(d->alMainSource, AL_GAIN, volume);
@ -149,17 +214,31 @@ void Audio::setOutputVolume(qreal volume)
qreal Audio::inputVolume() qreal Audio::inputVolume()
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
return d->inputVolume; return d->inputVolume;
} }
void Audio::setInputVolume(qreal volume) void Audio::setInputVolume(qreal volume)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
d->inputVolume = volume; d->inputVolume = volume;
} }
void Audio::reinitInput(const QString& inDevDesc)
{
QMutexLocker locker(&d->audioLock);
d->cleanupInput();
d->initInput(inDevDesc);
}
bool Audio::reinitOutput(const QString& outDevDesc)
{
QMutexLocker locker(&d->audioLock);
d->cleanupOutput();
return d->initOutput(outDevDesc);
}
/** /**
@brief Subscribe to capture sound from the opened input device. @brief Subscribe to capture sound from the opened input device.
@ -167,14 +246,14 @@ If the input device is not open, it will be opened before capturing.
*/ */
void Audio::subscribeInput(const void* inListener) void Audio::subscribeInput(const void* inListener)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (!d->alInDev) if (!d->alInDev)
initInput(Settings::getInstance().getInDev()); d->initInput(Settings::getInstance().getInDev());
if (!inputSubscriptions.contains(inListener)) { if (!d->inputSubscriptions.contains(inListener)) {
inputSubscriptions << inListener; d->inputSubscriptions << inListener;
qDebug() << "Subscribed to audio input device [" << inputSubscriptions.size() << "subscriptions ]"; qDebug() << "Subscribed to audio input device [" << d->inputSubscriptions.size() << "subscriptions ]";
} }
} }
@ -185,54 +264,54 @@ If the input device has no more subscriptions, it will be closed.
*/ */
void Audio::unsubscribeInput(const void* inListener) void Audio::unsubscribeInput(const void* inListener)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (inListener && inputSubscriptions.size()) if (inListener && d->inputSubscriptions.size())
{ {
inputSubscriptions.removeOne(inListener); d->inputSubscriptions.removeOne(inListener);
qDebug() << "Unsubscribed from audio input device [" << inputSubscriptions.size() << "subscriptions left ]"; qDebug() << "Unsubscribed from audio input device [" << d->inputSubscriptions.size() << "subscriptions left ]";
} }
if (inputSubscriptions.isEmpty()) if (d->inputSubscriptions.isEmpty())
cleanupInput(); d->cleanupInput();
} }
void Audio::subscribeOutput(const void* outListener) void Audio::subscribeOutput(const void* outListener)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (!d->alOutDev) if (!d->alOutDev)
initOutput(Settings::getInstance().getOutDev()); d->initOutput(Settings::getInstance().getOutDev());
if (!outputSubscriptions.contains(outListener)) { if (!d->outputSubscriptions.contains(outListener)) {
outputSubscriptions << outListener; d->outputSubscriptions << outListener;
qDebug() << "Subscribed to audio output device [" << outputSubscriptions.size() << "subscriptions ]"; qDebug() << "Subscribed to audio output device [" << d->outputSubscriptions.size() << "subscriptions ]";
} }
} }
void Audio::unsubscribeOutput(const void* outListener) void Audio::unsubscribeOutput(const void* outListener)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (outListener && outputSubscriptions.size()) if (outListener && d->outputSubscriptions.size())
{ {
outputSubscriptions.removeOne(outListener); d->outputSubscriptions.removeOne(outListener);
qDebug() << "Unsubscribed from audio output device [" << outputSubscriptions.size() << " subscriptions left ]"; qDebug() << "Unsubscribed from audio output device [" << d->outputSubscriptions.size() << " subscriptions left ]";
} }
if (outputSubscriptions.isEmpty()) if (d->outputSubscriptions.isEmpty())
cleanupOutput(); d->cleanupOutput();
} }
void Audio::initInput(const QString& inDevDescr) void AudioPrivate::initInput(const QString& inDevDescr)
{ {
qDebug() << "Opening audio input" << inDevDescr; qDebug() << "Opening audio input" << inDevDescr;
mInputInitialized = false; inputInitialized = false;
if (inDevDescr == "none") if (inDevDescr == "none")
return; return;
assert(!d->alInDev); assert(!alInDev);
/// TODO: Try to actually detect if our audio source is stereo /// TODO: Try to actually detect if our audio source is stereo
int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
@ -260,10 +339,10 @@ void Audio::initInput(const QString& inDevDescr)
} }
} }
else else
d->alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(), alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),
sampleRate, stereoFlag, bufSize); sampleRate, stereoFlag, bufSize);
if (d->alInDev) if (alInDev)
qDebug() << "Opened audio input" << inDevDescr; qDebug() << "Opened audio input" << inDevDescr;
else else
qWarning() << "Cannot open input audio device" << inDevDescr; qWarning() << "Cannot open input audio device" << inDevDescr;
@ -273,9 +352,9 @@ void Audio::initInput(const QString& inDevDescr)
core->getAv()->resetCallSources(); // Force to regen each group call's sources core->getAv()->resetCallSources(); // Force to regen each group call's sources
// Restart the capture if necessary // Restart the capture if necessary
if (d->alInDev) if (alInDev)
{ {
alcCaptureStart(d->alInDev); alcCaptureStart(alInDev);
} }
else else
{ {
@ -284,7 +363,7 @@ void Audio::initInput(const QString& inDevDescr)
#endif #endif
} }
mInputInitialized = true; inputInitialized = true;
} }
/** /**
@ -292,15 +371,15 @@ void Audio::initInput(const QString& inDevDescr)
Open an audio output device Open an audio output device
*/ */
bool Audio::initOutput(const QString& outDevDescr) bool AudioPrivate::initOutput(const QString& outDevDescr)
{ {
qDebug() << "Opening audio output" << outDevDescr; qDebug() << "Opening audio output" << outDevDescr;
mOutputInitialized = false; outputInitialized = false;
if (outDevDescr == "none") if (outDevDescr == "none")
return true; return true;
assert(!d->alOutDev); assert(!alOutDev);
if (outDevDescr.isEmpty()) if (outDevDescr.isEmpty())
{ {
@ -327,15 +406,15 @@ bool Audio::initOutput(const QString& outDevDescr)
} }
} }
else else
d->alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str()); alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
if (d->alOutDev) if (alOutDev)
{ {
d->alContext = alcCreateContext(d->alOutDev, nullptr); alContext = alcCreateContext(alOutDev, nullptr);
if (alcMakeContextCurrent(d->alContext)) if (alcMakeContextCurrent(alContext))
{ {
alGenSources(1, &d->alMainSource); alGenSources(1, &alMainSource);
alSourcef(d->alMainSource, AL_GAIN, d->outputVolume); alSourcef(alMainSource, AL_GAIN, outputVolume);
} }
else else
{ {
@ -348,7 +427,7 @@ bool Audio::initOutput(const QString& outDevDescr)
if (core) if (core)
core->getAv()->resetCallSources(); // Force to regen each group call's sources core->getAv()->resetCallSources(); // Force to regen each group call's sources
mOutputInitialized = true; outputInitialized = true;
return true; return true;
} }
@ -361,35 +440,16 @@ Play a 44100Hz mono 16bit PCM sound
*/ */
void Audio::playMono16Sound(const QByteArray& data) void Audio::playMono16Sound(const QByteArray& data)
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (!d->alOutDev) if (!d->alOutDev)
initOutput(Settings::getInstance().getOutDev()); d->initOutput(Settings::getInstance().getOutDev());
alSourcei(d->alMainSource, AL_LOOPING, false);
alSourcef(d->alMainSource, AL_GAIN, d->outputVolume); alSourcef(d->alMainSource, AL_GAIN, d->outputVolume);
alSource3f(d->alMainSource, AL_POSITION, 0, 0, 0);
alSource3f(d->alMainSource, AL_VELOCITY, 0, 0, 0);
ALuint buffer; AudioPlayer *player = new AudioPlayer(d, data);
alGenBuffers(1, &buffer); connect(player, &AudioPlayer::finished, player, &AudioPlayer::deleteLater);
alBufferData(buffer, AL_FORMAT_MONO16, data.constData(), data.size(), 44100); player->start();
alSourcei(d->alMainSource, AL_BUFFER, buffer);
alSourceRewind(d->alMainSource);
alSourcePlay(d->alMainSource);
ALint state;
alGetSourcei(d->alMainSource, AL_SOURCE_STATE, &state);
while (state == AL_PLAYING) {
alGetSourcei(d->alMainSource, AL_SOURCE_STATE, &state);
}
alDeleteBuffers(1, &buffer);
if (outputSubscriptions.isEmpty())
cleanupOutput();
} }
/** /**
@ -422,8 +482,8 @@ Must be called from the audio thread, plays a group call's received audio
void Audio::playGroupAudio(int group, int peer, const int16_t* data, void Audio::playGroupAudio(int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate) unsigned samples, uint8_t channels, unsigned sample_rate)
{ {
assert(QThread::currentThread() == audioThread); assert(QThread::currentThread() == d->audioThread);
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
if (!CoreAV::groupCalls.contains(group)) if (!CoreAV::groupCalls.contains(group))
return; return;
@ -452,7 +512,7 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
void Audio::playAudioBuffer(quint32 alSource, const int16_t *data, int samples, unsigned channels, int sampleRate) void Audio::playAudioBuffer(quint32 alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
{ {
assert(channels == 1 || channels == 2); assert(channels == 1 || channels == 2);
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
ALuint bufid; ALuint bufid;
ALint processed = 0, queued = 16; ALint processed = 0, queued = 16;
@ -492,20 +552,20 @@ void Audio::playAudioBuffer(quint32 alSource, const int16_t *data, int samples,
Close active audio input device. Close active audio input device.
*/ */
void Audio::cleanupInput() void AudioPrivate::cleanupInput()
{ {
mInputInitialized = false; inputInitialized = false;
if (d->alInDev) if (alInDev)
{ {
#if (!FIX_SND_PCM_PREPARE_BUG) #if (!FIX_SND_PCM_PREPARE_BUG)
qDebug() << "stopping audio capture"; qDebug() << "stopping audio capture";
alcCaptureStop(d->alInDev); alcCaptureStop(alInDev);
#endif #endif
qDebug() << "Closing audio input"; qDebug() << "Closing audio input";
if (alcCaptureCloseDevice(d->alInDev) == ALC_TRUE) if (alcCaptureCloseDevice(alInDev) == ALC_TRUE)
d->alInDev = nullptr; alInDev = nullptr;
else else
qWarning() << "Failed to close input"; qWarning() << "Failed to close input";
} }
@ -516,24 +576,24 @@ void Audio::cleanupInput()
Close active audio output device Close active audio output device
*/ */
void Audio::cleanupOutput() void AudioPrivate::cleanupOutput()
{ {
mOutputInitialized = false; outputInitialized = false;
if (d->alOutDev) { if (alOutDev) {
qDebug() << "Closing audio output"; qDebug() << "Closing audio output";
alSourcei(d->alMainSource, AL_LOOPING, AL_FALSE); alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
alSourceStop(d->alMainSource); alSourceStop(alMainSource);
alDeleteSources(1, &d->alMainSource); alDeleteSources(1, &alMainSource);
if (!alcMakeContextCurrent(nullptr)) if (!alcMakeContextCurrent(nullptr))
qWarning("Failed to clear current audio context."); qWarning("Failed to clear current audio context.");
alcDestroyContext(d->alContext); alcDestroyContext(alContext);
d->alContext = nullptr; alContext = nullptr;
if (alcCloseDevice(d->alOutDev)) if (alcCloseDevice(alOutDev))
d->alOutDev = nullptr; alOutDev = nullptr;
else else
qWarning("Failed to close output."); qWarning("Failed to close output.");
} }
@ -544,8 +604,8 @@ Returns true if the input device is open and suscribed to
*/ */
bool Audio::isInputReady() bool Audio::isInputReady()
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
return d->alInDev && mInputInitialized; return d->alInDev && d->inputInitialized;
} }
/** /**
@ -553,8 +613,8 @@ Returns true if the output device is open
*/ */
bool Audio::isOutputReady() bool Audio::isOutputReady()
{ {
QMutexLocker locker(&mAudioLock); QMutexLocker locker(&d->audioLock);
return d->alOutDev && mOutputInitialized; return d->alOutDev && d->outputInitialized;
} }
const char* Audio::outDeviceNames() const char* Audio::outDeviceNames()
@ -604,9 +664,9 @@ Does nothing and return false on failure
*/ */
bool Audio::tryCaptureSamples(int16_t* buf, int samples) bool Audio::tryCaptureSamples(int16_t* buf, int samples)
{ {
QMutexLocker lock(&mAudioLock); QMutexLocker lock(&d->audioLock);
if (!(d->alInDev && mInputInitialized)) if (!(d->alInDev && d->inputInitialized))
return false; return false;
ALint curSamples=0; ALint curSamples=0;

View File

@ -40,6 +40,7 @@ class Audio : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
typedef QList<const void*> PtrList; typedef QList<const void*> PtrList;
public: public:
@ -54,18 +55,8 @@ public:
qreal inputVolume(); qreal inputVolume();
void setInputVolume(qreal volume); void setInputVolume(qreal volume);
inline void reinitInput(const QString& inDevDesc) void reinitInput(const QString& inDevDesc);
{ bool reinitOutput(const QString& outDevDesc);
QMutexLocker locker(&mAudioLock);
cleanupInput();
initInput(inDevDesc);
}
inline bool reinitOutput(const QString& outDevDesc)
{
QMutexLocker locker(&mAudioLock);
cleanupOutput();
return initOutput(outDevDesc);
}
bool isInputReady(); bool isInputReady();
bool isOutputReady(); bool isOutputReady();
@ -107,24 +98,11 @@ private:
Audio(); Audio();
~Audio(); ~Audio();
void initInput(const QString& inDevDescr);
bool initOutput(const QString& outDevDescr);
void cleanupInput();
void cleanupOutput();
private: private:
static Audio* instance; static Audio* instance;
private: private:
AudioPrivate* d; AudioPrivate* d;
private:
QThread* audioThread;
QMutex mAudioLock;
PtrList inputSubscriptions;
PtrList outputSubscriptions;
bool mInputInitialized;
bool mOutputInitialized;
}; };
#endif // AUDIO_H #endif // AUDIO_H