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

Merge pull request #2415 from antis81:ngf/mic

This commit is contained in:
Nils Fenner 2015-10-21 22:19:37 +02:00
commit 33e54b841a
No known key found for this signature in database
GPG Key ID: 9591A163FF9BE04C
13 changed files with 519 additions and 173 deletions

View File

@ -494,12 +494,13 @@ SOURCES += \
src/widget/friendlistlayout.cpp \
src/widget/genericchatitemlayout.cpp \
src/widget/categorywidget.cpp \
src/widget/tool/removefrienddialog.cpp \
src/widget/contentlayout.cpp \
src/widget/contentdialog.cpp \
src/video/genericnetcamview.cpp \
src/widget/tool/activatedialog.cpp \
src/widget/tool/movablewidget.cpp \
src/video/genericnetcamview.cpp \
src/widget/tool/micfeedbackwidget.cpp \
src/widget/tool/removefrienddialog.cpp \
src/video/groupnetcamview.cpp
HEADERS += \
@ -547,6 +548,7 @@ HEADERS += \
src/widget/contentlayout.h \
src/widget/contentdialog.h \
src/widget/tool/activatedialog.h \
src/widget/tool/micfeedbackwidget.h \
src/widget/tool/removefrienddialog.h \
src/widget/tool/movablewidget.h \
src/video/genericnetcamview.h \

View File

@ -28,58 +28,85 @@
#include "audio.h"
#include "src/core/core.h"
#include "src/persistence/settings.h"
#include <QDebug>
#include <QTimer>
#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};
/**
Returns the singleton's instance. Will construct on first call.
*/
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);
instance->startAudioThread();
}
return *instance;
}
Audio::Audio()
: audioThread(new QThread())
, audioInLock(QMutex::Recursive)
, audioOutLock(QMutex::Recursive)
, inputSubscriptions(0)
, alOutDev(nullptr)
, alInDev(nullptr)
, outputVolume(1.0)
, inputVolume(1.0)
, alMainSource(0)
, alContext(nullptr)
, timer(new QTimer(this))
{
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, &Audio::closeOutput);
audioThread->setObjectName("qTox Audio");
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
}
Audio::~Audio()
{
audioThread->exit(0);
audioThread->exit();
audioThread->wait();
if (audioThread->isRunning())
audioThread->terminate();
delete audioThread;
delete audioInLock;
delete audioOutLock;
}
float Audio::getOutputVolume()
/**
Start the audio thread for capture and playback.
*/
void Audio::startAudioThread()
{
if (!audioThread->isRunning())
audioThread->start();
else
qWarning("Audio thread already started -> ignored.");
moveToThread(audioThread);
}
/**
Returns the current output volume, between 0 and 1
*/
qreal Audio::getOutputVolume()
{
QMutexLocker locker(&audioOutLock);
return outputVolume;
}
void Audio::setOutputVolume(float volume)
/**
The volume must be between 0 and 1
*/
void Audio::setOutputVolume(qreal volume)
{
QMutexLocker locker(&audioOutLock);
outputVolume = volume;
alSourcef(alMainSource, AL_GAIN, outputVolume);
@ -99,87 +126,114 @@ void Audio::setOutputVolume(float volume)
}
}
void Audio::suscribeInput()
/**
The volume must be between 0 and 2
*/
void Audio::setInputVolume(qreal volume)
{
if (!alInDev)
{
qWarning()<<"input device is closed";
return;
}
QMutexLocker locker(&audioInLock);
inputVolume = volume;
}
qDebug() << "suscribing input";
QMutexLocker lock(audioInLock);
if (!userCount++ && alInDev)
/**
@brief Subscribe to capture sound from the opened input device.
If the input device is not open, it will be opened before capturing.
*/
void Audio::subscribeInput()
{
qDebug() << "subscribing input" << inputSubscriptions;
if (!inputSubscriptions++)
{
openInput(Settings::getInstance().getInDev());
openOutput(Settings::getInstance().getOutDev());
#if (!FIX_SND_PCM_PREPARE_BUG)
qDebug() << "starting capture";
alcCaptureStart(alInDev);
if (alInDev)
{
qDebug() << "starting capture";
alcCaptureStart(alInDev);
}
#endif
}
}
void Audio::unsuscribeInput()
{
if (!alInDev)
{
qWarning()<<"input device is closed";
return;
}
/**
@brief Unsubscribe from capturing from an opened input device.
qDebug() << "unsuscribing input";
QMutexLocker lock(audioInLock);
if (!--userCount && alInDev)
{
If the input device has no more subscriptions, it will be closed.
*/
void Audio::unsubscribeInput()
{
qDebug() << "unsubscribing input" << inputSubscriptions;
if (inputSubscriptions > 0)
inputSubscriptions--;
else if(inputSubscriptions < 0)
inputSubscriptions = 0;
if (!inputSubscriptions) {
closeOutput();
closeInput();
}
}
/**
Open an input device, use before suscribing
*/
void Audio::openInput(const QString& inDevDescr)
{
QMutexLocker lock(&audioInLock);
if (alInDev) {
#if (!FIX_SND_PCM_PREPARE_BUG)
qDebug() << "stopping capture";
alcCaptureStop(alInDev);
#endif
alcCaptureCloseDevice(alInDev);
}
}
void Audio::openInput(const QString& 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;
const uint32_t sampleRate = av_DefaultSettings.audio_sample_rate;
const uint16_t frameDuration = av_DefaultSettings.audio_frame_duration;
const uint32_t chnls = av_DefaultSettings.audio_channels;
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
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;
alInDev = alcCaptureOpenDevice(nullptr, sampleRate, stereoFlag, bufSize);
else
alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),
sampleRate, stereoFlag, bufSize);
if (alInDev)
qDebug() << "Opening audio input "<<inDevDescr;
else
qWarning() << "Cannot open input audio device " + inDevDescr;
Core* core = Core::getInstance();
if (core)
core->resetCallSources(); // Force to regen each group call's sources
// Restart the capture if necessary
if (userCount.load() != 0 && alInDev)
if (alInDev)
{
alcCaptureStart(alInDev);
}
else
{
#if (FIX_SND_PCM_PREPARE_BUG)
alcCaptureStart(alInDev);
alcCaptureStart(alInDev);
#endif
}
}
void Audio::openOutput(const QString& outDevDescr)
/**
Open an output device
*/
bool Audio::openOutput(const QString &outDevDescr)
{
QMutexLocker lock(audioOutLock);
qDebug() << "Opening audio output " + outDevDescr;
QMutexLocker lock(&audioOutLock);
auto* tmp = alOutDev;
alOutDev = nullptr;
if (outDevDescr.isEmpty())
@ -187,11 +241,7 @@ void Audio::openOutput(const QString& outDevDescr)
else
alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str());
if (!alOutDev)
{
qWarning() << "Cannot open output audio device " + outDevDescr;
}
else
if (alOutDev)
{
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
alcDestroyContext(alContext);
@ -199,36 +249,49 @@ void Audio::openOutput(const QString& outDevDescr)
if (tmp)
alcCloseDevice(tmp);
alContext=alcCreateContext(alOutDev,nullptr);
if (!alcMakeContextCurrent(alContext))
{
qWarning() << "Cannot create output audio context";
alcCloseDevice(alOutDev);
}
else
alContext = alcCreateContext(alOutDev, nullptr);
if (alcMakeContextCurrent(alContext))
{
alGenSources(1, &alMainSource);
}
qDebug() << "Opening audio output " + outDevDescr;
else
{
qWarning() << "Cannot create output audio context";
alcCloseDevice(alOutDev);
return false;
}
}
else
{
qWarning() << "Cannot open output audio device " + outDevDescr;
return false;
}
Core* core = Core::getInstance();
if (core)
core->resetCallSources(); // Force to regen each group call's sources
return true;
}
/**
Close an input device, please don't use unless everyone's unsuscribed
*/
void Audio::closeInput()
{
qDebug() << "Closing input";
QMutexLocker lock(audioInLock);
QMutexLocker locker(&audioInLock);
if (alInDev)
{
#if (!FIX_SND_PCM_PREPARE_BUG)
qDebug() << "stopping capture";
alcCaptureStop(alInDev);
#endif
if (alcCaptureCloseDevice(alInDev) == ALC_TRUE)
{
alInDev = nullptr;
userCount = 0;
inputSubscriptions = 0;
}
else
{
@ -237,12 +300,22 @@ void Audio::closeInput()
}
}
/**
Close an output device
*/
void Audio::closeOutput()
{
qDebug() << "Closing output";
QMutexLocker lock(audioOutLock);
QMutexLocker locker(&audioOutLock);
if (inputSubscriptions > 0)
return;
if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE)
{
alcDestroyContext(alContext);
alContext = nullptr;
}
if (alOutDev)
{
@ -253,11 +326,18 @@ void Audio::closeOutput()
}
}
/**
Play a 44100Hz mono 16bit PCM sound
*/
void Audio::playMono16Sound(const QByteArray& data)
{
QMutexLocker lock(audioOutLock);
QMutexLocker lock(&audioOutLock);
if (!alOutDev)
return;
{
if (!openOutput(Settings::getInstance().getOutDev()))
return;
}
ALuint buffer;
alGenBuffers(1, &buffer);
@ -265,9 +345,32 @@ void Audio::playMono16Sound(const QByteArray& data)
alSourcef(alMainSource, AL_GAIN, outputVolume);
alSourcei(alMainSource, AL_BUFFER, buffer);
alSourcePlay(alMainSource);
ALint sizeInBytes;
ALint channels;
ALint bits;
alGetBufferi(buffer, AL_SIZE, &sizeInBytes);
alGetBufferi(buffer, AL_CHANNELS, &channels);
alGetBufferi(buffer, AL_BITS, &bits);
int lengthInSamples = sizeInBytes * 8 / (channels * bits);
ALint frequency;
alGetBufferi(buffer, AL_FREQUENCY, &frequency);
qreal duration = (lengthInSamples / static_cast<qreal>(frequency)) * 1000;
int remaining = timer->interval();
if (duration > remaining)
timer->start(duration);
alDeleteBuffers(1, &buffer);
}
/**
@brief May be called from any thread, will always queue a call to playGroupAudio.
The first and last argument are ignored, but allow direct compatibility with toxcore.
*/
void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate, void* core)
{
@ -277,12 +380,16 @@ void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
emit static_cast<Core*>(core)->groupPeerAudioPlaying(group, peer);
}
/**
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,
unsigned samples, uint8_t channels, unsigned sample_rate)
{
assert(QThread::currentThread() == audioThread);
QMutexLocker lock(audioOutLock);
QMutexLocker lock(&audioOutLock);
ToxGroupCall& call = Core::groupCalls[group];
@ -298,7 +405,7 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
qreal volume = 0.;
int bufsize = samples * 2 * channels;
for (int i = 0; i < bufsize; ++i)
volume += abs(data[i]);//std::max(volume, data[i]);
volume += abs(data[i]);
emit groupAudioPlayed(group, peer, volume / bufsize);
@ -309,7 +416,7 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
{
assert(channels == 1 || channels == 2);
QMutexLocker lock(audioOutLock);
QMutexLocker lock(&audioOutLock);
ALuint bufid;
ALint processed = 0, queued = 16;
@ -345,19 +452,30 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
alSourcePlay(alSource);
}
/**
Returns true if the input device is open and suscribed to
*/
bool Audio::isInputReady()
{
return (alInDev && userCount);
QMutexLocker locker(&audioInLock);
return alInDev && inputSubscriptions;
}
/**
Returns true if the output device is open
*/
bool Audio::isOutputClosed()
{
return (alOutDev);
QMutexLocker locker(&audioOutLock);
return alOutDev;
}
/**
Does nothing and return false on failure
*/
bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
{
QMutexLocker lock(audioInLock);
QMutexLocker lock(&audioInLock);
ALint samples=0;
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
@ -366,6 +484,23 @@ bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
memset(buf, 0, framesize * 2 * av_DefaultSettings.audio_channels); // Avoid uninitialized values (Valgrind)
alcCaptureSamples(Audio::alInDev, buf, framesize);
if (inputVolume != 1)
{
int16_t* bufReal = reinterpret_cast<int16_t*>(buf);
for (int i = 0; i < framesize; ++i)
{
int sample = bufReal[i] * pow(inputVolume, 2);
if (sample < std::numeric_limits<int16_t>::min())
sample = std::numeric_limits<int16_t>::min();
else if (sample > std::numeric_limits<int16_t>::max())
sample = std::numeric_limits<int16_t>::max();
bufReal[i] = sample;
}
}
return true;
}

View File

@ -22,7 +22,7 @@
#define AUDIO_H
#include <QObject>
#include <QHash>
#include <QMutexLocker>
#include <atomic>
#if defined(__APPLE__) && defined(__MACH__)
@ -33,11 +33,7 @@
#include <AL/alc.h>
#endif
class QString;
class QByteArray;
class QTimer;
class QThread;
class QMutex;
struct Tox;
class AudioFilterer;
@ -46,28 +42,27 @@ class Audio : public QObject
Q_OBJECT
public:
static Audio& getInstance(); ///< Returns the singleton's instance. Will construct on first call.
static Audio& getInstance();
static float getOutputVolume(); ///< Returns the current output volume, between 0 and 1
static void setOutputVolume(float volume); ///< The volume must be between 0 and 1
public:
void startAudioThread();
static void suscribeInput(); ///< Call when you need to capture sound from the open input device.
static void unsuscribeInput(); ///< Call once you don't need to capture on the open input device anymore.
qreal getOutputVolume();
void setOutputVolume(qreal volume);
static void openInput(const QString& inDevDescr); ///< Open an input device, use before suscribing
static void openOutput(const QString& outDevDescr); ///< Open an output device
void setInputVolume(qreal volume);
static void closeInput(); ///< Close an input device, please don't use unless everyone's unsuscribed
static void closeOutput(); ///< Close an output device
void subscribeInput();
void unsubscribeInput();
void openInput(const QString& inDevDescr);
bool openOutput(const QString& outDevDescr);
static bool isInputReady(); ///< Returns true if the input device is open and suscribed to
static bool isOutputClosed(); ///< Returns true if the output device is open
bool isInputReady();
bool isOutputClosed();
static void playMono16Sound(const QByteArray& data); ///< Play a 44100Hz mono 16bit PCM sound
static bool tryCaptureSamples(uint8_t* buf, int framesize); ///< Does nothing and return false on failure
void playMono16Sound(const QByteArray& data);
bool tryCaptureSamples(uint8_t* buf, int framesize);
/// May be called from any thread, will always queue a call to playGroupAudio
/// The first and last argument are ignored, but allow direct compatibility with toxcore
static void playGroupAudioQueued(Tox*, int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate, void*);
@ -77,7 +72,8 @@ public:
#endif
public slots:
/// Must be called from the audio thread, plays a group call's received audio
void closeInput();
void closeOutput();
void playGroupAudio(int group, int peer, const int16_t* data,
unsigned samples, uint8_t channels, unsigned sample_rate);
@ -85,19 +81,26 @@ signals:
void groupAudioPlayed(int group, int peer, unsigned short volume);
private:
explicit Audio()=default;
Audio();
~Audio();
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
private:
static Audio* instance;
static std::atomic<int> userCount;
static ALCdevice* alOutDev, *alInDev;
static QMutex* audioInLock, *audioOutLock;
static float outputVolume;
static ALuint alMainSource;
static QThread* audioThread;
static ALCcontext* alContext;
private:
QThread* audioThread;
QMutex audioInLock;
QMutex audioOutLock;
std::atomic<int> inputSubscriptions;
ALCdevice* alOutDev;
ALCdevice* alInDev;
qreal outputVolume;
qreal inputVolume;
ALuint alMainSource;
ALCcontext* alContext;
QTimer* timer;
};
#endif // AUDIO_H

View File

@ -80,12 +80,6 @@ Core::Core(QThread *CoreThread, Profile& profile) :
calls[i].sendAudioTimer = new QTimer();
calls[i].sendAudioTimer->moveToThread(coreThread);
}
// OpenAL init
QString outDevDescr = Settings::getInstance().getOutDev();
Audio::openOutput(outDevDescr);
QString inDevDescr = Settings::getInstance().getInDev();
Audio::openInput(inDevDescr);
}
void Core::deadifyTox()
@ -131,8 +125,9 @@ Core::~Core()
delete[] videobuf;
videobuf=nullptr;
Audio::closeInput();
Audio::closeOutput();
Audio& audio = Audio::getInstance();
audio.closeInput();
audio.closeOutput();
}
Core* Core::getInstance()

View File

@ -70,7 +70,7 @@ void Core::prepareCall(uint32_t friendId, int32_t callId, ToxAv* toxav, bool vid
qWarning() << QString("Error starting call %1: toxav_prepare_transmission failed with %2").arg(callId).arg(r);
// Audio
Audio::suscribeInput();
Audio::getInstance().subscribeInput();
// Go
calls[callId].active = true;
@ -175,7 +175,6 @@ void Core::answerCall(int32_t callId)
void Core::hangupCall(int32_t callId)
{
qDebug() << QString("hanging up call %1").arg(callId);
calls[callId].active = false;
toxav_hangup(toxav, callId);
}
@ -249,7 +248,7 @@ void Core::cleanupCall(int32_t callId)
}
}
Audio::unsuscribeInput();
Audio::getInstance().unsubscribeInput();
toxav_kill_transmission(Core::getInstance()->toxav, callId);
if (!anyActiveCalls())
@ -279,7 +278,7 @@ void Core::sendCallAudio(int32_t callId, ToxAv* toxav)
if (!calls[callId].active)
return;
if (calls[callId].muteMic || !Audio::isInputReady())
if (calls[callId].muteMic || !Audio::getInstance().isInputReady())
{
calls[callId].sendAudioTimer->start();
return;
@ -289,7 +288,7 @@ void Core::sendCallAudio(int32_t callId, ToxAv* toxav)
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize];
if (Audio::tryCaptureSamples(buf, framesize))
if (Audio::getInstance().tryCaptureSamples(buf, framesize))
{
#ifdef QTOX_FILTER_AUDIO
if (Settings::getInstance().getFilterAudio())
@ -603,7 +602,7 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un
ALint state;
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
alSourcef(alSource, AL_GAIN, Audio::getOutputVolume());
alSourcef(alSource, AL_GAIN, Audio::getInstance().getOutputVolume());
if (state != AL_PLAYING)
{
alSourcePlay(alSource);
@ -629,7 +628,7 @@ void Core::joinGroupCall(int groupId)
groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT;
// Audio
Audio::suscribeInput();
Audio::getInstance().subscribeInput();
// Go
Core* core = Core::getInstance();
@ -652,7 +651,7 @@ void Core::leaveGroupCall(int groupId)
for (ALuint source : groupCalls[groupId].alSources)
alDeleteSources(1, &source);
groupCalls[groupId].alSources.clear();
Audio::unsuscribeInput();
Audio::getInstance().unsubscribeInput();
delete groupCalls[groupId].sendAudioTimer;
}
@ -661,7 +660,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav)
if (!groupCalls[groupId].active)
return;
if (groupCalls[groupId].muteMic || !Audio::isInputReady())
if (groupCalls[groupId].muteMic || !Audio::getInstance().isInputReady())
{
groupCalls[groupId].sendAudioTimer->start();
return;
@ -671,7 +670,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav)
const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels;
uint8_t buf[bufsize];
if (Audio::tryCaptureSamples(buf, framesize))
if (Audio::getInstance().tryCaptureSamples(buf, framesize))
{
if (toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf,
framesize, av_DefaultSettings.audio_channels, av_DefaultSettings.audio_sample_rate) < 0)

View File

@ -227,6 +227,8 @@ void Settings::loadGlobal()
s.beginGroup("Audio");
inDev = s.value("inDev", "").toString();
outDev = s.value("outDev", "").toString();
inVolume = s.value("inVolume", 100).toInt();
outVolume = s.value("outVolume", 100).toInt();
filterAudio = s.value("filterAudio", false).toBool();
s.endGroup();
@ -427,6 +429,8 @@ void Settings::saveGlobal()
s.beginGroup("Audio");
s.setValue("inDev", inDev);
s.setValue("outDev", outDev);
s.setValue("inVolume", inVolume);
s.setValue("outVolume", outVolume);
s.setValue("filterAudio", filterAudio);
s.endGroup();
@ -1138,6 +1142,18 @@ void Settings::setInDev(const QString& deviceSpecifier)
inDev = deviceSpecifier;
}
int Settings::getInVolume() const
{
QMutexLocker locker{&bigLock};
return inVolume;
}
void Settings::setInVolume(int volume)
{
QMutexLocker locker{&bigLock};
inVolume = volume;
}
QString Settings::getVideoDev() const
{
QMutexLocker locker{&bigLock};
@ -1162,6 +1178,18 @@ void Settings::setOutDev(const QString& deviceSpecifier)
outDev = deviceSpecifier;
}
int Settings::getOutVolume() const
{
QMutexLocker locker{&bigLock};
return outVolume;
}
void Settings::setOutVolume(int volume)
{
QMutexLocker locker{&bigLock};
outVolume = volume;
}
bool Settings::getFilterAudio() const
{
QMutexLocker locker{&bigLock};

View File

@ -154,6 +154,12 @@ public:
QString getOutDev() const;
void setOutDev(const QString& deviceSpecifier);
int getInVolume() const;
void setInVolume(int volume);
int getOutVolume() const;
void setOutVolume(int volume);
bool getFilterAudio() const;
void setFilterAudio(bool newValue);
@ -374,6 +380,8 @@ private:
// Audio
QString inDev;
QString outDev;
int inVolume;
int outVolume;
bool filterAudio;
// Video

View File

@ -62,8 +62,10 @@ AVForm::AVForm() :
connect(bodyUI->filterAudio, &QCheckBox::toggled, this, &AVForm::onFilterAudioToggled);
connect(bodyUI->rescanButton, &QPushButton::clicked, this, [=](){getAudioInDevices(); getAudioOutDevices();});
bodyUI->playbackSlider->setValue(100);
bodyUI->microphoneSlider->setValue(100);
connect(bodyUI->playbackSlider, &QSlider::sliderReleased, this, &AVForm::onPlaybackSliderReleased);
connect(bodyUI->microphoneSlider, &QSlider::sliderReleased, this, &AVForm::onMicrophoneSliderReleased);
bodyUI->playbackSlider->setValue(Settings::getInstance().getOutVolume());
bodyUI->microphoneSlider->setValue(Settings::getInstance().getInVolume());
for (QComboBox* cb : findChildren<QComboBox*>())
{
@ -87,6 +89,7 @@ void AVForm::showEvent(QShowEvent*)
getAudioInDevices();
createVideoSurface();
getVideoDevices();
Audio::getInstance().subscribeInput();
}
void AVForm::onVideoModesIndexChanged(int index)
@ -217,6 +220,7 @@ void AVForm::hideEvent(QHideEvent *)
killVideoSurface();
}
videoDeviceList.clear();
Audio::getInstance().unsubscribeInput();
}
void AVForm::getVideoDevices()
@ -310,13 +314,19 @@ void AVForm::getAudioOutDevices()
void AVForm::onInDevChanged(const QString &deviceDescriptor)
{
Settings::getInstance().setInDev(deviceDescriptor);
Audio::openInput(deviceDescriptor);
Audio& audio = Audio::getInstance();
audio.unsubscribeInput();
audio.subscribeInput();
}
void AVForm::onOutDevChanged(const QString& deviceDescriptor)
{
Settings::getInstance().setOutDev(deviceDescriptor);
Audio::openOutput(deviceDescriptor);
Audio& audio = Audio::getInstance();
audio.unsubscribeInput();
audio.subscribeInput();
}
void AVForm::onFilterAudioToggled(bool filterAudio)
@ -326,16 +336,26 @@ void AVForm::onFilterAudioToggled(bool filterAudio)
void AVForm::on_playbackSlider_valueChanged(int value)
{
Audio::setOutputVolume(value / 100.0);
Audio::getInstance().setOutputVolume(value / 100.0);
bodyUI->playbackMax->setText(QString::number(value));
}
void AVForm::on_microphoneSlider_valueChanged(int value)
{
Audio::setOutputVolume(value / 100.0);
Audio::getInstance().setInputVolume(value / 100.0);
bodyUI->microphoneMax->setText(QString::number(value));
}
void AVForm::onPlaybackSliderReleased()
{
Settings::getInstance().setOutVolume(bodyUI->playbackSlider->value());
}
void AVForm::onMicrophoneSliderReleased()
{
Settings::getInstance().setInVolume(bodyUI->microphoneSlider->value());
}
bool AVForm::eventFilter(QObject *o, QEvent *e)
{
if ((e->type() == QEvent::Wheel) &&

View File

@ -59,6 +59,8 @@ private slots:
void onFilterAudioToggled(bool filterAudio);
void on_playbackSlider_valueChanged(int value);
void on_microphoneSlider_valueChanged(int value);
void onPlaybackSliderReleased();
void onMicrophoneSliderReleased();
// camera
void onVideoDevChanged(int index);

View File

@ -48,7 +48,7 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QCheckBox" name="filterAudio">
<property name="toolTip">
<string>Filter sound from your microphone, so that people hearing you would get better sound.</string>
@ -74,15 +74,15 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="microphoneLabel">
<property name="text">
<string>Microphone</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="playbackMax">
<item row="5" column="3">
<widget class="QLabel" name="microphoneMax">
<property name="text">
<string>100</string>
</property>
@ -95,27 +95,13 @@
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="microphoneMax">
<item row="2" column="3">
<widget class="QLabel" name="playbackMax">
<property name="text">
<string>100</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSlider" name="microphoneSlider">
<property name="toolTip">
<string>Use slider to set volume of your microphone.
WARNING: slider is not supposed to work yet.</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="inDevLabel">
<property name="text">
@ -123,13 +109,26 @@ WARNING: slider is not supposed to work yet.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QLabel" name="microphoneMin">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSlider" name="microphoneSlider">
<property name="toolTip">
<string>Use slider to set volume of your microphone.</string>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="playbackLabel">
<property name="text">
@ -147,6 +146,9 @@ WARNING: slider is not supposed to work yet.</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="MicFeedbackWidget" name="microphoneFeedback" native="true"/>
</item>
</layout>
</widget>
</item>
@ -234,6 +236,12 @@ which may lead to problems with video calls.</string>
<header>src/widget/form/settings/verticalonlyscroller.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MicFeedbackWidget</class>
<extends>QWidget</extends>
<header>src/widget/tool/micfeedbackwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -0,0 +1,104 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox 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.
qTox 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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "micfeedbackwidget.h"
#include "src/audio/audio.h"
#include <QPainter>
#include <QLinearGradient>
MicFeedbackWidget::MicFeedbackWidget(QWidget *parent)
: QWidget(parent)
, timerId(0)
{
setFixedHeight(20);
}
void MicFeedbackWidget::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black));
painter.drawRect(QRect(0, 0, width() - 1, height() - 1));
int gradientWidth = round(width() * current) - 4;
if (gradientWidth < 0)
gradientWidth = 0;
QRect gradientRect(2, 2, gradientWidth, height() - 4);
QLinearGradient gradient(0, 0, width(), 0);
gradient.setColorAt(0, Qt::green);
gradient.setColorAt(0.5, Qt::yellow);
gradient.setColorAt(1, Qt::red);
painter.fillRect(gradientRect, gradient);
float slice = width() / 5;
int padding = slice / 2;
for (int i = 0; i < 5; ++i)
{
float pos = slice * i + padding;
painter.drawLine(pos, 2, pos, height() - 4);
}
}
void MicFeedbackWidget::timerEvent(QTimerEvent*)
{
const int framesize = (20 * 48000) / 1000;
const int bufsize = framesize * 2;
uint8_t buff[bufsize];
memset(buff, 0, bufsize);
if (Audio::getInstance().tryCaptureSamples(buff, framesize))
{
double max = 0;
int16_t* buffReal = reinterpret_cast<int16_t*>(&buff[0]);
for (int i = 0; i < bufsize / 2; ++i)
max = std::max(max, fabs(buffReal[i] / 32767.0));
if (max > current)
current = max;
else
current -= 0.05;
update();
}
else if (current > 0)
{
current -= 0.01;
}
}
void MicFeedbackWidget::showEvent(QShowEvent*)
{
Audio::getInstance().subscribeInput();
timerId = startTimer(60);
}
void MicFeedbackWidget::hideEvent(QHideEvent*)
{
Audio::getInstance().unsubscribeInput();
if (timerId != 0)
{
killTimer(timerId);
timerId = 0;
}
}

View File

@ -0,0 +1,42 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox 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.
qTox 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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MICFEEDBACKWIDGET_H
#define MICFEEDBACKWIDGET_H
#include <QWidget>
class MicFeedbackWidget : public QWidget
{
Q_OBJECT
public:
explicit MicFeedbackWidget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent* event) override;
void timerEvent(QTimerEvent* event) override;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private:
double current;
int timerId;
};
#endif // MICFEEDBACKWIDGET_H

View File

@ -1249,7 +1249,7 @@ bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify)
sndFile.close();
}
Audio::playMono16Sound(sndData);
Audio::getInstance().playMono16Sound(sndData);
}
}
@ -1271,7 +1271,7 @@ void Widget::playRingtone()
sndFile1.close();
}
Audio::playMono16Sound(sndData1);
Audio::getInstance().playMono16Sound(sndData1);
}
void Widget::onFriendRequestReceived(const QString& userId, const QString& message)