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
{
typedef QList<Audio::SID> ALSources;
public:
AudioPrivate()
: audioThread(new QThread)
@ -162,6 +164,7 @@ public:
, outputVolume(1.f)
, inputInitialized(false)
, outputInitialized(false)
, inSubscriptions(0)
{
audioThread->setObjectName("qTox Audio");
QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
@ -196,8 +199,8 @@ public:
bool inputInitialized;
bool outputInitialized;
Audio::PtrList inputSubscriptions;
Audio::PtrList outputSubscriptions;
quint32 inSubscriptions;
ALSources outSources;
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.
*/
void Audio::subscribeInput(const void* inListener)
void Audio::subscribeInput()
{
QMutexLocker locker(&d->audioLock);
if (!d->alInDev)
d->initInput(Settings::getInstance().getInDev());
if (!d->inputSubscriptions.contains(inListener)) {
d->inputSubscriptions << inListener;
qDebug() << "Subscribed to audio input device [" << d->inputSubscriptions.size() << "subscriptions ]";
}
d->inSubscriptions++;
qDebug() << "Subscribed to audio input device [" << d->inSubscriptions << "subscriptions ]";
}
/**
@ -388,47 +389,19 @@ void Audio::subscribeInput(const void* inListener)
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);
if (inListener && d->inputSubscriptions.size())
{
d->inputSubscriptions.removeOne(inListener);
qDebug() << "Unsubscribed from audio input device [" << d->inputSubscriptions.size() << "subscriptions left ]";
if (d->inSubscriptions > 0) {
d->inSubscriptions--;
qDebug() << "Unsubscribed from audio input device [" << d->inSubscriptions << "subscriptions left ]";
}
if (d->inputSubscriptions.isEmpty())
if (!d->inSubscriptions)
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)
{
qDebug() << "Opening audio input" << inDevDescr;
@ -473,10 +446,6 @@ void AudioPrivate::initInput(const QString& inDevDescr)
else
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
if (alInDev)
{
@ -500,6 +469,7 @@ Open an audio output device
bool AudioPrivate::initOutput(const QString& outDevDescr)
{
qDebug() << "Opening audio output" << outDevDescr;
outSources.clear();
outputInitialized = false;
if (outDevDescr == "none")
@ -508,29 +478,29 @@ bool AudioPrivate::initOutput(const QString& outDevDescr)
assert(!alOutDev);
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.
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)
{
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);
}
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(outDevDescr.toStdString().c_str());
@ -551,7 +521,7 @@ bool AudioPrivate::initOutput(const QString& outDevDescr)
Core* core = Core::getInstance();
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;
return true;
@ -577,7 +547,7 @@ void Audio::playMono16Sound(const QByteArray& data)
connect(player, &AudioPlayer::finished, [=]() {
QMutexLocker locker(&d->audioLock);
if (d->outputSubscriptions.isEmpty())
if (d->outSources.isEmpty())
d->cleanupOutput();
else
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);
QMutexLocker locker(&d->audioLock);
if (!(d->alOutDev && d->outputInitialized))
return;
ALuint bufid;
ALint processed = 0, queued = 16;
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
@ -767,19 +740,41 @@ const char* Audio::inDeviceNames()
return alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
}
void Audio::createSource(quint32* sid)
void Audio::subscribeOutput(SID& sid)
{
alGenSources(1, sid);
alSourcef(*sid, AL_GAIN, 1.f);
QMutexLocker locker(&d->audioLock);
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)) {
alDeleteSources(1, &sid);
} else {
qWarning() << "Trying to delete invalid audio source" << sid;
QMutexLocker locker(&d->audioLock);
if (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()

View File

@ -21,10 +21,10 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <QObject>
#include <QMutexLocker>
#include <atomic>
#include <cmath>
#include <QObject>
#include <QWaitCondition>
struct Tox;
@ -44,7 +44,7 @@ class Audio : public QObject
Q_OBJECT
public:
typedef QList<const void*> PtrList;
typedef quint32 SID;
public:
static Audio& getInstance();
@ -68,8 +68,8 @@ public:
static const char* outDeviceNames();
static const char* inDeviceNames();
void createSource(quint32* sid);
void deleteSource(quint32 sid);
void subscribeOutput(SID& sid);
void unsubscribeOutput(SID& sid);
void startLoop();
void stopLoop();
@ -88,10 +88,8 @@ public:
#endif
public slots:
void subscribeInput(const void* inListener);
void subscribeOutput(const void* outListener);
void unsubscribeInput(const void* inListener);
void unsubscribeOutput(const void* outListener);
void subscribeInput();
void unsubscribeInput();
void playGroupAudio(int group, int peer, const int16_t* data,
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;
}
void CoreAV::resetCallSources()
void CoreAV::invalidateCallSources()
{
for (ToxGroupCall& call : groupCalls)
{
if (call.alSource)
{
Audio::getInstance().deleteSource(call.alSource);
Audio::getInstance().createSource(&call.alSource);
}
call.alSource = 0;
}
for (ToxFriendCall& call : calls)
{
if (call.alSource)
{
Audio::getInstance().deleteSource(call.alSource);
Audio::getInstance().createSource(&call.alSource);
}
call.alSource = 0;
}
}
@ -646,10 +638,11 @@ void CoreAV::audioFrameCallback(ToxAV *, uint32_t friendNum, const int16_t *pcm,
if (call.muteVol)
return;
Audio& audio = Audio::getInstance();
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,

View File

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

View File

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

View File

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