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.hpp
|
||||
source/packets/AudioPacket.hpp
|
||||
source/utils/audio.cpp
|
||||
source/utils/audio.hpp
|
||||
)
|
||||
target_include_directories(M2-PT-DRP PRIVATE
|
||||
${MPG123_INCLUDE_DIRS}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
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