linux下 基于libmad的socket多用户mp3音频在线播放服务器

标签: socket  linux  流媒体服务器

在众多大神的帮助下,这个在线播放流媒体服务器终于完成啦。。。。
这个mp3流媒体服务器设计的思路是,服务器程序server用多线程实现和多个客户端的通信(这是必然的),然后发送给客户端当前的音频列表公客户端选择,之后根据k客户端的选择给多个客户端传输相应mp3文件的数据,同时,客户端进行实时地音频解码并播放。
关于libmad开源mp3音频解码库的使用,见上一篇博客吧。。。。
在服务器程序这一端,首先我们要获取当前的音频列表。这里我们用到了glob函数来获取文件的列表。glob() 函数返回匹配指定模式的文件名或目录。 该函数返回一个包含有匹配文件 / 目录的数组。如果出错返回 false。
下面我们在终端中输入 man glob 来看一下glob函数的手册:
int glob(const char *pattern // 文件路径, int flags // 匹配模式,
int (*errfunc) (const char *epath, int eerrno) // 查看错误信息,不需要 则为NULL,
glob_t *pglob // 注意是glob变量的指针);
void globfree(glob_t *pglob);
其中,glob_t 变量是一个结构体,在手册中我们即可看到:
glob变量:
// ** 表示列表 (二维数组,可以用for循环查看其元素)
typedef struct {
size_t gl_pathc; /* Count of paths matched so far */ //迄今为止匹配的路径个数
char *gl_pathv; / List of matched pathnames. */ // 匹配的路径列表
size_t gl_offs; /* Slots to reserve in gl_pathv. */
} glob_t;
而使用for循环即可看到文件列表,之后globfree释放空间。
下面是一个简单的例程,可以实现固定路径下mp3文件的列表显示:

 #include <stdio.h>
 #include <glob.h>

 int main(int argc, const char *argv[])
 {
     glob_t buf;
      int i;
     glob("/home/gaoyuzhe/music/*.mp3",GLOB_NOSORT, NULL, &buf);                                                                                 

    for(i=0; i < buf.gl_pathc; i++)
    {
         printf("buf.gl_pathv[%d]= %s \n", i, (buf.gl_pathv[i]));
    } 
    globfree(&buf);
     return 0;
}

接下来我们进入正题,首先贴出代码:
服务器 server.c


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#include <glob.h>

#include <sys/stat.h>   
#include <fcntl.h>

#define MAXSIZE 512
#define BUF_SIZE 4000000 //缓冲区的大小,也就是一次性传输数据的字节数
#define PORT 1234

/*函数声明*/
void *start_routine(void* arg);
void process_client(int connectfd, struct sockaddr_in client_addr);

    struct sockaddr_in server_addr,client_addr;
    int listenfd = 0,connectfd = 0;
    pthread_t thread;
    struct ARG{
    int connfd;//文件连接描述符
    struct sockaddr_in client;//客户端地址信息
    };      
    struct ARG *arg;

    struct MUSIC_PLAY{
    char content[BUF_SIZE];
        int size;
    };

int main()
{

   //创建套接字
    socklen_t cli_addr_size;
    listenfd =  socket(PF_INET, SOCK_STREAM,0);
    if(listenfd == -1)
    {  
        perror("socket error");
    exit(1);

    }  
    int opt_name = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt_name,sizeof(opt_name));
    //绑定套接字
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    //监听
    if((listen(listenfd,128)) == -1)
    {
        perror("listen error!");
        exit(1);
    }
    printf("waitting for client...\n");
    while(1)
    {
        //接受请求建立连接
        cli_addr_size = sizeof(struct sockaddr_in);
        connectfd =  accept(listenfd,(struct sockaddr *)&client_addr,&cli_addr_size);
        if(connectfd == -1)
        {
           perror("accept error\n");
           exit(1);
        }

        arg = malloc(sizeof(struct ARG));//动态申请内存
        arg->connfd = connectfd;
        memcpy((void *)&arg->client,&client_addr,sizeof(client_addr));

        if((pthread_create(&thread,NULL, start_routine,(void *)arg)) == -1)
        {   
        printf("thread create error!\n");
            exit(1);
        }
    } 
    close(listenfd); 
    return 0;
}

void *start_routine(void *arg)
{   
    struct ARG *info;
    info = (struct ARG*)arg;
    process_client(info->connfd, info->client);
    free(arg);
    pthread_exit(NULL);

}
void process_client(int connectfd, struct sockaddr_in client_addr)
{  

    int send_r = 0, recv_r = 0;
    char send_buffer[MAXSIZE], recv_buffer[MAXSIZE], path_buffer[MAXSIZE];
    int i = 0;
    int sum[100];
    glob_t buf;

    printf("there is a client connected\n");

/*发送单曲数*/
    glob("/home/gaoyuzhe/NEWONE/*.mp3", GLOB_NOSORT, NULL, &buf);
    sum[0] = buf.gl_pathc;
    send(connectfd,sum,MAXSIZE,0);

/*发送单曲信息*/
    for(i = 0; i< buf.gl_pathc; i++)

{
        send_r = send(connectfd,buf.gl_pathv[i],512,0);
        if(send_r == -1)
        {   
            perror("message send error!\n");
            exit(1);
        }   

     }

    send(connectfd, "请输入音乐播放序号:", 100, 0);
    recv(connectfd, recv_buffer, MAXSIZE, 0);//接收用户选择的单曲序号
    //printf("%s",recv_buffer);   


    int music_number;
    music_number=atoi(recv_buffer);

    int music_fd;
    struct MUSIC_PLAY snd_msg;


    music_fd = open(buf.gl_pathv[music_number-1],O_RDONLY);
    printf("the music is :%s\n",buf.gl_pathv[music_number-1]);
    if ( music_fd < 0 )
    {
        perror("open()");
        exit(1);
    }
    //读取
    bzero(&snd_msg,sizeof(snd_msg));

globfree(&buf);
    while(1)
    {
        snd_msg.size = read(music_fd, snd_msg.content, BUF_SIZE);//读BUF_SIZE字节到content[]里     
        if( snd_msg.size <= 0 )
        break;
    }
    send(connectfd,&snd_msg,sizeof(snd_msg),0);

    close(connectfd);
}

客户端 client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/soundcard.h> 
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h> 
#include "mad.h"

#define MAXSIZE 512

#define BUF_SIZE 4000000
struct buffer {     

    unsigned char const *start;     

    unsigned long length;     

};     

static int sfd;         /*声音设备的描述符 */

static int decode(unsigned char const *, unsigned long);    

int main()
{
    int sockfd,connect_server,send_r;
    struct sockaddr_in sockaddr;
    char *src = "127.0.0.1";

    int  path_t[MAXSIZE];
    char send_buffer[MAXSIZE];
    char recv_buffer[BUF_SIZE];
    char msg_buffer[MAXSIZE];
    char recv_cont[MAXSIZE][MAXSIZE];
    int i,j,k,numbytes;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(1234);
    inet_pton(AF_INET,src,&sockaddr.sin_addr);
    connect_server =  connect(sockfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr));

    if(connect_server == -1)
    {
    perror("connect to server error!\n");
    exit(1);
    }

/*获取单曲数*/
    if((i = read(sockfd,path_t,MAXSIZE) == -1))
    {
    perror("read error!");
    exit(1);
    } 
    printf("以下是音频列表:%d\n",path_t[0]);

    for(k = 0; k < path_t[0]; k++)
        {
        if((j = read(sockfd,recv_cont[k],MAXSIZE) == -1))
        { 
            perror("read error!");
            exit(1);
        }
        printf("第%d首音频: %s\n", k+1, recv_cont[k]);
    }

/*获取服务器提示信息*/

   recv(sockfd, msg_buffer, MAXSIZE, 0);
   printf("%s\n", msg_buffer);

/*向服务器发送歌曲序号*/

   fgets(send_buffer, sizeof(send_buffer), stdin);
   send(sockfd, send_buffer, MAXSIZE, 0);
       int count = 0;
   printf("播放中......\n");
//这里分5次接收
   for(i=0;i<5;i++){

       numbytes = recv(sockfd, recv_buffer+count, BUF_SIZE, 0);
     //  printf("接收到字节数Byte =%d\n",numbytes);
    count=count+numbytes;

   }

//开始解码播放

    if ((sfd = open("/dev/dsp1", O_WRONLY)) < 0) 
    {     

    printf("can not open device!!!/n");     

    }
    decode(recv_buffer, sizeof(recv_buffer));     
    close(sockfd);
    return 0;

}



static

enum mad_flow input(void *data, struct mad_stream *stream)     

{     

    struct buffer *buffer = data;     

    if (!buffer->length)     

    return MAD_FLOW_STOP;     

    mad_stream_buffer(stream, buffer->start, buffer->length);     

    buffer->length = 0;     
    //printf("start input\n");
    return MAD_FLOW_CONTINUE;     

}     

/*这一段是处理采样后的pcm音频 */

static inline signed int scale(mad_fixed_t sample)     

{     

    sample += (1L << (MAD_F_FRACBITS - 16));     

    if (sample >= MAD_F_ONE)     

    sample = MAD_F_ONE - 1;     

    else if (sample < -MAD_F_ONE)     

    sample = -MAD_F_ONE;     

    return sample >> (MAD_F_FRACBITS + 1 - 16);     

}     

static

enum mad_flow output(void *data,     

             struct mad_header const *header, struct mad_pcm *pcm)     

{     

    unsigned int nchannels, nsamples, n;     

    mad_fixed_t const *left_ch, *right_ch;     

    unsigned char Output[6912], *OutputPtr;     

    int fmt, wrote, speed;     



    nchannels = pcm->channels;     

    n = nsamples = pcm->length;     

    left_ch = pcm->samples[0];     

    right_ch = pcm->samples[1];     



    fmt = AFMT_S16_LE;     

    speed = pcm->samplerate * 2;    /*播放速度是采样率的两倍 */

    ioctl(sfd, SNDCTL_DSP_SPEED, &(speed));     

    ioctl(sfd, SNDCTL_DSP_SETFMT, &fmt);     

    ioctl(sfd, SNDCTL_DSP_CHANNELS, &(pcm->channels));     

    OutputPtr = Output;     

    while (nsamples--) {     

    signed int sample;     

    sample = scale(*left_ch++);     

    *(OutputPtr++) = sample >> 0;     

    *(OutputPtr++) = sample >> 8;     

    if (nchannels == 2) {     

        sample = scale(*right_ch++);     

        *(OutputPtr++) = sample >> 0;     

        *(OutputPtr++) = sample >> 8;     

    }     

    }     

    n *= 4;         /*数据长度为pcm音频采样的4倍 */

    OutputPtr = Output;     

    while (n) {     

    wrote = write(sfd, OutputPtr, n);     

    OutputPtr += wrote;     

    n -= wrote;     

    }     

    OutputPtr = Output;     
    //printf("start output\n");
    return MAD_FLOW_CONTINUE;     

}     



static

enum mad_flow error(void *data,     

            struct mad_stream *stream, struct mad_frame *frame)     

{     

    return MAD_FLOW_CONTINUE;     

}     

static int decode(unsigned char const *start, unsigned long length)     

{     

    struct buffer buffer;     

    struct mad_decoder decoder;     

    int result;     

    buffer.start = start;     

    buffer.length = length;     

    mad_decoder_init(&decoder, &buffer, input, 0, 0, output, error, 0);     

    mad_decoder_options(&decoder, 0);     

    result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);     

    mad_decoder_finish(&decoder);     

    return result;     
}

可以看到,在客户端程序中,直接把libmad的相关函数拿来用即可。

以下是程序运行截图(音频正在播放)
这里写图片描述

虽然基本实现socket传输数据给客户端mp3文件,客户端进行实时的解析并播放,但是仅在本机上测试过,不同的计算机在局域网内传输数据效果不太好,即使是用的可靠的TCP传输,从接收到的字节数来看,还是存在丢包现象。而且服务器发送数据,必须要一次性将音频文件全部发送过去(客户端可以分次接收并解析),若服务器也分次发送,则会解析失败,程序直接结束,由于个人水平有限,暂时还没有弄清楚是什么原因。另外,如果多个客户同时选择一个音频时,要加上互斥锁来规避读取出错的问题。

版权声明:本文为gepanqiang3020原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gepanqiang3020/article/details/73696533

智能推荐

Springboot整合rabbitMQ

依赖: 配置文件application.yml RabbitConfig 消息生产者RabbitProducer 消息消费者RabbitCustomer 通过Controller进行调用 启动项目后调用接口: 结果:...

Thread.join()方法的使用

如果一个线程A执行了thread.join()语句,代表当前线程A等待thread线程终止后才从thread.join()方法返回 并且这个方法具有超时特性,可以添加参数设置 输出结果: jdk中Thread.join()方法的源码(进行了部门调整)   每个线程终止的条件是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,  当线程终止时,会调用自身的no...

linux服务器部署jenkins笔记

安装jenkins参考文档:https://blog.csdn.net/tomatocc/article/details/83930714 1. 打开jenkins官网:https://jenkins.io/download/ 将war包下载到本地 **ps:**这里要注意的是要下载左边下方的war包,不要下载右边下面的war包。左边是稳定版本,右边是最新版本,建议大家使用稳定版本(我刚开始下载的...

k8s部署elasticsearch集群

百度营销大学     环境准备 我们使用的k8s和ceph环境见: https://blog.51cto.com/leejia/2495558 https://blog.51cto.com/leejia/2499684 ECK简介 Elastic Cloud on Kubernetes,这是一款基于 Kubernetes Operator 模式的新型编排产品,用户可使用该产品在...

saas-export项目-AdminLTE介绍与入门

AdminLTE介绍 (1)AdminLTE是什么? AdminLTE是一款建立在bootstrap和jquery之上的开源的模板主题工具 (2)AdminLTE有什么特点? 提供一系列响应的、可重复使用的组件, 并内置了多个模板页面 自适应多种屏幕分辨率,兼容PC和移动端 快速的创建一个响应式的Html5网站 AdminLTE 不但美观, 而且可以免去写很大CSS与JS的工作量 AdminLTE...

猜你喜欢

MyBatis中ResultMap结果集映射

用于解决属性名和字段名不一致的情况: resultMap 元素是 MyBatis 中最重要最强大的元素。...

编写一个shell

编写shell的过程: 1.从标准输入中读入一个字符串。 2.解析字符串 3.创建一个子进程的执行程序。 4.子进程程序替换。 5.父进程等待子进程退出。...

WEB自动化测试中Xpath定位方法

前言: Xpath是在XML文档中查找信息的一种语言,使用路径表达式来选取XML文档中的节点或节点集,由于XML与HTML结构类似(前者用于传输数据,后者用于显示数据),所以Xpath也常用于查找HTML文档中的节点或节点集。 一  路径表达式: 路径以“/”开始     表示找到满足该绝对路径的元素; 路径以//”开始  ...

力扣困难难度 第4题 寻找两个正序数组的中位数

先看一眼题 我的思路: 设置下标i,j分别用于遍历两个数组,初始值均为0,直到找到两个数组中从小到大的第第length/2个数为止结束循环,length为两个数组长度之和。 ·每次比较nums[i]nums[j],如果前者小则i++,否则j++ ·循环结束时,如果count已经达到length/2,则说明已经找到了中位数,[注意:此时有可能正好其中一个数组遍历完了!所以...

[国家集训队]小Z的袜子(莫队)

[国家集训队]小Z的袜子 题目描述 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这NN只袜子从1到NN编号,然后从编号LL到RR(LL 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同...