我正在尝试通过UDP套接字从服务器向客户端发送.wav文件,并从客户端播放它。
文件为48kHz和16位,持续时间为25秒。
由于server.c是较大的C-RAN代码模块中的一小段代码,所以我通过传递来自stdin的wav文件进行检查:
./SERVER < filename.wav
然后我执行client.c之后,服务器开始发送数据报,而client.c开始播放它们。音频以更快的速度回放时一切都很好,就像以正确速度的两倍回放音频一样。有时它可以正确开始播放,但是几秒钟后速度加快。
我传递了一个大的.txt文件而不是.wav文件,并且客户端从头到尾都接收了所有.txt文件。
为了正确播放音频wav文件,我是否应该考虑一些缺少的东西?
client.c代码:
// 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;
}
实施中的问题是音乐数据的传输是从服务器到客户端的一种方式。当客户端发送第一个请求时,服务器将尽可能快地开始广播音频流。结果,客户端将丢失一些数据包。如果只有二分之一的数据包实际写入了音频设备,则音乐播放速度似乎快了两倍。如果将客户端中的所有bytes_read
加在一起,就可以很容易地看到丢失了数据包。它会比实际文件大小小得多。
同样也不清楚为什么在客户端中首先将recvfrom
套接字插入payload
,然后将read
插入buffer2
。从理论上讲,只需要执行第一项操作,然后从payload
写入音频设备。
如果要以正确的方式实现流传输,则需要在客户端中实现适当的缓冲解决方案,并在服务器上进行一定的速度调节,以避免以远远高于所需的速度发送数据。
如果您想以一种简单的方式来修复代码,一种可能是添加客户端在收到一个数据包后发送给服务器的ACK。服务器将在发送下一个数据包之前等待客户端ACK。这或多或少会使UDP协议成为TCP协议。
我对您的代码做了一些修改,以向您展示我的想法。使用此代码,您可以正确播放wav文件。它不是完美的,但至少它应该使您了解代码的问题是什么]
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;
}