mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
76cbaf18f1
The qTox Project is not associated with the Tox Project in any ways, with the exception of "qTox" using the Tox Projet's "toxcore" collection of libraries. In particular, the Tox Projet does not own copyright over the qTox Project's "qTox" collection of software, source code, and assets. The qTox Project's assets are under the sole copyright of the qTox contributors, and no partiular rights are granted to the Tox Project.
385 lines
10 KiB
C++
385 lines
10 KiB
C++
/*
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
This program 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.
|
|
This program 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 COPYING file for more details.
|
|
*/
|
|
|
|
|
|
// Output some extra debug info
|
|
#define AUDIO_DEBUG 1
|
|
|
|
// Fix a 7 years old openal-soft/alsa bug
|
|
// http://blog.gmane.org/gmane.comp.lib.openal.devel/month=20080501
|
|
// If set to 1, the capture will be started as long as the device is open
|
|
#define FIX_SND_PCM_PREPARE_BUG 0
|
|
|
|
#include "audio.h"
|
|
#include "src/core/core.h"
|
|
|
|
#include <QDebug>
|
|
#include <QThread>
|
|
#include <QMutexLocker>
|
|
|
|
#include <cassert>
|
|
|
|
std::atomic<int> Audio::userCount{0};
|
|
Audio* Audio::instance{nullptr};
|
|
QThread* Audio::audioThread{nullptr};
|
|
QMutex* Audio::audioInLock{nullptr};
|
|
QMutex* Audio::audioOutLock{nullptr};
|
|
ALCdevice* Audio::alInDev{nullptr};
|
|
ALCdevice* Audio::alOutDev{nullptr};
|
|
ALCcontext* Audio::alContext{nullptr};
|
|
ALuint Audio::alMainSource{0};
|
|
float Audio::outputVolume{1.0};
|
|
|
|
Audio& Audio::getInstance()
|
|
{
|
|
if (!instance)
|
|
{
|
|
instance = new Audio();
|
|
audioThread = new QThread(instance);
|
|
audioThread->setObjectName("qTox Audio");
|
|
audioThread->start();
|
|
audioInLock = new QMutex(QMutex::Recursive);
|
|
audioOutLock = new QMutex(QMutex::Recursive);
|
|
instance->moveToThread(audioThread);
|
|
}
|
|
return *instance;
|
|
}
|
|
|
|
Audio::~Audio()
|
|
{
|
|
qDebug() << "Deleting Audio";
|
|
audioThread->exit(0);
|
|
audioThread->wait();
|
|
if (audioThread->isRunning())
|
|
audioThread->terminate();
|
|
|
|
delete audioThread;
|
|
delete audioInLock;
|
|
delete audioOutLock;
|
|
}
|
|
|
|
float Audio::getOutputVolume()
|
|
{
|
|
return outputVolume;
|
|
}
|
|
|
|
void Audio::setOutputVolume(float volume)
|
|
{
|
|
outputVolume = volume;
|
|
alSourcef(alMainSource, AL_GAIN, outputVolume);
|
|
|
|
for (const ToxGroupCall& call : Core::groupCalls)
|
|
{
|
|
if (!call.active)
|
|
continue;
|
|
for (ALuint source : call.alSources)
|
|
alSourcef(source, AL_GAIN, outputVolume);
|
|
}
|
|
|
|
for (const ToxCall& call : Core::calls)
|
|
{
|
|
if (!call.active)
|
|
continue;
|
|
alSourcef(call.alSource, AL_GAIN, outputVolume);
|
|
}
|
|
}
|
|
|
|
void Audio::suscribeInput()
|
|
{
|
|
if (!alInDev)
|
|
{
|
|
qWarning()<<"input device is closed";
|
|
return;
|
|
}
|
|
|
|
qDebug() << "suscribing input";
|
|
QMutexLocker lock(audioInLock);
|
|
if (!userCount++ && alInDev)
|
|
{
|
|
#if (!FIX_SND_PCM_PREPARE_BUG)
|
|
qDebug() << "starting capture";
|
|
alcCaptureStart(alInDev);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Audio::unsuscribeInput()
|
|
{
|
|
if (!alInDev)
|
|
{
|
|
qWarning()<<"input device is closed";
|
|
return;
|
|
}
|
|
|
|
qDebug() << "unsuscribing input";
|
|
QMutexLocker lock(audioInLock);
|
|
if (!--userCount && alInDev)
|
|
{
|
|
#if (!FIX_SND_PCM_PREPARE_BUG)
|
|
qDebug() << "stopping capture";
|
|
alcCaptureStop(alInDev);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Audio::openInput(const QString& inDevDescr)
|
|
{
|
|
qDebug() << "Trying to open input "+inDevDescr;
|
|
QMutexLocker lock(audioInLock);
|
|
auto* tmp = alInDev;
|
|
alInDev = nullptr;
|
|
if (tmp)
|
|
alcCaptureCloseDevice(tmp);
|
|
|
|
int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
|
if (inDevDescr.isEmpty())
|
|
alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag,
|
|
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4)
|
|
/ 1000 * av_DefaultSettings.audio_channels);
|
|
else
|
|
alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag,
|
|
(av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4)
|
|
/ 1000 * av_DefaultSettings.audio_channels);
|
|
if (!alInDev)
|
|
qWarning() << "Cannot open input audio device " + inDevDescr;
|
|
else
|
|
qDebug() << "Opening audio input "<<inDevDescr;
|
|
|
|
Core::getInstance()->resetCallSources(); // Force to regen each group call's sources
|
|
|
|
// Restart the capture if necessary
|
|
if (userCount.load() != 0 && alInDev)
|
|
{
|
|
alcCaptureStart(alInDev);
|
|
}
|
|
else
|
|
{
|
|
#if (FIX_SND_PCM_PREPARE_BUG)
|
|
alcCaptureStart(alInDev);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
void Audio::openOutput(const QString& outDevDescr)
|
|
{
|
|
qDebug() << "Trying to open output " + outDevDescr;
|
|
QMutexLocker lock(audioOutLock);
|
|
auto* tmp = alOutDev;
|
|
alOutDev = nullptr;
|
|
if (outDevDescr.isEmpty())
|
|
alOutDev = alcOpenDevice(nullptr);
|
|
else
|
|
alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
|
|
|
|
if (!alOutDev)
|
|
{
|
|
qWarning() << "Cannot open output audio device " + outDevDescr;
|
|
}
|
|
else
|
|
{
|
|
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
|
|
alcDestroyContext(alContext);
|
|
|
|
if (tmp)
|
|
alcCloseDevice(tmp);
|
|
|
|
alContext=alcCreateContext(alOutDev,nullptr);
|
|
if (!alcMakeContextCurrent(alContext))
|
|
{
|
|
qWarning() << "Cannot create output audio context";
|
|
alcCloseDevice(alOutDev);
|
|
}
|
|
else
|
|
{
|
|
alGenSources(1, &alMainSource);
|
|
}
|
|
|
|
|
|
qDebug() << "Opening audio output " + outDevDescr;
|
|
}
|
|
|
|
Core::getInstance()->resetCallSources(); // Force to regen each group call's sources
|
|
}
|
|
|
|
void Audio::closeInput()
|
|
{
|
|
qDebug() << "Closing input";
|
|
QMutexLocker lock(audioInLock);
|
|
if (alInDev)
|
|
{
|
|
if (alcCaptureCloseDevice(alInDev) == ALC_TRUE)
|
|
{
|
|
alInDev = nullptr;
|
|
userCount = 0;
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "Failed to close input";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Audio::closeOutput()
|
|
{
|
|
qDebug() << "Closing output";
|
|
QMutexLocker lock(audioOutLock);
|
|
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
|
|
alcDestroyContext(alContext);
|
|
|
|
if (alOutDev)
|
|
{
|
|
if (alcCloseDevice(alOutDev) == ALC_TRUE)
|
|
alOutDev = nullptr;
|
|
else
|
|
qWarning() << "Failed to close output";
|
|
}
|
|
}
|
|
|
|
void Audio::playMono16Sound(const QByteArray& data)
|
|
{
|
|
QMutexLocker lock(audioOutLock);
|
|
if (!alOutDev)
|
|
return;
|
|
|
|
ALuint buffer;
|
|
alGenBuffers(1, &buffer);
|
|
alBufferData(buffer, AL_FORMAT_MONO16, data.data(), data.size(), 44100);
|
|
alSourcef(alMainSource, AL_GAIN, outputVolume);
|
|
alSourcei(alMainSource, AL_BUFFER, buffer);
|
|
alSourcePlay(alMainSource);
|
|
alDeleteBuffers(1, &buffer);
|
|
}
|
|
|
|
void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
|
|
unsigned samples, uint8_t channels, unsigned sample_rate, void* core)
|
|
{
|
|
QMetaObject::invokeMethod(instance, "playGroupAudio", Qt::BlockingQueuedConnection,
|
|
Q_ARG(int,group), Q_ARG(int,peer), Q_ARG(const int16_t*,data),
|
|
Q_ARG(unsigned,samples), Q_ARG(uint8_t,channels), Q_ARG(unsigned,sample_rate));
|
|
emit static_cast<Core*>(core)->groupPeerAudioPlaying(group, peer);
|
|
}
|
|
|
|
void Audio::playGroupAudio(int group, int peer, const int16_t* data,
|
|
unsigned samples, uint8_t channels, unsigned sample_rate)
|
|
{
|
|
assert(QThread::currentThread() == audioThread);
|
|
|
|
QMutexLocker lock(audioOutLock);
|
|
|
|
ToxGroupCall& call = Core::groupCalls[group];
|
|
|
|
if (!call.active || call.muteVol)
|
|
return;
|
|
|
|
if (!call.alSources.contains(peer))
|
|
{
|
|
alGenSources(1, &call.alSources[peer]);
|
|
alSourcef(call.alSources[peer], AL_GAIN, outputVolume);
|
|
}
|
|
|
|
playAudioBuffer(call.alSources[peer], data, samples, channels, sample_rate);
|
|
}
|
|
|
|
void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
|
|
{
|
|
assert(channels == 1 || channels == 2);
|
|
|
|
QMutexLocker lock(audioOutLock);
|
|
|
|
ALuint bufid;
|
|
ALint processed = 0, queued = 16;
|
|
alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed);
|
|
alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued);
|
|
alSourcei(alSource, AL_LOOPING, AL_FALSE);
|
|
|
|
if (processed)
|
|
{
|
|
ALuint bufids[processed];
|
|
alSourceUnqueueBuffers(alSource, processed, bufids);
|
|
alDeleteBuffers(processed - 1, bufids + 1);
|
|
bufid = bufids[0];
|
|
}
|
|
else if (queued < 16)
|
|
{
|
|
alGenBuffers(1, &bufid);
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Dropped frame";
|
|
return;
|
|
}
|
|
|
|
alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data,
|
|
samples * 2 * channels, sampleRate);
|
|
alSourceQueueBuffers(alSource, 1, &bufid);
|
|
|
|
ALint state;
|
|
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
|
|
alSourcef(alSource, AL_GAIN, outputVolume);
|
|
if (state != AL_PLAYING)
|
|
alSourcePlay(alSource);
|
|
}
|
|
|
|
bool Audio::isInputReady()
|
|
{
|
|
return (alInDev && userCount);
|
|
}
|
|
|
|
bool Audio::isOutputClosed()
|
|
{
|
|
return (alOutDev);
|
|
}
|
|
|
|
bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
|
|
{
|
|
QMutexLocker lock(audioInLock);
|
|
|
|
ALint samples=0;
|
|
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
|
|
if (samples < framesize)
|
|
return false;
|
|
|
|
memset(buf, 0, framesize * 2 * av_DefaultSettings.audio_channels); // Avoid uninitialized values (Valgrind)
|
|
alcCaptureSamples(Audio::alInDev, buf, framesize);
|
|
return true;
|
|
}
|
|
|
|
#ifdef QTOX_FILTER_AUDIO
|
|
#include "audiofilterer.h"
|
|
|
|
/* include for compatibility with older versions of OpenAL */
|
|
#ifndef ALC_ALL_DEVICES_SPECIFIER
|
|
#include <AL/alext.h>
|
|
#endif
|
|
|
|
void Audio::getEchoesToFilter(AudioFilterer* filterer, int framesize)
|
|
{
|
|
#ifdef ALC_LOOPBACK_CAPTURE_SAMPLES
|
|
ALint samples;
|
|
alcGetIntegerv(Audio::alOutDev, ALC_LOOPBACK_CAPTURE_SAMPLES, sizeof(samples), &samples);
|
|
if (samples >= framesize)
|
|
{
|
|
int16_t buf[framesize];
|
|
alcCaptureSamplesLoopback(Audio::alOutDev, buf, framesize);
|
|
filterer->passAudioOutput(buf, framesize);
|
|
filterer->setEchoDelayMs(5); // This 5ms is configurable I believe
|
|
}
|
|
#else
|
|
Q_UNUSED(filterer);
|
|
Q_UNUSED(framesize);
|
|
#endif
|
|
}
|
|
#endif
|