audio settings can now change dynamically

This commit is contained in:
faraphel 2024-10-22 20:14:45 +02:00
parent 21f14948b9
commit c3c452ff5d
9 changed files with 117 additions and 58 deletions

View file

@ -18,6 +18,8 @@ add_executable(M2-PT-DRP
source/Server.cpp
source/Server.hpp
source/packets/AudioPacket.hpp
source/utils/audio.cpp
source/utils/audio.hpp
)
target_include_directories(M2-PT-DRP PRIVATE
${MPG123_INCLUDE_DIRS}

View file

@ -13,24 +13,45 @@
#include "packets/AudioPacket.hpp"
Client::Client(const int channels, const double rate) {
Client::Client() {
this->stream = nullptr;
this->audioLock = std::unique_lock(this->audioMutex);
this->channels = channels;
// TODO(Faraphel): make the sampleFormat an argument.
// open a PortAudio stream
this->streamChannels = 0;
this->streamSampleFormat = 0;
this->streamRate = 0;
}
void Client::updateStream(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,
paInt16,
rate,
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;
}
Client::~Client() {
@ -126,13 +147,18 @@ void Client::loopPlayer() {
[this] { return !this->audioQueue.empty(); }
);
// get the most recent audio chunk
const auto audioPacket = audioQueue.top();
const auto audioPacket = this->audioQueue.top();
// update the stream with the new audio settings
this->updateStream(
audioPacket.channels,
audioPacket.sampleFormat,
audioPacket.sampleRate
);
// wait until it must be played
std::this_thread::sleep_until(audioPacket.timePlay);
// TODO(Faraphel) / 2 => / encoding size
// TODO(Faraphel): the number of frames could be improved
std::cout << "[Client] Playing: " << audioPacket.timePlay << std::endl;
// immediately stop playing music
@ -145,15 +171,17 @@ void Client::loopPlayer() {
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
// TODO(Faraphel) / 2 => / encoding size
// TODO(Faraphel): the number of frames could be improved
const int error = Pa_WriteStream(
this->stream,
audioPacket.content.data(),
audioPacket.contentSize / 2 / this->channels
audioPacket.contentSize / 2 / this->streamChannels
);
switch (error) {
// success
case paNoError:
// the output might be very slightly underflown,
// the output might be very slightly underflowed,
// causing a very small period where no noise will be played.
case paOutputUnderflowed:
break;

View file

@ -24,13 +24,17 @@ struct AudioPacketsComparator {
*/
class Client {
public:
/**
* :param channels: the number of channel in the audio
* :param rate: the rate of the audio
*/
explicit Client(int channels, double rate);
explicit Client();
~Client();
/**
* Update the current audio stream
* @param channels the number of channels
* @param sampleFormat the sample format type
* @param sampleRate the audio rate
*/
void updateStream(int channels, std::uint32_t sampleFormat, double sampleRate);
/**
* Indefinitely receive and play audio data.
*/
@ -48,7 +52,9 @@ private:
void loopPlayer();
PaStream* stream;
int channels;
int streamChannels;
std::uint32_t streamSampleFormat;
double streamRate;
std::priority_queue<AudioPacket, std::vector<AudioPacket>, AudioPacketsComparator> audioQueue;
std::mutex audioMutex;

View file

@ -11,9 +11,14 @@
#include <vector>
#include "packets/AudioPacket.hpp"
#include "utils/audio.hpp"
Server::Server() {
this->channels = 0;
this->encoding = 0;
this->sampleRate = 0;
// create a new mpg123 handle
int error;
this->mpgHandle = mpg123_new(nullptr, &error);
@ -21,16 +26,18 @@ Server::Server() {
throw std::runtime_error("[Server] Could not create a mpg123 handle.");
// open the mp3 file
// TODO(Faraphel): mp3 file as argument
if (mpg123_open(
this->mpgHandle,
"./assets/Caravan Palace - Wonderland.mp3"
))
// "./assets/Caravan Palace - Wonderland.mp3"
"./assets/Queen - Another One Bites the Dust.mp3"
) != MPG123_OK)
throw std::runtime_error("[Server] Could not open file.");
// get the format of the file
if (mpg123_getformat(
this->mpgHandle,
&this->rate,
&this->sampleRate,
&this->channels,
&this->encoding
) != MPG123_OK)
@ -43,7 +50,7 @@ Server::~Server() {
mpg123_delete(this->mpgHandle);
}
void Server::loop() {
void Server::loop() const {
// get the broadcast address
addrinfo broadcastHints {};
broadcastHints.ai_family = AF_INET6;
@ -79,13 +86,20 @@ void Server::loop() {
&done
) == MPG123_OK) {
// set the target time
// TODO(Faraphel): dynamically change this delay to be the lowest possible
audioPacket.timePlay =
std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds(5000);
// set the audio settings
audioPacket.channels = this->channels;
audioPacket.sampleFormat = encoding_mpg123_to_PulseAudio(this->encoding);
audioPacket.sampleRate = this->sampleRate;
// set the size of the content
audioPacket.contentSize = done;
// broadcast the audio data
if (sendto(
broadcastSocket,
&audioPacket,
@ -100,10 +114,10 @@ void Server::loop() {
std::cout << "[Server] Sent: " << done << " bytes" << std::endl;
// wait 10ms to simulate lag
// TODO(Faraphel): should be extended to simulate live music streaming
// wait for the duration of the audio chunk
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<uint64_t>(
(1 / static_cast<double>(this->rate * this->channels * mpg123_encsize(this->encoding))) *
(1 / static_cast<double>(this->sampleRate * this->channels * mpg123_encsize(this->encoding))) *
1000 *
static_cast<double>(done)
)));
@ -112,16 +126,3 @@ void Server::loop() {
// free the server address
freeaddrinfo(broadcastInfo);
}
long Server::getRate() const {
return this->rate;
}
int Server::getChannels() const {
return this->channels;
}
int Server::getEncoding() const {
return this->encoding;
}

View file

@ -14,28 +14,12 @@ public:
/**
* Indefinitely read and broadcast audio data.
*/
void loop();
/**
* get the current rate of the audio data.
* @return the current rate of the audio data.
*/
long getRate() const;
/**
* get the current number of channels of the audio data.
* @return the current number of channels of the audio data.
*/
int getChannels() const;
/**
* get the current encoding of the audio data.
* @return the current encoding of the audio data.
*/
int getEncoding() const;
void loop() const;
private:
mpg123_handle* mpgHandle;
long rate{};
int channels{};
int encoding{};
long sampleRate;
int channels;
int encoding;
};

View file

@ -20,7 +20,7 @@ int main(int argc, char* argv[]) {
// start the client and server
Server server;
Client client(server.getChannels(), static_cast<double>(server.getRate()));
Client client;
std::thread serverThread(&Server::loop, &server);
std::thread clientThread(&Client::loop, &client);

View file

@ -6,7 +6,12 @@
struct AudioPacket {
// scheduling
// TODO(Faraphel): use a more "fixed" size format ?
std::chrono::time_point<std::chrono::high_resolution_clock> timePlay;
// audio settings
std::uint8_t channels;
std::uint32_t sampleFormat;
std::uint32_t sampleRate;
// content
std::uint16_t contentSize;
std::array<std::uint8_t, 65280> content;

27
source/utils/audio.cpp Normal file
View file

@ -0,0 +1,27 @@
#include "audio.hpp"
#include <stdexcept>
#include <fmt123.h>
#include <portaudio.h>
std::uint32_t encoding_mpg123_to_PulseAudio(const int encoding_mpg123) {
switch (encoding_mpg123) {
case MPG123_ENC_UNSIGNED_8:
return paUInt8;
case MPG123_ENC_SIGNED_8:
return paInt8;
case MPG123_ENC_SIGNED_16:
return paInt16;
case MPG123_ENC_SIGNED_24:
return paInt24;
case MPG123_ENC_SIGNED_32:
return paInt32;
case MPG123_ENC_FLOAT:
case MPG123_ENC_FLOAT_32:
return paFloat32;
default:
throw std::runtime_error("Invalid encoding value.");
}
}

6
source/utils/audio.hpp Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
std::uint32_t encoding_mpg123_to_PulseAudio(int encoding_mpg123);