splitted the program into a server and a client, communicating through sockets.

This commit is contained in:
faraphel 2024-10-13 23:07:08 +02:00
parent 270017b1d3
commit 145d4b8c2c
10 changed files with 304 additions and 148 deletions

View file

@ -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}

105
source/Client.cpp Normal file
View file

@ -0,0 +1,105 @@
#include "Client.hpp"
#include <cstring>
#include <cstdint>
#include <iostream>
#include <netdb.h>
#include <stdexcept>
#include <vector>
#include <sys/socket.h>
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<std::uint8_t> buffer(4096);
// receive new audio data
while (ssize_t size = recvfrom(
clientSocket,
buffer.data(),
buffer.size(),
0,
reinterpret_cast<sockaddr *>(&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
}
}

17
source/Client.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <portaudio.h>
class Client {
public:
explicit Client(int channels, double rate);
~Client();
void loop();
private:
PaStream* stream = nullptr;
int channels;
};

118
source/Server.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "Server.hpp"
#include <iostream>
#include <cstdint>
#include <mpg123.h>
#include <netdb.h>
#include <stdexcept>
#include <thread>
#include <sys/socket.h>
#include <vector>
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<std::uint8_t> 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<uint64_t>(
(1 / static_cast<double>(this->rate * this->channels * mpg123_encsize(this->encoding))) *
1000 *
static_cast<double>(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;
}

21
source/Server.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <mpg123.h>
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;
};

View file

@ -1,51 +0,0 @@
#include "connection.hpp"
#include <chrono>
#include <stdexcept>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/*
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;
}
*/

37
source/main.cpp Normal file
View file

@ -0,0 +1,37 @@
#include "main.hpp"
#include <mpg123.h>
#include <portaudio.h>
#include <stdexcept>
#include <thread>
#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<double>(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();
}

View file

@ -1,93 +0,0 @@
#include "stream.hpp"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <openssl/aes.h>
#include <mpg123.h>
#include <portaudio.h>
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<double>(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<std::uint8_t> 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;
}

View file

@ -1 +0,0 @@
#pragma once