136 lines
No EOL
4.1 KiB
C++
136 lines
No EOL
4.1 KiB
C++
#include "AudioEvent.hpp"
|
|
|
|
#include <cstring>
|
|
#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(
|
|
Context& context,
|
|
const packet::GenericPacketContent& content,
|
|
sockaddr* fromAddress,
|
|
socklen_t fromAddressLength
|
|
) {
|
|
// get the audio data in the content
|
|
packet::AudioPacketData audioData;
|
|
std::memcpy(&audioData, content.data.data(), content.data.size());
|
|
// save it in the audio queue
|
|
this->audioQueue.push(audioData);
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
|
|
} |