audio settings can now change dynamically
This commit is contained in:
parent
21f14948b9
commit
c3c452ff5d
9 changed files with 117 additions and 58 deletions
|
@ -18,6 +18,8 @@ add_executable(M2-PT-DRP
|
||||||
source/Server.cpp
|
source/Server.cpp
|
||||||
source/Server.hpp
|
source/Server.hpp
|
||||||
source/packets/AudioPacket.hpp
|
source/packets/AudioPacket.hpp
|
||||||
|
source/utils/audio.cpp
|
||||||
|
source/utils/audio.hpp
|
||||||
)
|
)
|
||||||
target_include_directories(M2-PT-DRP PRIVATE
|
target_include_directories(M2-PT-DRP PRIVATE
|
||||||
${MPG123_INCLUDE_DIRS}
|
${MPG123_INCLUDE_DIRS}
|
||||||
|
|
|
@ -13,24 +13,45 @@
|
||||||
#include "packets/AudioPacket.hpp"
|
#include "packets/AudioPacket.hpp"
|
||||||
|
|
||||||
|
|
||||||
Client::Client(const int channels, const double rate) {
|
Client::Client() {
|
||||||
this->stream = nullptr;
|
this->stream = nullptr;
|
||||||
this->audioLock = std::unique_lock(this->audioMutex);
|
this->audioLock = std::unique_lock(this->audioMutex);
|
||||||
this->channels = channels;
|
|
||||||
|
|
||||||
// TODO(Faraphel): make the sampleFormat an argument.
|
this->streamChannels = 0;
|
||||||
// open a PortAudio stream
|
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(
|
if (const PaError error = Pa_OpenDefaultStream(
|
||||||
&this->stream,
|
&this->stream,
|
||||||
0,
|
0,
|
||||||
channels,
|
channels,
|
||||||
paInt16,
|
sampleFormat,
|
||||||
rate,
|
sampleRate,
|
||||||
paFramesPerBufferUnspecified,
|
paFramesPerBufferUnspecified,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr
|
nullptr
|
||||||
) != paNoError)
|
) != paNoError)
|
||||||
throw std::runtime_error("[Client] Could not open the stream: " + std::string(Pa_GetErrorText(error)));
|
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() {
|
Client::~Client() {
|
||||||
|
@ -126,13 +147,18 @@ void Client::loopPlayer() {
|
||||||
[this] { return !this->audioQueue.empty(); }
|
[this] { return !this->audioQueue.empty(); }
|
||||||
);
|
);
|
||||||
// get the most recent audio chunk
|
// 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
|
// wait until it must be played
|
||||||
std::this_thread::sleep_until(audioPacket.timePlay);
|
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;
|
std::cout << "[Client] Playing: " << audioPacket.timePlay << std::endl;
|
||||||
|
|
||||||
// immediately stop playing music
|
// 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)));
|
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
|
// 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(
|
const int error = Pa_WriteStream(
|
||||||
this->stream,
|
this->stream,
|
||||||
audioPacket.content.data(),
|
audioPacket.content.data(),
|
||||||
audioPacket.contentSize / 2 / this->channels
|
audioPacket.contentSize / 2 / this->streamChannels
|
||||||
);
|
);
|
||||||
switch (error) {
|
switch (error) {
|
||||||
// success
|
// success
|
||||||
case paNoError:
|
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.
|
// causing a very small period where no noise will be played.
|
||||||
case paOutputUnderflowed:
|
case paOutputUnderflowed:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -24,13 +24,17 @@ struct AudioPacketsComparator {
|
||||||
*/
|
*/
|
||||||
class Client {
|
class Client {
|
||||||
public:
|
public:
|
||||||
/**
|
explicit Client();
|
||||||
* :param channels: the number of channel in the audio
|
|
||||||
* :param rate: the rate of the audio
|
|
||||||
*/
|
|
||||||
explicit Client(int channels, double rate);
|
|
||||||
~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.
|
* Indefinitely receive and play audio data.
|
||||||
*/
|
*/
|
||||||
|
@ -48,7 +52,9 @@ private:
|
||||||
void loopPlayer();
|
void loopPlayer();
|
||||||
|
|
||||||
PaStream* stream;
|
PaStream* stream;
|
||||||
int channels;
|
int streamChannels;
|
||||||
|
std::uint32_t streamSampleFormat;
|
||||||
|
double streamRate;
|
||||||
std::priority_queue<AudioPacket, std::vector<AudioPacket>, AudioPacketsComparator> audioQueue;
|
std::priority_queue<AudioPacket, std::vector<AudioPacket>, AudioPacketsComparator> audioQueue;
|
||||||
|
|
||||||
std::mutex audioMutex;
|
std::mutex audioMutex;
|
||||||
|
|
|
@ -11,9 +11,14 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packets/AudioPacket.hpp"
|
#include "packets/AudioPacket.hpp"
|
||||||
|
#include "utils/audio.hpp"
|
||||||
|
|
||||||
|
|
||||||
Server::Server() {
|
Server::Server() {
|
||||||
|
this->channels = 0;
|
||||||
|
this->encoding = 0;
|
||||||
|
this->sampleRate = 0;
|
||||||
|
|
||||||
// create a new mpg123 handle
|
// create a new mpg123 handle
|
||||||
int error;
|
int error;
|
||||||
this->mpgHandle = mpg123_new(nullptr, &error);
|
this->mpgHandle = mpg123_new(nullptr, &error);
|
||||||
|
@ -21,16 +26,18 @@ Server::Server() {
|
||||||
throw std::runtime_error("[Server] Could not create a mpg123 handle.");
|
throw std::runtime_error("[Server] Could not create a mpg123 handle.");
|
||||||
|
|
||||||
// open the mp3 file
|
// open the mp3 file
|
||||||
|
// TODO(Faraphel): mp3 file as argument
|
||||||
if (mpg123_open(
|
if (mpg123_open(
|
||||||
this->mpgHandle,
|
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.");
|
throw std::runtime_error("[Server] Could not open file.");
|
||||||
|
|
||||||
// get the format of the file
|
// get the format of the file
|
||||||
if (mpg123_getformat(
|
if (mpg123_getformat(
|
||||||
this->mpgHandle,
|
this->mpgHandle,
|
||||||
&this->rate,
|
&this->sampleRate,
|
||||||
&this->channels,
|
&this->channels,
|
||||||
&this->encoding
|
&this->encoding
|
||||||
) != MPG123_OK)
|
) != MPG123_OK)
|
||||||
|
@ -43,7 +50,7 @@ Server::~Server() {
|
||||||
mpg123_delete(this->mpgHandle);
|
mpg123_delete(this->mpgHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::loop() {
|
void Server::loop() const {
|
||||||
// get the broadcast address
|
// get the broadcast address
|
||||||
addrinfo broadcastHints {};
|
addrinfo broadcastHints {};
|
||||||
broadcastHints.ai_family = AF_INET6;
|
broadcastHints.ai_family = AF_INET6;
|
||||||
|
@ -79,13 +86,20 @@ void Server::loop() {
|
||||||
&done
|
&done
|
||||||
) == MPG123_OK) {
|
) == MPG123_OK) {
|
||||||
// set the target time
|
// set the target time
|
||||||
|
// TODO(Faraphel): dynamically change this delay to be the lowest possible
|
||||||
audioPacket.timePlay =
|
audioPacket.timePlay =
|
||||||
std::chrono::high_resolution_clock::now() +
|
std::chrono::high_resolution_clock::now() +
|
||||||
std::chrono::milliseconds(5000);
|
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
|
// set the size of the content
|
||||||
audioPacket.contentSize = done;
|
audioPacket.contentSize = done;
|
||||||
|
|
||||||
|
// broadcast the audio data
|
||||||
if (sendto(
|
if (sendto(
|
||||||
broadcastSocket,
|
broadcastSocket,
|
||||||
&audioPacket,
|
&audioPacket,
|
||||||
|
@ -100,10 +114,10 @@ void Server::loop() {
|
||||||
|
|
||||||
std::cout << "[Server] Sent: " << done << " bytes" << std::endl;
|
std::cout << "[Server] Sent: " << done << " bytes" << std::endl;
|
||||||
|
|
||||||
// wait 10ms to simulate lag
|
|
||||||
// TODO(Faraphel): should be extended to simulate live music streaming
|
// 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>(
|
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 *
|
1000 *
|
||||||
static_cast<double>(done)
|
static_cast<double>(done)
|
||||||
)));
|
)));
|
||||||
|
@ -112,16 +126,3 @@ void Server::loop() {
|
||||||
// free the server address
|
// free the server address
|
||||||
freeaddrinfo(broadcastInfo);
|
freeaddrinfo(broadcastInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
long Server::getRate() const {
|
|
||||||
return this->rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Server::getChannels() const {
|
|
||||||
return this->channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Server::getEncoding() const {
|
|
||||||
return this->encoding;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,28 +14,12 @@ public:
|
||||||
/**
|
/**
|
||||||
* Indefinitely read and broadcast audio data.
|
* Indefinitely read and broadcast audio data.
|
||||||
*/
|
*/
|
||||||
void loop();
|
void loop() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mpg123_handle* mpgHandle;
|
mpg123_handle* mpgHandle;
|
||||||
|
|
||||||
long rate{};
|
long sampleRate;
|
||||||
int channels{};
|
int channels;
|
||||||
int encoding{};
|
int encoding;
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
// start the client and server
|
// start the client and server
|
||||||
Server server;
|
Server server;
|
||||||
Client client(server.getChannels(), static_cast<double>(server.getRate()));
|
Client client;
|
||||||
|
|
||||||
std::thread serverThread(&Server::loop, &server);
|
std::thread serverThread(&Server::loop, &server);
|
||||||
std::thread clientThread(&Client::loop, &client);
|
std::thread clientThread(&Client::loop, &client);
|
||||||
|
|
|
@ -6,7 +6,12 @@
|
||||||
|
|
||||||
struct AudioPacket {
|
struct AudioPacket {
|
||||||
// scheduling
|
// scheduling
|
||||||
|
// TODO(Faraphel): use a more "fixed" size format ?
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> timePlay;
|
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
|
// content
|
||||||
std::uint16_t contentSize;
|
std::uint16_t contentSize;
|
||||||
std::array<std::uint8_t, 65280> content;
|
std::array<std::uint8_t, 65280> content;
|
||||||
|
|
27
source/utils/audio.cpp
Normal file
27
source/utils/audio.cpp
Normal 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
6
source/utils/audio.hpp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
std::uint32_t encoding_mpg123_to_PulseAudio(int encoding_mpg123);
|
Loading…
Reference in a new issue