239 lines
7.3 KiB
C++
239 lines
7.3 KiB
C++
// Copyright 2007-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "Audio.h"
|
|
|
|
#include "AudioInput.h"
|
|
#include "AudioOutput.h"
|
|
#include "Log.h"
|
|
#include "PacketDataStream.h"
|
|
#include "PluginManager.h"
|
|
#include "Global.h"
|
|
|
|
#include <QtCore/QObject>
|
|
|
|
#include <cstring>
|
|
|
|
|
|
#define DOUBLE_RAND (rand() / static_cast< double >(RAND_MAX))
|
|
|
|
LoopUser LoopUser::lpLoopy;
|
|
|
|
LoopUser::LoopUser() {
|
|
qsName = QLatin1String("Loopy");
|
|
uiSession = 0;
|
|
iId = 0;
|
|
bMute = bDeaf = bSuppress = false;
|
|
bLocalIgnore = bLocalMute = bSelfDeaf = false;
|
|
tsState = Settings::Passive;
|
|
cChannel = nullptr;
|
|
qetTicker.start();
|
|
qetLastFetch.start();
|
|
}
|
|
|
|
void LoopUser::addFrame(const Mumble::Protocol::AudioData &audioData) {
|
|
if (DOUBLE_RAND < Global::get().s.dPacketLoss) {
|
|
qWarning("Drop");
|
|
return;
|
|
}
|
|
|
|
{
|
|
QMutexLocker l(&qmLock);
|
|
bool restart = (qetLastFetch.elapsed() > 100);
|
|
|
|
long time = qetTicker.elapsed();
|
|
|
|
float r;
|
|
if (restart)
|
|
r = 0;
|
|
else
|
|
r = static_cast< float >(DOUBLE_RAND * Global::get().s.dMaxPacketDelay);
|
|
|
|
|
|
float virtualArrivalTime = static_cast< float >(time) + r;
|
|
// Insert default-constructed AudioPacket object and only then fill its data in-place. This is necessary to
|
|
// avoid any moving around of the payload vector which would mess up our pointers in the AudioData object.
|
|
m_packets[virtualArrivalTime] = AudioPacket{};
|
|
AudioPacket &packet = m_packets[virtualArrivalTime];
|
|
|
|
// copy audio data to packet
|
|
packet.payload.resize(audioData.payload.size());
|
|
std::memcpy(packet.payload.data(), audioData.payload.data(), audioData.payload.size());
|
|
|
|
packet.audioData = audioData;
|
|
// The audio data is now stored in the payload vector and thus this is where we should point the used view (we
|
|
// don't own the original buffer and can thus not guarantee what happens with it once this function returns).
|
|
packet.audioData.payload = { packet.payload.data(), packet.payload.size() };
|
|
}
|
|
|
|
// Restart check
|
|
if (qetLastFetch.elapsed() > 100) {
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
if (ao) {
|
|
Mumble::Protocol::AudioData empty;
|
|
empty.usedCodec = audioData.usedCodec;
|
|
ao->addFrameToBuffer(this, empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoopUser::fetchFrames() {
|
|
QMutexLocker l(&qmLock);
|
|
|
|
AudioOutputPtr ao(Global::get().ao);
|
|
if (!ao || m_packets.empty()) {
|
|
return;
|
|
}
|
|
|
|
float cmp = static_cast< float >(qetTicker.elapsed());
|
|
|
|
auto it = m_packets.begin();
|
|
while (it != m_packets.end()) {
|
|
if (it->first > cmp) {
|
|
break;
|
|
}
|
|
|
|
ao->addFrameToBuffer(this, it->second.audioData);
|
|
|
|
it = m_packets.erase(it);
|
|
}
|
|
|
|
qetLastFetch.restart();
|
|
}
|
|
|
|
RecordUser::RecordUser() {
|
|
qsName = QLatin1String("Recorder");
|
|
}
|
|
|
|
RecordUser::~RecordUser() {
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
if (ao)
|
|
ao->removeUser(this);
|
|
}
|
|
|
|
void RecordUser::addFrame(const Mumble::Protocol::AudioData &audioData) {
|
|
AudioOutputPtr ao(Global::get().ao);
|
|
if (!ao)
|
|
return;
|
|
|
|
ao->addFrameToBuffer(this, audioData);
|
|
}
|
|
|
|
void Audio::startOutput(const QString &output) {
|
|
Global::get().ao = AudioOutputRegistrar::newFromChoice(output);
|
|
if (Global::get().ao)
|
|
Global::get().ao->start(QThread::HighPriority);
|
|
}
|
|
|
|
void Audio::stopOutput() {
|
|
// Take a copy of the global AudioOutput shared pointer
|
|
// to keep a reference around.
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
|
|
// Reset the global AudioOutput shared pointer to the null pointer.
|
|
Global::get().ao.reset();
|
|
|
|
// Wait until our copy of the AudioOutput shared pointer (ao)
|
|
// is the only one left.
|
|
while (ao.get() && !ao.unique()) {
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
|
|
// Reset our copy of the AudioOutput shared pointer.
|
|
// This causes the AudioOutput destructor to be called
|
|
// right here in this function, on the main thread.
|
|
// Our audio backends expect this to happen.
|
|
//
|
|
// One such example is PulseAudioInput, whose destructor
|
|
// takes the PulseAudio mainloop lock. If the destructor
|
|
// is called inside one of the PulseAudio callbacks that
|
|
// take copies of Global::get().ai, the destructor will try to also
|
|
// take the mainloop lock, causing an abort().
|
|
ao.reset();
|
|
}
|
|
|
|
void Audio::startInput(const QString &input) {
|
|
Global::get().ai = AudioInputRegistrar::newFromChoice(input);
|
|
if (Global::get().ai)
|
|
Global::get().ai->start(QThread::HighestPriority);
|
|
}
|
|
|
|
void Audio::stopInput() {
|
|
// Take a copy of the global AudioInput shared pointer
|
|
// to keep a reference around.
|
|
AudioInputPtr ai = Global::get().ai;
|
|
|
|
// Reset the global AudioInput shared pointer to the null pointer.
|
|
Global::get().ai.reset();
|
|
|
|
// Wait until our copy of the AudioInput shared pointer (ai)
|
|
// is the only one left.
|
|
while (ai.get() && !ai.unique()) {
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
|
|
// Reset our copy of the AudioInput shared pointer.
|
|
// This causes the AudioInput destructor to be called
|
|
// right here in this function, on the main thread.
|
|
// Our audio backends expect this to happen.
|
|
//
|
|
// One such example is PulseAudioInput, whose destructor
|
|
// takes the PulseAudio mainloop lock. If the destructor
|
|
// is called inside one of the PulseAudio callbacks that
|
|
// take copies of Global::get().ai, the destructor will try to also
|
|
// take the mainloop lock, causing an abort().
|
|
ai.reset();
|
|
}
|
|
|
|
void Audio::start(const QString &input, const QString &output) {
|
|
startInput(input);
|
|
startOutput(output);
|
|
|
|
// Now that the audio input and output is created, we connect them to the PluginManager
|
|
// As these callbacks might want to change the audio before it gets further processed, all these connections have to
|
|
// be direct
|
|
QObject::connect(Global::get().ai.get(), &AudioInput::audioInputEncountered, Global::get().pluginManager,
|
|
&PluginManager::on_audioInput, Qt::DirectConnection);
|
|
QObject::connect(Global::get().ao.get(), &AudioOutput::audioSourceFetched, Global::get().pluginManager,
|
|
&PluginManager::on_audioSourceFetched, Qt::DirectConnection);
|
|
QObject::connect(Global::get().ao.get(), &AudioOutput::audioOutputAboutToPlay, Global::get().pluginManager,
|
|
&PluginManager::on_audioOutputAboutToPlay, Qt::DirectConnection);
|
|
}
|
|
|
|
void Audio::stop() {
|
|
// Take copies of the global AudioInput and AudioOutput
|
|
// shared pointers to keep a reference to each of them
|
|
// around.
|
|
AudioInputPtr ai = Global::get().ai;
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
|
|
// Reset the global AudioInput and AudioOutput shared pointers
|
|
// to the null pointer.
|
|
Global::get().ao.reset();
|
|
Global::get().ai.reset();
|
|
|
|
// Wait until our copies of the AudioInput and AudioOutput shared pointers
|
|
// (ai and ao) are the only ones left.
|
|
while ((ai.get() && !ai.unique()) || (ao.get() && !ao.unique())) {
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
|
|
// Reset our copies of the AudioInput and AudioOutput
|
|
// shared pointers.
|
|
// This causes the AudioInput and AudioOutput destructors
|
|
// to be called right here in this function, on the main
|
|
// thread. Our audio backends expect this to happen.
|
|
//
|
|
// One such example is PulseAudioInput, whose destructor
|
|
// takes the PulseAudio mainloop lock. If the destructor
|
|
// is called inside one of the PulseAudio callbacks that
|
|
// take copies of Global::get().ai, the destructor will try to also
|
|
// take the mainloop lock, causing an abort().
|
|
ai.reset();
|
|
ao.reset();
|
|
}
|
|
|
|
#undef DOUBLE_RAND
|