M2-PT-DRP/source/events/audio/AudioEvent.cpp

126 lines
No EOL
3.9 KiB
C++

#include "AudioEvent.hpp"
#include <iostream>
#include <bits/unique_lock.h>
namespace drp::event {
AudioEvent::AudioEvent() {
this->stream = nullptr;
this->audioLock = std::unique_lock(this->audioMutex);
this->streamChannels = 0;
this->streamSampleFormat = 0;
this->streamRate = 0;
// start a thread for the player
this->playerThread = std::thread(&AudioEvent::loopPlay, this);
}
AudioEvent::~AudioEvent() {
// stop any currently playing audio
Pa_StopStream(this->stream);
// close the audio stream
if (const PaError error = Pa_CloseStream(this->stream))
std::cerr << "[Client] Could not close the stream: " << std::string(Pa_GetErrorText(error)) << std::endl;
}
void AudioEvent::handle(const packet::GenericPacketContent& content) {
this->audioQueue.push(static_cast<packet::AudioPacket>(content));
// notify that a new audio chunk is available
this->audioCondition.notify_one();
}
void AudioEvent::updateAudioStream(const int channels, const std::uint32_t sampleFormat, const double sampleRate) {
// check if any information changed. If no, ignore this
if (
this->streamChannels == channels &&
this->streamSampleFormat == sampleFormat &&
this->streamRate == sampleRate
)
return;
// close the current stream
// ignore errors that could happen if no audio is currently playing
Pa_CloseStream(&this->stream);
// open a new stream with the new settings
if (const PaError error = Pa_OpenDefaultStream(
&this->stream,
0,
channels,
sampleFormat,
sampleRate,
paFramesPerBufferUnspecified,
nullptr,
nullptr
) != paNoError)
throw std::runtime_error("[Client] Could not open the stream: " + std::string(Pa_GetErrorText(error)));
// update the new audio values
this->streamChannels = channels;
this->streamSampleFormat = sampleFormat;
this->streamRate = sampleRate;
}
void AudioEvent::loopPlay() {
while (true) {
// wait for a new element in the audio queue
this->audioCondition.wait(
this->audioLock,
[this] { return !this->audioQueue.empty(); }
);
// get the most recent audio chunk
const auto audioPacket = this->audioQueue.top();
// update the stream with the new audio settings
this->updateAudioStream(
audioPacket.channels,
audioPacket.sampleFormat,
audioPacket.sampleRate
);
// wait until it must be played
std::this_thread::sleep_until(audioPacket.timePlay);
std::cout << "[Client] Playing: " << audioPacket.timePlay << std::endl;
// immediately stop playing music
// this avoids an offset created if this client's clock is too ahead of the others
// don't handle errors since audio might not be playing before
Pa_AbortStream(this->stream);
// play the new audio data
if (const int error = Pa_StartStream(this->stream) != paNoError)
throw std::runtime_error("[Client] Could not start the PortAudio stream: " + std::string(Pa_GetErrorText(error)));
// write the new audio data into the audio buffer
const int error = Pa_WriteStream(
this->stream,
audioPacket.content.data(),
audioPacket.contentSize / Pa_GetSampleSize(this->streamSampleFormat) / this->streamChannels
);
switch (error) {
// success
case paNoError:
// the output might be very slightly underflown,
// causing a very small period where no noise will be played.
case paOutputUnderflowed:
break;
default:
std::cerr << "[Client] Could not write to the audio stream: " << Pa_GetErrorText(error) << std::endl;
}
// remove the audio chunk
this->audioQueue.pop();
}
}
}