diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bc1f7d..e74c799 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,12 @@ pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) add_executable(M2-PT-DRP - source/connection.cpp - source/stream.cpp - source/stream.hpp + source/Client.cpp + source/Client.hpp + source/Server.cpp + source/Server.hpp + source/main.cpp + source/main.hpp ) target_include_directories(M2-PT-DRP PRIVATE ${MPG123_INCLUDE_DIRS} diff --git a/source/Client.cpp b/source/Client.cpp new file mode 100644 index 0000000..66ac9d4 --- /dev/null +++ b/source/Client.cpp @@ -0,0 +1,105 @@ +#include "Client.hpp" + +#include +#include +#include +#include +#include +#include +#include + + +Client::Client(const int channels, const double rate) { + this->channels = channels; + + // TODO(Faraphel): make the sampleFormat and the framesPerBuffer arguments. + // open a PortAudio stream + if (Pa_OpenDefaultStream( + &this->stream, + 0, + channels, + paInt16, + rate, + 512, + nullptr, + nullptr + ) != paNoError) + throw std::runtime_error("Could not open PortAudio stream."); +} + +Client::~Client() { + // close the audio stream + Pa_StopStream(this->stream); + Pa_CloseStream(this->stream); +} + + +void Client::loop() { + int error; + + // start the PortAudio stream + if (Pa_StartStream(stream) != paNoError) + throw std::runtime_error("Could not start the PortAudio stream."); + + // create the socket + const int clientSocket = socket( + AF_INET6, + SOCK_DGRAM, + 0 + ); + if (clientSocket < 0) + throw std::runtime_error("Could not create the socket."); + + // get the broadcast address + addrinfo serverHints = {}; + serverHints.ai_family = AF_INET6; + serverHints.ai_socktype = SOCK_DGRAM; + serverHints.ai_protocol = IPPROTO_UDP; + + // TODO(Faraphel): port as argument + addrinfo *serverInfo; + if((error = getaddrinfo( + nullptr, // any source + "5650", // our port + &serverHints, + &serverInfo + )) != 0) + throw std::runtime_error("Could not get the address.\n" + std::string(gai_strerror(error))); + + // bind to this address + if (bind( + clientSocket, + serverInfo->ai_addr, + serverInfo->ai_addrlen + ) < 0) + throw std::runtime_error("Could not bind to the address."); + + // free the server address + freeaddrinfo(serverInfo); + + sockaddr_storage serverAddress {}; + socklen_t serverAddressLength; + std::vector buffer(4096); + + // receive new audio data + while (ssize_t size = recvfrom( + clientSocket, + buffer.data(), + buffer.size(), + 0, + reinterpret_cast(&serverAddress), + &serverAddressLength + )) { + std::cout << "[Client] received: " << size << " bytes" << std::endl; + + // TODO(Faraphel): check for the error ? + error = Pa_WriteStream( + this->stream, + buffer.data(), + size / 2 / this->channels + ); + + // play the audio buffer + // TODO(Faraphel): the number of frames could be improved + } +} diff --git a/source/Client.hpp b/source/Client.hpp new file mode 100644 index 0000000..34225b6 --- /dev/null +++ b/source/Client.hpp @@ -0,0 +1,17 @@ +#pragma once + + +#include + + +class Client { +public: + explicit Client(int channels, double rate); + ~Client(); + + void loop(); + +private: + PaStream* stream = nullptr; + int channels; +}; diff --git a/source/Server.cpp b/source/Server.cpp new file mode 100644 index 0000000..f747669 --- /dev/null +++ b/source/Server.cpp @@ -0,0 +1,118 @@ +#include "Server.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +Server::Server() { + // create a new mpg123 handle + int error; + this->mpgHandle = mpg123_new(nullptr, &error); + + // open the mp3 file + if (mpg123_open( + this->mpgHandle, + "./assets/Caravan Palace - Wonderland.mp3" + )) + throw std::runtime_error("Could not open file."); + + // get the format of the file + if (mpg123_getformat( + this->mpgHandle, + &this->rate, + &this->channels, + &this->encoding + ) != MPG123_OK) + throw std::runtime_error("Could not get the format of the file."); +} + +Server::~Server() { + // delete the mpg123 handle + mpg123_close(this->mpgHandle); + mpg123_delete(this->mpgHandle); +} + + +void Server::loop() { + int error; + + // create the socket + const int serverSocket = socket( + AF_INET6, + SOCK_DGRAM, + 0 + ); + if (serverSocket < 0) + throw std::runtime_error("Could not create the socket."); + + // get the broadcast address + addrinfo clientHints = {}; + clientHints.ai_family = AF_INET6; + clientHints.ai_socktype = SOCK_DGRAM; + clientHints.ai_protocol = IPPROTO_UDP; + + // TODO(Faraphel): ip / port as argument + addrinfo *clientInfo; + if((error = getaddrinfo( + "localhost", + // "ff02::1", // IPv6 local broadcast + "5650", // our port + &clientHints, + &clientInfo + )) != 0) + throw std::runtime_error("Could not get the address.\n" + std::string(gai_strerror(error))); + + + // read the file + std::vector buffer(4096); + std::size_t done; + + while (mpg123_read( + this->mpgHandle, + buffer.data(), + buffer.size(), + &done) == MPG123_OK + ) { + // send the content of the file + sendto( + serverSocket, + buffer.data(), + buffer.size(), + 0, + clientInfo->ai_addr, + clientInfo->ai_addrlen + ); + + std::cout << "[Server] sent : " << done << " bytes" << std::endl; + + // wait 10ms to simulate lag + // TODO(Faraphel): should be extended to simulate live music streaming + std::this_thread::sleep_for(std::chrono::milliseconds(static_cast( + (1 / static_cast(this->rate * this->channels * mpg123_encsize(this->encoding))) * + 1000 * + static_cast(done) + ))); + } + + // free the server address + freeaddrinfo(clientInfo); +} + + +long Server::getRate() const { + return this->rate; +} + +int Server::getChannels() const { + return this->channels; +} + +int Server::getEncoding() const { + return this->encoding; +} diff --git a/source/Server.hpp b/source/Server.hpp new file mode 100644 index 0000000..4b0d869 --- /dev/null +++ b/source/Server.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + + +class Server { +public: + explicit Server(); + ~Server(); + void loop(); + + long getRate() const; + int getChannels() const; + int getEncoding() const; + +private: + mpg123_handle* mpgHandle; + + long rate; + int channels; + int encoding; +}; diff --git a/source/connection.cpp b/source/connection.cpp deleted file mode 100644 index d24e7b4..0000000 --- a/source/connection.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "connection.hpp" - - -#include -#include -#include -#include -#include -#include - - -/* -int main(int argc, char* argv[]) { - // create an IPv6 socket based on datagram (UDP) - const int serverSocket = socket( - AF_INET6, - SOCK_DGRAM, - 0 - ); - - // get the broadcast address - addrinfo broadcastAddressConfig = {}; - broadcastAddressConfig.ai_family = AF_INET6; - broadcastAddressConfig.ai_socktype = SOCK_DGRAM; - broadcastAddressConfig.ai_protocol = IPPROTO_UDP; - - addrinfo *broadcastAddress; - if(!getaddrinfo( - "ff02::1", // multicast address - "5650", // our port - &broadcastAddressConfig, - &broadcastAddress - )) - throw std::runtime_error("Could not get the address."); - - const std::string message = "Salut !"; - - // broadcast the message - if (sendto( - serverSocket, - message.c_str(), - message.size(), - 0, - broadcastAddress->ai_addr, - broadcastAddress->ai_addrlen - ) == -1) - throw std::runtime_error("Could not broadcast the message."); - - return 0; -} -*/ diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..ad16d77 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,37 @@ +#include "main.hpp" + +#include +#include +#include +#include + +#include "Client.hpp" +#include "Server.hpp" + + +int main(int argc, char* argv[]) { + // initialize the mpg123 library + if (mpg123_init() != MPG123_OK) + throw std::runtime_error("Error while initializing mpg123."); + + // initialize the PortAudio library + if (Pa_Initialize() != paNoError) + throw std::runtime_error("Could not initialize PortAudio."); + + // start the client and server + Server server; + Client client ( + server.getChannels(), + static_cast(server.getRate()) + ); + + std::thread serverThread(&Server::loop, server); + std::thread clientThread(&Client::loop, client); + + serverThread.join(); + clientThread.join(); + + // terminate the libraries + Pa_Terminate(); + mpg123_exit(); +} diff --git a/source/connection.hpp b/source/main.hpp similarity index 100% rename from source/connection.hpp rename to source/main.hpp diff --git a/source/stream.cpp b/source/stream.cpp deleted file mode 100644 index 7b5b2db..0000000 --- a/source/stream.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "stream.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -int main(int argc, char* argv[]) { - // initialize the mpg123 library - if (mpg123_init() != MPG123_OK) - throw std::runtime_error("Error while initializing mpg123."); - - // create a new mpg123 handle - int error; - mpg123_handle* mpgHandle = mpg123_new(nullptr, &error); - - // open the mp3 file - if (mpg123_open(mpgHandle, "./assets/Caravan Palace - Wonderland.mp3")) - throw std::runtime_error("Could not open file."); - - // get the format of the file - long rate; - int channels; - int encoding; - mpg123_getformat(mpgHandle, &rate, &channels, &encoding); - printf("rate: %ld, channels: %d, encoding: %d\n", rate, channels, encoding); - - // --- - - // initialize the PortAudio library - if (Pa_Initialize() != paNoError) - throw std::runtime_error("Could not initialize PortAudio."); - - // open the PortAudio stream - PaStream* stream; - if (Pa_OpenDefaultStream( - &stream, - 0, - channels, - paInt16, - static_cast(rate), - 512, - nullptr, - nullptr - ) != paNoError) - throw std::runtime_error("Could not open PortAudio stream."); - - // start the PortAudio stream - if (Pa_StartStream(stream) != paNoError) - throw std::runtime_error("Could not start the PortAudio stream."); - - // --- - - // read the file - std::vector buffer(4096); - std::size_t done; - std::size_t i = 0; - - while ((error = mpg123_read(mpgHandle, buffer.data(), buffer.size(), &done)) == MPG123_OK) { - // write the audio data to the PortAudio stream - if ((error = Pa_WriteStream(stream, buffer.data(), done / 2 / channels)) != paNoError) - throw std::runtime_error( - "Could not write audio data in the PortAudio stream.\n" + - std::string(Pa_GetErrorText(error)) - ); - - printf("write (%04lu)\n", i++); - } - - // display the error message - std::printf(mpg123_plain_strerror(error)); - - // Stop the audio stream - Pa_StopStream(stream); - Pa_CloseStream(stream); - - // delete the mpg123 handle - mpg123_close(mpgHandle); - mpg123_delete(mpgHandle); - - // free the libraries - Pa_Terminate(); - mpg123_exit(); - - return error; -} diff --git a/source/stream.hpp b/source/stream.hpp deleted file mode 100644 index 6f70f09..0000000 --- a/source/stream.hpp +++ /dev/null @@ -1 +0,0 @@ -#pragma once