mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-01-24 13:58:00 -05:00
Switched to the more efficient way of spawning pulse audio context/stream. This change fixed rare issues reported by address sanitizer and accidental freezes.
132 lines
4.0 KiB
C++
132 lines
4.0 KiB
C++
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "LinuxAudioDevice.hpp"
|
|
#include <Audio/Stream.hpp>
|
|
#include <log/log.hpp>
|
|
#include <cmath>
|
|
#include <stdexcept>
|
|
|
|
namespace
|
|
{
|
|
/// Creating and destroying context each time LinuxAudioDevice is spawned seems very unstable. It causes various
|
|
/// problems, from raising sanitizer errors to completely freezing the simulator. As a solution, another approach
|
|
/// has been chosen. Instead of recreating the pulse audio context, we rely on static instance of it, which is valid
|
|
/// throughout the life of the simulator process. During the standard operation, we only create and attach audio
|
|
/// streams, which have proven to be a fast and reliable way of doing things. All the necessary context cleaning is
|
|
/// handled in the context's destructor during the simulator process close.
|
|
static audio::pulse_audio::Context ctx;
|
|
audio::pulse_audio::Context &get_context()
|
|
{
|
|
return ctx;
|
|
}
|
|
} // namespace
|
|
|
|
namespace audio
|
|
{
|
|
LinuxAudioDevice::LinuxAudioDevice(const float initialVolume)
|
|
: supportedFormats(
|
|
audio::AudioFormat::makeMatrix(supportedSampleRates, supportedBitWidths, supportedChannelModes)),
|
|
audioProxy("audioProxy", [this](const auto &data) { onDataSend(); })
|
|
{
|
|
setOutputVolume(initialVolume);
|
|
}
|
|
|
|
auto LinuxAudioDevice::Start() -> RetCode
|
|
{
|
|
if (!isSinkConnected()) {
|
|
return AudioDevice::RetCode::Failure;
|
|
}
|
|
return AudioDevice::RetCode::Success;
|
|
}
|
|
|
|
auto LinuxAudioDevice::Stop() -> RetCode
|
|
{
|
|
return AudioDevice::RetCode::Success;
|
|
}
|
|
|
|
auto LinuxAudioDevice::setOutputVolume(float vol) -> RetCode
|
|
{
|
|
vol = std::clamp(vol, minVolume, maxVolume);
|
|
/// Using y=x^4 function as an approximation seems very natural and sufficient
|
|
/// For more info check: https://www.dr-lex.be/info-stuff/volumecontrols.html
|
|
volumeFactor = std::pow(1.0f * (vol / maxVolume), 4);
|
|
return RetCode::Success;
|
|
}
|
|
|
|
auto LinuxAudioDevice::setInputGain([[maybe_unused]] float gain) -> RetCode
|
|
{
|
|
return RetCode::Success;
|
|
}
|
|
|
|
auto LinuxAudioDevice::getTraits() const -> Traits
|
|
{
|
|
return Traits{};
|
|
}
|
|
|
|
auto LinuxAudioDevice::getSupportedFormats() -> std::vector<audio::AudioFormat>
|
|
{
|
|
return supportedFormats;
|
|
}
|
|
|
|
auto LinuxAudioDevice::getSourceFormat() -> audio::AudioFormat
|
|
{
|
|
return currentFormat;
|
|
}
|
|
|
|
void LinuxAudioDevice::onDataSend()
|
|
{
|
|
audio::Stream::Span dataSpan;
|
|
if (!isSinkConnected()) {
|
|
return;
|
|
}
|
|
Sink::_stream->peek(dataSpan);
|
|
scaleVolume(dataSpan);
|
|
stream->insert(dataSpan);
|
|
Sink::_stream->consume();
|
|
|
|
if (stream->bytes() >= requestedBytes) {
|
|
stream->consume();
|
|
}
|
|
else {
|
|
audioProxy.post(requestedBytes);
|
|
}
|
|
}
|
|
|
|
void LinuxAudioDevice::onDataReceive()
|
|
{}
|
|
|
|
void LinuxAudioDevice::enableInput()
|
|
{}
|
|
|
|
void LinuxAudioDevice::enableOutput()
|
|
{
|
|
currentFormat = Sink::_stream->getOutputTraits().format;
|
|
|
|
stream = get_context().open_stream(currentFormat, [this](const std::size_t size) {
|
|
requestedBytes = size;
|
|
audioProxy.post(requestedBytes);
|
|
});
|
|
|
|
if (stream == nullptr) {
|
|
fprintf(stderr, "Failed to open the audio stream");
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
void LinuxAudioDevice::disableInput()
|
|
{}
|
|
|
|
void LinuxAudioDevice::disableOutput()
|
|
{
|
|
get_context().close_stream();
|
|
currentFormat = {};
|
|
}
|
|
void LinuxAudioDevice::scaleVolume(audio::AbstractStream::Span data)
|
|
{
|
|
const auto samplesBeg = reinterpret_cast<std::int16_t *>(data.data);
|
|
const auto samplesEnd = samplesBeg + data.dataSize / 2;
|
|
std::for_each(samplesBeg, samplesEnd, [this](auto &sample) { sample *= volumeFactor; });
|
|
}
|
|
} // namespace audio
|