在衆多大神的幫助下,這個在線播放流媒體服務器終於完成啦。。。。
這個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傳輸,從接收到的字節數來看,還是存在丟包現象。而且服務器發送數據,必須要一次性將音頻文件全部發送過去(客戶端可以分次接收並解析),若服務器也分次發送,則會解析失敗,程序直接結束,由於個人水平有限,暫時還沒有弄清楚是什麼原因。另外,如果多個客戶同時選擇一個音頻時,要加上互斥鎖來規避讀取出錯的問題。