#include "AudioEvent.hpp" #include #include 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(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(); } } }