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

reimplement audio in/out subscription concept

This commit is contained in:
Nils Fenner 2015-12-10 06:48:28 +01:00
parent 27bfade9e1
commit 0615c7c3c6
No known key found for this signature in database
GPG Key ID: 9591A163FF9BE04C
7 changed files with 135 additions and 133 deletions

View File

@ -152,6 +152,8 @@ public:
class AudioPrivate class AudioPrivate
{ {
typedef QList<Audio::SID> ALSources;
public: public:
AudioPrivate() AudioPrivate()
: audioThread(new QThread) : audioThread(new QThread)
@ -162,6 +164,7 @@ public:
, outputVolume(1.f) , outputVolume(1.f)
, inputInitialized(false) , inputInitialized(false)
, outputInitialized(false) , outputInitialized(false)
, inSubscriptions(0)
{ {
audioThread->setObjectName("qTox Audio"); audioThread->setObjectName("qTox Audio");
QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
@ -196,8 +199,8 @@ public:
bool inputInitialized; bool inputInitialized;
bool outputInitialized; bool outputInitialized;
Audio::PtrList inputSubscriptions; quint32 inSubscriptions;
Audio::PtrList outputSubscriptions; ALSources outSources;
QPointer<AudioMeter> audioMeter; QPointer<AudioMeter> audioMeter;
}; };
@ -370,17 +373,15 @@ bool Audio::reinitOutput(const QString& outDevDesc)
If the input device is not open, it will be opened before capturing. If the input device is not open, it will be opened before capturing.
*/ */
void Audio::subscribeInput(const void* inListener) void Audio::subscribeInput()
{ {
QMutexLocker locker(&d->audioLock); QMutexLocker locker(&d->audioLock);
if (!d->alInDev) if (!d->alInDev)
d->initInput(Settings::getInstance().getInDev()); d->initInput(Settings::getInstance().getInDev());
if (!d->inputSubscriptions.contains(inListener)) { d->inSubscriptions++;
d->inputSubscriptions << inListener; qDebug() << "Subscribed to audio input device [" << d->inSubscriptions << "subscriptions ]";
qDebug() << "Subscribed to audio input device [" << d->inputSubscriptions.size() << "subscriptions ]";
}
} }
/** /**
@ -388,47 +389,19 @@ void Audio::subscribeInput(const void* inListener)
If the input device has no more subscriptions, it will be closed. If the input device has no more subscriptions, it will be closed.
*/ */
void Audio::unsubscribeInput(const void* inListener) void Audio::unsubscribeInput()
{ {
QMutexLocker locker(&d->audioLock); QMutexLocker locker(&d->audioLock);
if (inListener && d->inputSubscriptions.size()) if (d->inSubscriptions > 0) {
{ d->inSubscriptions--;
d->inputSubscriptions.removeOne(inListener); qDebug() << "Unsubscribed from audio input device [" << d->inSubscriptions << "subscriptions left ]";
qDebug() << "Unsubscribed from audio input device [" << d->inputSubscriptions.size() << "subscriptions left ]";
} }
if (d->inputSubscriptions.isEmpty()) if (!d->inSubscriptions)
d->cleanupInput(); d->cleanupInput();
} }
void Audio::subscribeOutput(const void* outListener)
{
QMutexLocker locker(&d->audioLock);
if (!d->alOutDev)
d->initOutput(Settings::getInstance().getOutDev());
if (!d->outputSubscriptions.contains(outListener)) {
d->outputSubscriptions << outListener;
qDebug() << "Subscribed to audio output device [" << d->outputSubscriptions.size() << "subscriptions ]";
}
}
void Audio::unsubscribeOutput(const void* outListener)
{
QMutexLocker locker(&d->audioLock);
if (outListener && d->outputSubscriptions.size())
{
d->outputSubscriptions.removeOne(outListener);
qDebug() << "Unsubscribed from audio output device [" << d->outputSubscriptions.size() << " subscriptions left ]";
}
if (d->outputSubscriptions.isEmpty())
d->cleanupOutput();
}
void AudioPrivate::initInput(const QString& inDevDescr) void AudioPrivate::initInput(const QString& inDevDescr)
{ {
qDebug() << "Opening audio input" << inDevDescr; qDebug() << "Opening audio input" << inDevDescr;
@ -473,10 +446,6 @@ void AudioPrivate::initInput(const QString& inDevDescr)
else else
qWarning() << "Cannot open input audio device" << inDevDescr; qWarning() << "Cannot open input audio device" << inDevDescr;
Core* core = Core::getInstance();
if (core)
core->getAv()->resetCallSources(); // Force to regen each group call's sources
// Restart the capture if necessary // Restart the capture if necessary
if (alInDev) if (alInDev)
{ {
@ -500,6 +469,7 @@ Open an audio output device
bool AudioPrivate::initOutput(const QString& outDevDescr) bool AudioPrivate::initOutput(const QString& outDevDescr)
{ {
qDebug() << "Opening audio output" << outDevDescr; qDebug() << "Opening audio output" << outDevDescr;
outSources.clear();
outputInitialized = false; outputInitialized = false;
if (outDevDescr == "none") if (outDevDescr == "none")
@ -508,29 +478,29 @@ bool AudioPrivate::initOutput(const QString& outDevDescr)
assert(!alOutDev); assert(!alOutDev);
if (outDevDescr.isEmpty()) if (outDevDescr.isEmpty())
{
// Attempt to default to the first available audio device.
const ALchar *pDeviceList;
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE)
pDeviceList = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
else
pDeviceList = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
if (pDeviceList)
{ {
// Attempt to default to the first available audio device. alOutDev = alcOpenDevice(pDeviceList);
const ALchar *pDeviceList; int len = strlen(pDeviceList);
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE) #ifdef Q_OS_WIN
pDeviceList = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); QString outDev = QString::fromUtf8(pDeviceList, len);
else #else
pDeviceList = alcGetString(NULL, ALC_DEVICE_SPECIFIER); QString outDev = QString::fromLocal8Bit(pDeviceList, len);
if (pDeviceList) #endif
{ Settings::getInstance().setOutDev(outDev);
alOutDev = alcOpenDevice(pDeviceList);
int len = strlen(pDeviceList);
#ifdef Q_OS_WIN
QString outDev = QString::fromUtf8(pDeviceList, len);
#else
QString outDev = QString::fromLocal8Bit(pDeviceList, len);
#endif
Settings::getInstance().setOutDev(outDev);
}
else
{
alOutDev = alcOpenDevice(nullptr);
}
} }
else
{
alOutDev = alcOpenDevice(nullptr);
}
}
else else
alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str()); alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
@ -551,7 +521,7 @@ bool AudioPrivate::initOutput(const QString& outDevDescr)
Core* core = Core::getInstance(); Core* core = Core::getInstance();
if (core) if (core)
core->getAv()->resetCallSources(); // Force to regen each group call's sources core->getAv()->invalidateCallSources(); // Force to regen each group call's sources
outputInitialized = true; outputInitialized = true;
return true; return true;
@ -577,7 +547,7 @@ void Audio::playMono16Sound(const QByteArray& data)
connect(player, &AudioPlayer::finished, [=]() { connect(player, &AudioPlayer::finished, [=]() {
QMutexLocker locker(&d->audioLock); QMutexLocker locker(&d->audioLock);
if (d->outputSubscriptions.isEmpty()) if (d->outSources.isEmpty())
d->cleanupOutput(); d->cleanupOutput();
else else
qDebug("Audio output not closed -> there are pending subscriptions."); qDebug("Audio output not closed -> there are pending subscriptions.");
@ -648,6 +618,9 @@ void Audio::playAudioBuffer(quint32 alSource, const int16_t *data, int samples,
assert(channels == 1 || channels == 2); assert(channels == 1 || channels == 2);
QMutexLocker locker(&d->audioLock); QMutexLocker locker(&d->audioLock);
if (!(d->alOutDev && d->outputInitialized))
return;
ALuint bufid; ALuint bufid;
ALint processed = 0, queued = 16; ALint processed = 0, queued = 16;
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
@ -767,19 +740,41 @@ const char* Audio::inDeviceNames()
return alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); return alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
} }
void Audio::createSource(quint32* sid) void Audio::subscribeOutput(SID& sid)
{ {
alGenSources(1, sid); QMutexLocker locker(&d->audioLock);
alSourcef(*sid, AL_GAIN, 1.f);
if (!d->alOutDev)
d->initOutput(Settings::getInstance().getOutDev());
alGenSources(1, &sid);
assert(sid);
alSourcef(sid, AL_GAIN, 1.f);
d->outSources << sid;
qDebug() << "Audio source" << sid << "created. Sources active:"
<< d->outSources.size();
} }
void Audio::deleteSource(quint32 sid) void Audio::unsubscribeOutput(SID& sid)
{ {
if (alIsSource(sid)) { QMutexLocker locker(&d->audioLock);
alDeleteSources(1, &sid);
} else { if (sid) {
qWarning() << "Trying to delete invalid audio source" << sid; if (alIsSource(sid)) {
alDeleteSources(1, &sid);
qDebug() << "Audio source" << sid << "deleted. Sources active:"
<< d->outSources.size();
} else {
qWarning() << "Trying to delete invalid audio source" << sid;
}
sid = 0;
} }
d->outSources.removeAll(sid);
if (d->outSources.isEmpty())
d->cleanupOutput();
} }
void Audio::startLoop() void Audio::startLoop()

View File

@ -21,10 +21,10 @@
#ifndef AUDIO_H #ifndef AUDIO_H
#define AUDIO_H #define AUDIO_H
#include <QObject>
#include <QMutexLocker>
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <QObject>
#include <QWaitCondition> #include <QWaitCondition>
struct Tox; struct Tox;
@ -44,7 +44,7 @@ class Audio : public QObject
Q_OBJECT Q_OBJECT
public: public:
typedef QList<const void*> PtrList; typedef quint32 SID;
public: public:
static Audio& getInstance(); static Audio& getInstance();
@ -68,8 +68,8 @@ public:
static const char* outDeviceNames(); static const char* outDeviceNames();
static const char* inDeviceNames(); static const char* inDeviceNames();
void createSource(quint32* sid); void subscribeOutput(SID& sid);
void deleteSource(quint32 sid); void unsubscribeOutput(SID& sid);
void startLoop(); void startLoop();
void stopLoop(); void stopLoop();
@ -88,10 +88,8 @@ public:
#endif #endif
public slots: public slots:
void subscribeInput(const void* inListener); void subscribeInput();
void subscribeOutput(const void* outListener); void unsubscribeInput();
void unsubscribeInput(const void* inListener);
void unsubscribeOutput(const void* outListener);
void playGroupAudio(int group, int peer, const int16_t* data, void 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);

View File

@ -470,24 +470,16 @@ bool CoreAV::isGroupAvEnabled(int groupId) const
return tox_group_get_type(Core::getInstance()->tox, groupId) == TOX_GROUPCHAT_TYPE_AV; return tox_group_get_type(Core::getInstance()->tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
} }
void CoreAV::resetCallSources() void CoreAV::invalidateCallSources()
{ {
for (ToxGroupCall& call : groupCalls) for (ToxGroupCall& call : groupCalls)
{ {
if (call.alSource) call.alSource = 0;
{
Audio::getInstance().deleteSource(call.alSource);
Audio::getInstance().createSource(&call.alSource);
}
} }
for (ToxFriendCall& call : calls) for (ToxFriendCall& call : calls)
{ {
if (call.alSource) call.alSource = 0;
{
Audio::getInstance().deleteSource(call.alSource);
Audio::getInstance().createSource(&call.alSource);
}
} }
} }
@ -646,10 +638,11 @@ void CoreAV::audioFrameCallback(ToxAV *, uint32_t friendNum, const int16_t *pcm,
if (call.muteVol) if (call.muteVol)
return; return;
Audio& audio = Audio::getInstance();
if (!call.alSource) if (!call.alSource)
Audio::getInstance().createSource(&call.alSource); audio.subscribeOutput(call.alSource);
Audio::getInstance().playAudioBuffer(call.alSource, pcm, sampleCount, channels, samplingRate); audio.playAudioBuffer(call.alSource, pcm, sampleCount, channels, samplingRate);
} }
void CoreAV::videoFrameCallback(ToxAV *, uint32_t friendNum, uint16_t w, uint16_t h, void CoreAV::videoFrameCallback(ToxAV *, uint32_t friendNum, uint16_t w, uint16_t h,

View File

@ -57,7 +57,7 @@ public:
bool sendGroupCallAudio(int groupNum); bool sendGroupCallAudio(int groupNum);
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
void resetCallSources(); ///< Forces to regenerate each call's audio sources void invalidateCallSources(); ///< Forces to regenerate each call's audio sources
void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this. void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this.
void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread. void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.

View File

@ -21,8 +21,8 @@ ToxCall::ToxCall(uint32_t CallId)
sendAudioTimer->setSingleShot(true); sendAudioTimer->setSingleShot(true);
Audio& audio = Audio::getInstance(); Audio& audio = Audio::getInstance();
audio.subscribeInput(this); audio.subscribeInput();
audio.subscribeOutput(this); audio.subscribeOutput(alSource);
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
if (Settings::getInstance().getFilterAudio()) if (Settings::getInstance().getFilterAudio())
@ -46,11 +46,10 @@ ToxCall::ToxCall(ToxCall&& other) noexcept
other.callId = numeric_limits<decltype(callId)>::max(); other.callId = numeric_limits<decltype(callId)>::max();
other.alSource = 0; other.alSource = 0;
// required -> ownership of audio input is moved to new instance
Audio& audio = Audio::getInstance(); Audio& audio = Audio::getInstance();
audio.subscribeInput(this); audio.subscribeInput();
audio.unsubscribeInput(&other); audio.subscribeOutput(alSource);
audio.subscribeOutput(this);
audio.unsubscribeOutput(&other);
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
filterer = other.filterer; filterer = other.filterer;
@ -68,11 +67,8 @@ ToxCall::~ToxCall()
sendAudioTimer->stop(); sendAudioTimer->stop();
} }
if (alSource) audio.unsubscribeInput();
audio.deleteSource(alSource); audio.unsubscribeOutput(alSource);
audio.unsubscribeInput(this);
audio.unsubscribeOutput(this);
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
if (filterer) if (filterer)
@ -92,6 +88,10 @@ const ToxCall& ToxCall::operator=(ToxCall&& other) noexcept
alSource = other.alSource; alSource = other.alSource;
other.alSource = 0; other.alSource = 0;
// required -> ownership of audio input is moved to new instance
Audio& audio = Audio::getInstance();
audio.subscribeInput();
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
filterer = other.filterer; filterer = other.filterer;
other.filterer = nullptr; other.filterer = nullptr;

View File

@ -29,14 +29,17 @@
#include "src/core/coreav.h" #include "src/core/coreav.h"
#include <QDebug> #include <QDebug>
#include <QShowEvent>
#ifndef ALC_ALL_DEVICES_SPECIFIER #ifndef ALC_ALL_DEVICES_SPECIFIER
#define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER
#endif #endif
AVForm::AVForm() : AVForm::AVForm() :
GenericForm(QPixmap(":/img/settings/av.png")), GenericForm(QPixmap(":/img/settings/av.png"))
camVideoSurface{nullptr}, camera(CameraSource::getInstance()) , subscribedToAudioIn{false}
, camVideoSurface{nullptr}
, camera(CameraSource::getInstance())
{ {
bodyUI = new Ui::AVSettings; bodyUI = new Ui::AVSettings;
bodyUI->setupUi(this); bodyUI->setupUi(this);
@ -90,15 +93,38 @@ AVForm::~AVForm()
delete bodyUI; delete bodyUI;
} }
void AVForm::showEvent(QShowEvent*) void AVForm::hideEvent(QHideEvent* event)
{
if (subscribedToAudioIn) {
// TODO: this should not be done in show/hide events
Audio::getInstance().unsubscribeInput();
subscribedToAudioIn = false;
}
if (camVideoSurface)
{
camVideoSurface->setSource(nullptr);
killVideoSurface();
}
videoDeviceList.clear();
GenericForm::hideEvent(event);
}
void AVForm::showEvent(QShowEvent* event)
{ {
getAudioOutDevices(); getAudioOutDevices();
getAudioInDevices(); getAudioInDevices();
createVideoSurface(); createVideoSurface();
getVideoDevices(); getVideoDevices();
Audio& audio = Audio::getInstance();
audio.subscribeInput(this); if (!subscribedToAudioIn) {
audio.subscribeOutput(this); // TODO: this should not be done in show/hide events
Audio::getInstance().subscribeInput();
subscribedToAudioIn = true;
}
GenericForm::showEvent(event);
} }
void AVForm::onVideoModesIndexChanged(int index) void AVForm::onVideoModesIndexChanged(int index)
@ -227,19 +253,6 @@ void AVForm::onVideoDevChanged(int index)
Core::getInstance()->getAv()->sendNoVideo(); Core::getInstance()->getAv()->sendNoVideo();
} }
void AVForm::hideEvent(QHideEvent *)
{
if (camVideoSurface)
{
camVideoSurface->setSource(nullptr);
killVideoSurface();
}
videoDeviceList.clear();
Audio& audio = Audio::getInstance();
audio.unsubscribeInput(this);
audio.unsubscribeOutput(this);
}
void AVForm::getVideoDevices() void AVForm::getVideoDevices()
{ {
QString settingsInDev = Settings::getInstance().getVideoDev(); QString settingsInDev = Settings::getInstance().getVideoDev();

View File

@ -39,7 +39,7 @@ class AVForm : public GenericForm
public: public:
AVForm(); AVForm();
~AVForm(); ~AVForm();
virtual QString getFormName() final override {return tr("Audio/Video");} QString getFormName() final override {return tr("Audio/Video");}
private: private:
void getAudioInDevices(); void getAudioInDevices();
@ -64,15 +64,18 @@ private slots:
void onVideoDevChanged(int index); void onVideoDevChanged(int index);
void onVideoModesIndexChanged(int index); void onVideoModesIndexChanged(int index);
virtual void hideEvent(QHideEvent*) final override;
virtual void showEvent(QShowEvent*) final override;
protected: protected:
virtual bool eventFilter(QObject *o, QEvent *e) final override;
void updateVideoModes(int curIndex); void updateVideoModes(int curIndex);
private:
bool eventFilter(QObject *o, QEvent *e) final override;
void hideEvent(QHideEvent* event) final override;
void showEvent(QShowEvent*event) final override;
private: private:
Ui::AVSettings *bodyUI; Ui::AVSettings *bodyUI;
bool subscribedToAudioIn;
VideoSurface *camVideoSurface; VideoSurface *camVideoSurface;
CameraSource &camera; CameraSource &camera;
QVector<QPair<QString, QString>> videoDeviceList; QVector<QPair<QString, QString>> videoDeviceList;