I'm trying to send a .wav file via UDP sockets from the server to the client and playing it back from the client side.
The file is 48kHz and 16 bits and has duration of 25 sec.
Since the server.c is a small code section inside a larger C-RAN code module I'm checking it by passing a wav file from the stdin:
./SERVER < filename.wav
and then as soon as I execute the client.c, the server starts sending the datagrams and the client.c starts playing them back. Everything is fine bu the audio is played back at a much faster speed, its like playing it back at twice the correct speed. Sometimes it starts playing back correctlyI but after a few seconds it speeds up.
I've passed a large .txt file instead of the .wav file and the client recieves all the .txt file, from start to end.
Is there anything that I'm missing that I should take into account in order to playback the audio wav file correctly?
client.c code:
// Remote Radio Head - client side
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <alsa/asoundlib.h>
#include <dirent.h>
/* CONSTANTS */
#define PORT 8080
#define MAXLINE 1024
#define PAYLOAD_SIZE 128
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
/*************************************************
* DRIVER CODE *
*************************************************/
int main(int argc, char *argv[]) {
// Sockets variables
int sockfd;
char buffer1[MAXLINE];
char *data = "Hello from client, waiting for audio to playback";
struct sockaddr_in servaddr;
// ALSA playback variables
long loops;
int rc;
int size;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
int dir;
snd_pcm_uframes_t frames;
char *buffer2;
/*********************************************
* ALSA DRIVERS SETUP *
*********************************************/
// Open PCM device for playback.
rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
exit(1);
}
// Allocate a hardware parameters object.
snd_pcm_hw_params_alloca(¶ms);
// Fill it in with default values.
snd_pcm_hw_params_any(handle, params);
// Set the desired hardware parameters.
// Interleaved mode
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
// Signed 16-bit little-endian format
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
// Two channels (stereo)
snd_pcm_hw_params_set_channels(handle, params, 2);
// bits/second sampling rate
val = 48000;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
// Set period size to 32 frames.
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
// Write the parameters to the driver
rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
exit(1);
}
// Use a buffer large enough to hold one period
snd_pcm_hw_params_get_period_size(params, &frames, &dir);
size = frames * 4; // 2 bytes/sample, 2 channels
buffer2 = (char *) malloc(size);
// We want to loop for 25 seconds
snd_pcm_hw_params_get_period_time(params, &val, &dir);
// 25 seconds in microseconds divided by period time
loops = 25000000/val;
/*********************************************
* CREATING SOCKET FILE DESCRIPTOR *
*********************************************/
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
// Filling server information
servaddr.sin_family = AF_INET; //ipv4
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = INADDR_ANY; //Any server
/********************************************
* SEND MSG TO SERVER *
********************************************/
//sockfd: File descriptor of socket
//buffer: Application buffer cointaining the data to be sent
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//dest_addr: Structure containing address of destination
//addrlen: Size of dest_addr structure
sendto(sockfd, (const char *)data, strlen(data), MSG_CONFIRM, (const struct sockaddr *) &servaddr, sizeof(servaddr));
printf("Waiting for audio!!!\n");
/********************************************
* RECIEVE MSG FROM SERVER *
********************************************/
//sockfd: file descriptor of socket
//buffer: Apllication buffer in which to recieve data
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//src_addr: Structure containing source address is returned
//addrlen: Variable in which size of src_addr structure is returned
int n, len;
/* We allocate memory to store the payload of the incoming datagram. */
char *payload = (char *)malloc(PAYLOAD_SIZE*sizeof(char));
while(1){
//We receive datagram
int bytes_read = recvfrom(sockfd, (void *)payload, PAYLOAD_SIZE, MSG_WAITALL, (struct sockaddr *) &servaddr, &len);
fprintf(stderr,"r");
fflush(stderr);
// We write the datagram to stdout.
//write(1, (void *)payload, bytes_read);
//fprintf(stderr,"w");
//fflush(stderr);
while (loops > 0) {
loops--;
rc = read(sockfd, buffer2, size); //Read audio file
if (rc == 0)
{
fprintf(stderr, "end of file on input\n");
break;
}
else if (rc != size)
{
fprintf(stderr, "short read: read %d bytes\n", rc);
}
rc = snd_pcm_writei(handle, buffer2, frames);
if (rc == -EPIPE)
{
// EPIPE means underrun
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
}
else if (rc < 0)
{
fprintf(stderr, "error from writei: %s\n",
snd_strerror(rc));
}
else if (rc != (int)frames)
{
fprintf(stderr, "short write, write %d frames\n", rc);
}
}
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer2);
buffer1[n] = '\0';
printf("Server : %s\n", buffer1);
}
//n = recvfrom(sockfd, (char *)buffer1, MAXLINE, MSG_WAITALL, (struct sockaddr *) &servaddr, &len);
close(sockfd); //close file descriptor
return 0;
}
server.c
// Server side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//CONSTANTS
#define PORT 8080
#define MAXLINE 1024
#define PAYLOAD_SIZE 128
// Driver code
int main(int argc, char *argv[]) {
int sockfd;
char buffer[MAXLINE];
char *msg = "Hello from server, ready to send audio";
struct sockaddr_in servaddr, cliaddr;
/*********************************************
* CREATING SOCKET FILE DESCRIPTOR *
*********************************************/
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { /*AL_INET: ipv4; SOCK_DGRAM: UPD; 0: default protocol*/
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr)); //Allocate memory for structure
memset(&cliaddr, 0, sizeof(cliaddr));
// Filling server information
servaddr.sin_family = AF_INET; // IPv4
servaddr.sin_addr.s_addr = INADDR_ANY;//Any client
servaddr.sin_port = htons(PORT);
/********************************************
* BIND THE SOCKET WITH THE SERVER ADDRESS *
********************************************/
//sockfd: File descriptor of socket to be binded
//addr: Structure in which address to be binded to is specified
//addrlen: Size of addr structure
if ( bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 )
{
perror("bind failed");
exit(EXIT_FAILURE);
}
/********************************************
* RECIEVE MSG FROM CLIENT *
********************************************/
//sockfd: file descriptor of socket
//buffer: Apllication buffer in which to recieve data
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//src_addr: Structure containing source address is returned
//addrlen: Variable in which size of src_addr structure is returned
int len, n;
len = sizeof(cliaddr); //len is value/resuslt
n = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, ( struct sockaddr *) &cliaddr, &len);
buffer[n] = '\0';
printf("Client : %s\n", buffer);
/********************************************
* SEND MSG TO CLIENT *
********************************************/
//sockfd: File descriptor of socket
//buffer: Application buffer cointaining the data to be sent
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//dest_addr: Structure containing address of destination
//addrlen: Size of dest_addr structure
// We allocate memory for the datagrams payload
char *payload = (char *)malloc(PAYLOAD_SIZE*sizeof(char));
printf("Sending audio in 3, 2, 1.....\n");
while(1){
// Reading from the std in
int bytes_read = read(0, (void *)payload, PAYLOAD_SIZE);
fprintf(stderr, "r");
fflush(stderr);
if(bytes_read < 1) break;
// We write the datagram to stdout.
write(1, (void *)payload, bytes_read);
fprintf(stderr, "w");
fflush(stderr);
//Sending datagram
sendto(sockfd, (void *)payload, bytes_read, 0, (struct sockaddr *) &cliaddr, len);
fprintf(stderr, "s");
fflush(stderr);
}
//sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
return 0;
}
The problem in your implementation is that the transfer of the music data is one way from the server to the client. When the client sends the first request, the server starts broadcasting the audio stream as fast as it can. As a result the client will loose some packets. If only one in two packets gets actually written to the audio device, it looks like that the music is played twice as fast. You can easily see that you are loosing packets if you sum all the bytes_read
in the client. It will be much smaller than the actual file size.
Also it's not clear why in the client you first recvfrom
socket into payload
and then read
into buffer2
. In theory only the first operation is required and then you write from payload
to the audio device.
If you want to implement the streaming in the proper way you need to implement a proper buffering solution in the client and also some speed throttling on the server, to avoid sending data at a speed much higher than needed.
If you want to fix your code in an easy way, one possibility would be to add an ACK that the client sends to the server after receiving one packet. The server will wait for the client ACK before sending the next packet. This will more or less make the UDP protocol a TCP protocol.
I have modified your code a little bit to show you what I mwan. With this code you are able to playback correctly the wav file. It's not perfect, but at least it should give you an idea of what is the problem with your code
server.c
// gcc -o server server.c
// Server side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// CONSTANTS
#define PORT 8080
#define MAXLINE 1024
#define PAYLOAD_SIZE 2048
int main(int argc, char *argv[])
{
int sockfd;
char buffer[MAXLINE];
const char* msg = "SERVER: Sending audio complete";
struct sockaddr_in servaddr, cliaddr;
/*********************************************
* CREATING SOCKET FILE DESCRIPTOR *
*********************************************/
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
/*AL_INET: ipv4; SOCK_DGRAM: UPD; 0: default protocol*/
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr)); // Allocate memory for structure
memset(&cliaddr, 0, sizeof(cliaddr));
// Filling server information
servaddr.sin_family = AF_INET; // IPv4
servaddr.sin_addr.s_addr = INADDR_ANY; // Any client
servaddr.sin_port = htons(PORT);
/********************************************
* BIND THE SOCKET WITH THE SERVER ADDRESS *
********************************************/
//sockfd: File descriptor of socket to be binded
//addr: Structure in which address to be binded to is specified
//addrlen: Size of addr structure
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
/********************************************
* RECIEVE MSG FROM CLIENT *
********************************************/
//sockfd: file descriptor of socket
//buffer: Apllication buffer in which to recieve data
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//src_addr: Structure containing source address is returned
//addrlen: Variable in which size of src_addr structure is returned
int len, n;
len = sizeof(cliaddr); // len is value/result
printf("Waiting for client connection...\n");
n = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("%s\n", buffer);
/********************************************
* SEND MSG TO CLIENT *
********************************************/
//sockfd: File descriptor of socket
//buffer: Application buffer cointaining the data to be sent
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//dest_addr: Structure containing address of destination
//addrlen: Size of dest_addr structure
// We allocate memory for the datagrams payload
char* payload = (char*)malloc(PAYLOAD_SIZE * sizeof(char));
printf("Sending audio...\n");
while (1) {
// Reading from the stdin
int bytes_read = read(0, (void*)payload, PAYLOAD_SIZE);
if (bytes_read <= 0)
break;
// Sending datagram
sendto(sockfd, (void*)payload, bytes_read, 0, (struct sockaddr *)&cliaddr, len);
// Waiting for ACK
n = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
}
return 0;
}
client.c
// gcc -o client client.c -lasound
// Remote Radio Head - client side
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <alsa/asoundlib.h>
#include <dirent.h>
/* CONSTANTS */
#define PORT 8080
#define MAXLINE 1024
#define FRAME_SIZE 512
#define PAYLOAD_SIZE (FRAME_SIZE * 4)
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
int main(int argc, char *argv[])
{
// Sockets variables
int sockfd;
const char* ACK = "ack";
const char* START_BROADCAST = "CLIENT: waiting for audio to playback";
struct sockaddr_in servaddr;
// ALSA playback variables
int rc;
int size;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
int dir;
snd_pcm_uframes_t frames;
/*********************************************
* ALSA DRIVERS SETUP *
*********************************************/
// Open PCM device for playback.
rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
exit(1);
}
// Allocate a hardware parameters object.
snd_pcm_hw_params_alloca(¶ms);
// Fill it in with default values.
snd_pcm_hw_params_any(handle, params);
// Set the desired hardware parameters.
// Interleaved mode
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
// Signed 16-bit little-endian format
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
// Two channels (stereo)
snd_pcm_hw_params_set_channels(handle, params, 2);
// bits/second sampling rate
val = 48000;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
// Set period size to 32 frames.
frames = FRAME_SIZE;
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
// Write the parameters to the driver
rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
exit(1);
}
/*********************************************
* CREATING SOCKET FILE DESCRIPTOR *
*********************************************/
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
// Filling server information
servaddr.sin_family = AF_INET; // ipv4
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = INADDR_ANY; // Any server
/********************************************
* SEND MSG TO SERVER *
********************************************/
//sockfd: File descriptor of socket
//buffer: Application buffer cointaining the data to be sent
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//dest_addr: Structure containing address of destination
//addrlen: Size of dest_addr structure
sendto(sockfd, START_BROADCAST, strlen(START_BROADCAST), 0, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Waiting for audio!!!\n");
/********************************************
* RECIEVE MSG FROM SERVER *
********************************************/
//sockfd: file descriptor of socket
//buffer: Apllication buffer in which to recieve data
//len: Size of buffer
//flags: Bitwise OR flags to modify socket behaviour
//src_addr: Structure containing source address is returned
//addrlen: Variable in which size of src_addr structure is returned
int n, len;
/* We allocate memory to store the payload of the incoming datagram. */
char *payload = (char *)malloc(PAYLOAD_SIZE * sizeof(char));
while (1)
{
len = PAYLOAD_SIZE;
int bytes_read = recvfrom(sockfd, (void *)payload, PAYLOAD_SIZE, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
rc = snd_pcm_writei(handle, payload, frames);
if (rc == -EPIPE) {
// EPIPE means underrun
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
} else if (rc < 0) {
fprintf(stderr, "error from writei: %s\n",
snd_strerror(rc));
} else if (rc != (int)frames) {
fprintf(stderr, "short write, write %d frames\n", rc);
}
// Send ACK
sendto(sockfd, ACK, strlen(ACK), 0, (const struct sockaddr *)&servaddr, len);
}
snd_pcm_drain(handle);
snd_pcm_close(handle);
close(sockfd); //close file descriptor
return 0;
}