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:
parent
27bfade9e1
commit
0615c7c3c6
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user