流媒体MP3播放器教程.docx
《流媒体MP3播放器教程.docx》由会员分享,可在线阅读,更多相关《流媒体MP3播放器教程.docx(20页珍藏版)》请在冰豆网上搜索。
![流媒体MP3播放器教程.docx](https://file1.bdocx.com/fileroot1/2022-12/11/0cf32293-b699-469b-b9b1-17f897eaf26c/0cf32293-b699-469b-b9b1-17f897eaf26c1.gif)
流媒体MP3播放器教程
基于libmad的简单MP3流媒体播放器的实现.介绍
本文在Fedora5Linux下实现了一个基于libmad的mp3流媒体播放器。
此流媒体播放器可以播放基于HTTP1.1协议传输的MP3流媒体数据。
基本原理是:
从HTTP服务器获得MP3媒体信息,然后通过网络传输把MP3数据以数据流的形式接收到MP3流媒体播放器客户端,由客户端通过libmad解码MP3数据流,得到PCM音频数据,写入音频设备,播放音乐。
本文的流媒体播放器只是实现了必要的简单功能,没有考虑太多情况。
比如,没有考虑实时播放控制,这样的话就不能随意选取播放点进行播放。
本文的MP3流媒体播放器创建两个线程,使用两个缓冲区保存MP3数据,可以一边下载数据,一边播放音乐。
编译运行此MP3流媒体播放器需要安装libmad(以及ALSA(AdvancedLinuxSoundArchitecture)(http:
//www.alsa-project.org)相关的软件。
ALSA包括4部分,分别是sounddriver,soundlibrary,soundutilities以及tools。
至少应该安装sounddriver,soundlibrary。
编译程序时连接库的选项是:
-lmad-lasound-lpthread。
本文的MP3流媒体播放器使用双缓冲区,一个是数据接收缓冲区,另一个是数据解码缓冲区。
主程序结构如下图所示,图中的蓝色线表示数据流向。
图1:
MP3流媒体播放器主程序结构图
2.libmad简介
MAD(libmad)是一个开源的高精度MPEG音频解码库,支持MPEG-1(LayerI,LayerII和LayerIII(也就是MP3)。
LIBMAD提供24-bit的PCM输出,完全是定点计算,非常适合没有浮点支持的平台上使用。
使用libmad提供的一系列API,就可以非常简单地实现MP3数据解码工作。
在libmad的源代码文件目录下的mad.h文件中,可以看到绝大部分该库的数据结构和API等。
∙本文用到的libmad中的主要数据结构有:
structmad_stream,structmad_synth,structmad_frame。
它们的定义如下:
清单1:
libmad中的主要数据结构
structmad_stream{
unsignedcharconst*buffer;/*inputbitstreambuffer*/
unsignedcharconst*bufend;/*endofbuffer*/
unsignedlongskiplen;/*bytestoskipbeforenextframe*/
intsync;/*streamsyncfound*/
unsignedlongfreerate;/*freebitrate(fixed)*/
unsignedcharconst*this_frame;/*startofcurrentframe*/
unsignedcharconst*next_frame;/*startofnextframe*/
structmad_bitptrptr;/*currentprocessingbitpointer*/
structmad_bitptranc_ptr;/*ancillarybitspointer*/
unsignedintanc_bitlen;/*numberofancillarybits*/
unsignedchar(*main_data)[MAD_BUFFER_MDLEN];
/*LayerIIImain_data()*/
unsignedintmd_len;/*bytesinmain_data*/
intoptions;/*decodingoptions(seebelow)*/
enummad_errorerror;/*errorcode(seeabove)*/
};
更多内容请看流媒体播放器 流媒体文件格式播放技巧专题,或
如果缓冲区最后一个MPEG数据帧只有部分数据包括在缓冲区中,那么structmad_stream中的next_frame域指到不完整数据的开始地址。
∙由于缓冲区的MPEG数据帧不一定完整,所以不完整的MPEG帧的数据必须拷贝到下一次解码操作的缓冲区中,进行再次解码。
这里我们还看到bufend指向缓冲区数据的最后地址,也就是最后一字节的地址加1的位置。
mad_stream.bufend–mad_stream.next_frame就是剩余的未被解码的MPEG帧的数据的字节数量(假设此帧在缓冲区中不完整)。
mad_stream的error域用来记录操作mad_stream得到的错误代码。
错误代码在mad.h中有很详细的定义。
∙清单2:
错误代码在mad.h中的详细定义
∙structmad_synth{
∙mad_fixed_tfilter[2][2][2][16][8];/*polyphasefilterbankoutputs*/
∙/*[ch][eo][peo][s][v]*/
∙unsignedintphase;/*currentprocessingphase*/
∙structmad_pcmpcm;/*PCMoutput*/
∙};
mad_synth中的关键域pcm保存解码和合成后得到的PCM数据。
清单3:
mad_synth中的关键域
structmad_pcm{
unsignedintsamplerate;/*samplingfrequency(Hz)*/
unsignedshortchannels;/*numberofchannels*/
unsignedshortlength;/*numberofsamplesperchannel*/
mad_fixed_tsamples[2][1152];/*PCMoutputsamples[ch][sample]*/
};
structmad_pcm定义了音频的采样率、每个声道个数以及最后的PCM采样数据。
这些参数可用来初始化音频设备。
清单4:
structmad_pcm
structmad_frame{
structmad_headerheader;/*MPEGaudioheader*/
intoptions;/*decodingoptions(fromstream)*/
mad_fixed_tsbsample[2][36][32];/*synthesissubbandfiltersamples*/
mad_fixed_t(*overlap)[2][32][18];/*LayerIIIblockoverlapdata*/
};
mad_frame是记录MPEG帧解码后的数据的数据结构,其中的mad_header尤其重要,其用来记录MPEG帧的一些基本信息,比如MPEG层数、声道模式、流比特率、采样比特率等等。
声道模式包括单声道、双声道、联合立体混音声以及一般立体声。
清单5:
mad_frame
enummad_mode{
MAD_MODE_SINGLE_CHANNEL=0,/*singlechannel*/
MAD_MODE_DUAL_CHANNEL=1,/*dualchannel*/
MAD_MODE_JOINT_STEREO=2,/*joint(MS/intensity)stereo*/
MAD_MODE_STEREO=3/*normalLRstereo*/
};
structmad_header{
enummad_layerlayer;/*audiolayer(1,2,or3)*/
enummad_modemode;/*channelmode*/
intmode_extension;/*additionalmodeinfo*/
enummad_emphasisemphasis;/*de-emphasistouse*/
unsignedlongbitrate;/*streambitrate(bps)*/
unsignedintsamplerate;/*samplingfrequency(Hz)*/
unsignedshortcrc_check;/*frameCRCaccumulator*/
unsignedshortcrc_target;/*finaltargetCRCchecksum*/
intflags;/*flags*/
intprivate_bits;/*privatebits*/
mad_timer_tduration;/*audioplayingtimeofframe*/
};
∙下面就本文使用的API的功能做简单介绍。
在本文中用到的API包括:
voidmad_stream_init(structmad_stream*)
voidmad_synth_init(structmad_synth*);
voidmad_frame_init(structmad_frame*);
以上3个API初始化解码需要的数据结构。
voidmad_stream_buffer(structmad_stream*,unsignedcharconst*,unsignedlong);
此函数把原始的未解码的MPEG数据和mad_stream数据结构关联,以便使用mad_frame_decode()来解码MPEG帧数据。
intmad_frame_decode(structmad_frame*,structmad_stream*);
把mad_stream中的MPEG帧数据解码。
voidmad_synth_frame(structmad_synth*,structmad_frameconst*);
把解码后的音频数据合成PCM采样。
voidmad_stream_finish(structmad_stream*);
voidmad_frame_finish(structmad_frame*);
mad_synth_finish(structmad_synth);
以上3个API在解码完毕后使用,释放libmad占用的资源等。
更多内容请看流媒体播放器 流媒体文件格式播放技巧专题,或
3.PCM音频设备的操作
对音频设备的操作主要是初始化音频设备以及往音频设备发送PCM(PulseCodeModulation)数据。
为了方便,本文使用ALSA(AdvancedLinuxSoundArchitecture)提供的库和驱动。
在编译和运行本文中的MP3流媒体播放器的时候,必须先安装ALSA相关的文件。
本文用到的主要对PCM设备操作的函数分为PCM设备初始化的函数以及PCM接口的一些操作函数。
PCM硬件设备参数设置和初始化的函数有:
intsnd_pcm_hw_params_malloc(snd_pcm_hw_params_t**ptr)
intsnd_pcm_hw_params_any(snd_pcm_t*pcm,snd_pcm_hw_params_t*params)
voidsnd_pcm_hw_params_free(snd_pcm_hw_params_t*obj)
intsnd_pcm_hw_params_set_Access(snd_pcm_t*pcm,
snd_pcm_hw_params_t*params,
snd_pcm_access_t_access)
intsnd_pcm_hw_params_set_format(snd_pcm_t*pcm,
snd_pcm_hw_params_t*params,
snd_pcm_format_tval)
intsnd_pcm_hw_params_set_channels(snd_pcm_t*pcm,
snd_pcm_hw_params_t*params,
unsignedintval)
intsnd_pcm_hw_params_set_rate_near(snd_pcm_t*pcm,
snd_pcm_hw_params_t*params,
unsignedint*val,int*dir)
∙PCM接口的操作函数:
∙intsnd_pcm_hw_params(snd_pcm_t*pcm,snd_pcm_hw_params_t*params)
∙intsnd_pcm_prepare(snd_pcm_t*pcm)
∙intsnd_pcm_open(snd_pcm_t**pcm,constchar*name,
∙snd_pcm_stream_tstream,intmode)
∙intsnd_pcm_close(snd_pcm_t*pcm)
∙snd_pcm_sframes_tsnd_pcm_writei(snd_pcm_t*pcm,
∙constvoid*buffer,snd_pcm_uframes_tsize)
这些函数用到了snd_pcm_hw_params_t结构,此结构包含用来播放PCM数据流的硬件信息配置。
在往音频设备(声卡)写入音频数据之前,必须设置访问类型、采样格式、采样率、声道数等。
首先使用snd_pcm_open()打开PCM设备,在ALSA中,PCM设备都有名字与之对应。
比如我们可以定义PCM设备名字为char*pcm_name="plughw:
0,0"。
最重要的PCM设备接口是“plughw”以及“hw”接口。
使用“plughw”接口,程序员不必过多关心硬件,而且如果设置的配置参数和实际硬件支持的参数不一致,ALSA会自动转换数据。
如果使用“hw”接口,我们就必须检测硬件是否支持设置的参数了。
Plughw后面的两个数字分别表示设备号和次设备(subdevice)号。
snd_pcm_hw_params_malloc()在栈中分配snd_pcm_hw_params_t结构的空间,然后使用snd_pcm_hw_params_any()函数用声卡的全配置空间参数初始化已经分配的snd_pcm_hw_params_t结构。
snd_pcm_hw_params_set_access()设置访问类型,常用访问类型的宏定义有:
SND_PCM_ACCESS_RW_INTERLEAVED
交错访问。
在缓冲区的每个PCM帧都包含所有设置的声道的连续的采样数据。
比如声卡要播放采样长度是16-bit的PCM立体声数据,表示每个PCM帧中有16-bit的左声道数据,然后是16-bit右声道数据。
SND_PCM_ACCESS_RW_NONINTERLEAVED
非交错访问。
每个PCM帧只是一个声道需要的数据,如果使用多个声道,那么第一帧是第一个声道的数据,第二帧是第二个声道的数据,依此类推。
函数snd_pcm_hw_params_set_format()设置数据格式,主要控制输入的音频数据的类型、无符号还是有符号、是little-endian还是bit-endian。
比如对于16-bit长度的采样数据可以设置为:
∙SND_PCM_FORMAT_S16_LE有符号16bitLittleEndian
∙SND_PCM_FORMAT_S16_BE有符号16bitBigEndian
∙SND_PCM_FORMAT_U16_LE无符号16bitLittleEndian
∙SND_PCM_FORMAT_U16_BE无符号16bitBigEndian
∙
比如对于32-bit长度的采样数据可以设置为:
SND_PCM_FORMAT_S32_LE有符号32bitLittleEndian
SND_PCM_FORMAT_S32_BE有符号32bitBigEndian
SND_PCM_FORMAT_U32_LE无符号32bitLittleEndian
SND_PCM_FORMAT_U32_BE无符号32bitBigEndian
函数snd_pcm_hw_params_set_channels()设置音频设备的声道,常见的就是单声道和立体声,如果是立体声,设置最后一个参数为2。
snd_pcm_hw_params_set_rate_near()函数设置音频数据的最接近目标的采样率。
snd_pcm_hw_params()从设备配置空间选择一个配置,让函数snd_pcm_prepare()准备好PCM设备,以便写入PCM数据。
snd_pcm_writei()用来把交错的音频数据写入到音频设备。
初始化PCM设备的例程如下:
清单6:
初始化PCM设备的例程
/*openaPCMdevice*/
intopen_device(structmad_headerconst*header)
{
interr;
snd_pcm_hw_params_t*hw_params;
char*pcm_name="plughw:
0,0";
intrate=header->samplerate;
intchannels=2;
if(header->mode==0){
channels=1;
}else{
channels=2;
}
if((err=snd_pcm_open(&playback_handle,
pcm_name,SND_PCM_STREAM_PLAYBACK,0))<0){
printf("cannotopenaudiodevice%s(%s)\n",
pcm_name,
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_malloc(&hw_params))<0){
printf("cannotallocatehardwareparameterstructure(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_any(playback_handle,hw_params))<0){
printf("cannotinitializehardwareparameterstructure(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_set_access(playback_handle,hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED))<0){
printf("cannotsetaccesstype(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_set_format(playback_handle,
hw_params,SND_PCM_FORMAT_S32_LE))<0){
printf("cannotsetsampleformat(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_set_rate_near(playback_handle,
hw_params,&rate,0))<0){
printf("cannotsetsamplerate(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params_set_channels(playback_handle,
hw_params,channels))<0){
printf("cannotsetchannelcount(%s)\n",
snd_strerror(err));
return-1;
}
if((err=snd_pcm_hw_params(playback_handle,
hw_params))<0){
printf("cannotsetparameters(%s)\n",
snd_strerror(err));
return-1;
}
snd_pcm_hw_params_free(hw_params);
if((err=snd_pcm_prepare(playback_handle))<0){
printf("cannotprepareaudiointerfaceforuse(%s)\n",
snd_strerror(err));
return-1;
}
return0;
}
∙这里配置的PCM格式是SND_PCM_FORMAT_S32_LE,采样的格式是每个采样有32-bit的数据,数据按照little-endian存放。
如果通过mad_frame_decode()函数得到PCM数据后,要求每个采样数据只占16-bit,需要把数据进行MAD的定点类型到signedshort