linux下 基于libmad的socket多用户mp3音频在线播放服务器
2017-06-24 23:55
351 查看
在众多大神的帮助下,这个在线播放流媒体服务器终于完成啦。。。。
这个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文件的列表显示:
接下来我们进入正题,首先贴出代码:
服务器 server.c
客户端 client.c
可以看到,在客户端程序中,直接把libmad的相关函数拿来用即可。
以下是程序运行截图(音频正在播放)
![](https://img-blog.csdn.net/20170624234328294?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ2VwYW5xaWFuZzMwMjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
虽然基本实现socket传输数据给客户端mp3文件,客户端进行实时的解析并播放,但是仅在本机上测试过,不同的计算机在局域网内传输数据效果不太好,即使是用的可靠的TCP传输,从接收到的字节数来看,还是存在丢包现象。而且服务器发送数据,必须要一次性将音频文件全部发送过去(客户端可以分次接收并解析),若服务器也分次发送,则会解析失败,程序直接结束,由于个人水平有限,暂时还没有弄清楚是什么原因。另外,如果多个客户同时选择一个音频时,要加上互斥锁来规避读取出错的问题。
这个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 cf1b _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传输,从接收到的字节数来看,还是存在丢包现象。而且服务器发送数据,必须要一次性将音频文件全部发送过去(客户端可以分次接收并解析),若服务器也分次发送,则会解析失败,程序直接结束,由于个人水平有限,暂时还没有弄清楚是什么原因。另外,如果多个客户同时选择一个音频时,要加上互斥锁来规避读取出错的问题。
相关文章推荐
- linux下ftp和ftps以及ftp基于mysql虚拟用户认证服务器的搭建
- 基于socket的tcp回射服务器的小程序——Linux
- 网络编程(5)—— 基于Linux系统的UDP协议socket服务器和客户端
- 基于多台linux主机通过1台服务器进行socket通讯小程序编写
- Linux 下基于socket的简单网络聊天室(服务器与客户端)
- Linux服务器安全之用户密钥认证登录(基于CentOS 7.0系统)
- 在 Fedora 5 Linux 下实现了一个基于 libmad 的 MP3 流媒体播放器
- linux 基于alsa 使用libmad 解码库实现MP3文件的播放
- Linux服务器安全之用户密钥认证登录(基于CentOS 7.0系统)
- 基于socket的Linux上的网络聊天程序--多线程的服务器
- 基于 libmad 的简单 MP3 流媒体播放器的实现
- 基于 libmad 的简单 MP3 流媒体播放器的实现
- Linux下基于C/C++的Socket编程实例
- RHEL5基于虚拟用户验证的VSFTP服务器
- 转贴:基于 libmad 的简单 MP3 流媒体播放器的实现
- pxe网络安装系统---------基于windows xp做服务器(dhcp+ftp)安装linux系统
- 在LINUX下怎么播放MP3等音频文件啊?
- Linux网络管理之二:基于用户级别的samba服务器配置 推荐
- Linux下如何实现用户的集中管理(NIS服务器的高级配置)
- 设置LINUX 服务器中FTP 用户登录的默认目录