您的位置:首页 > 运维架构 > Linux

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文件的列表显示:

#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传输,从接收到的字节数来看,还是存在丢包现象。而且服务器发送数据,必须要一次性将音频文件全部发送过去(客户端可以分次接收并解析),若服务器也分次发送,则会解析失败,程序直接结束,由于个人水平有限,暂时还没有弄清楚是什么原因。另外,如果多个客户同时选择一个音频时,要加上互斥锁来规避读取出错的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: