mumble-voip_mumble/src/mumble/Audio.cpp

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