[UDP上的wav文件无法在带有Alsa-lib的Unix中正确播放

问题描述 投票:1回答:1

我正在尝试通过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(&params);
    // 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; 
} 
c sockets unix audio alsa
1个回答
1
投票

实施中的问题是音乐数据的传输是从服务器到客户端的一种方式。当客户端发送第一个请求时,服务器将尽可能快地开始广播音频流。结果,客户端将丢失一些数据包。如果只有二分之一的数据包实际写入了音频设备,则音乐播放速度似乎快了两倍。如果将客户端中的所有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(&params);
    // 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;
}

© www.soinside.com 2019 - 2024. All rights reserved.