该版本的声卡驱动有缺陷,放音是不正常的,会断断续续,所以还需要修改。主要就是对sound/soc/samsung目录下dma.c文件进行修改。基于网上的资料,我整理如下:
首先把第63行的下列语句注释掉,因为要重新写该函数:
static void audio_buffdone(void *data);
然后把dma_enqueue函数和audio_buffdone替换为下面的内容:
static void dma_enqueue(structsnd_pcm_substream *substream)
{
structruntime_data *prtd = substream->runtime->private_data;
dma_addr_tpos = prtd->dma_pos;
unsignedint limit;
intret;
pr_debug("Entered%s\n", __func__);
limit= (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s:loaded %d, limit %d\n",
__func__,prtd->dma_loaded, limit);
while(prtd->dma_loaded < limit) {
unsignedlong len = prtd->dma_period;
pr_debug("dma_loaded:%d\n", prtd->dma_loaded);
ret= s3c2410_dma_enqueue(prtd->params->channel,
substream,pos,len);
if(ret== 0){
prtd->dma_loaded++;
pos+= prtd->dma_period;
if(pos>=prtd->dma_end)
pos= prtd->dma_start;
}else
break;
}
prtd->dma_pos= pos;
}
static void audio_buffdone(structs3c2410_dma_chan *channel,
void*dev_id, int size,
enums3c2410_dma_buffresult result)
{
structsnd_pcm_substream *substream = dev_id;
structruntime_data *prtd;
pr_debug("Entered%s\n", __func__);
if(result== S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
return;
prtd= substream->runtime->private_data;
if(substream)
snd_pcm_period_elapsed(substream);
spin_lock(&prtd->lock);
if(!samsung_dma_has_circular()){
prtd->dma_loaded--;
dma_enqueue(substream);
}
spin_unlock(&prtd->lock);
}
最后在dma_hw_params函数中的snd_pcm_set_runtime_buffer(substream,&substream->dma_buffer);语句之前添加下列语句:
s3c2410_dma_set_buffdone_fn(prtd->params->channel,audio_buffdone);
在编译系统的时候,我们还需要配置menuconfig:
Device Drivers --->
<*>Soundcard support --->
<*>AdvancedLinux Sound Architecture --->
<*>ALSAfor SoC audio support --->
<*>SynopsysI2S Device Driver
<*>ASoCsupport for Samsung
<*>SoCI2S Audio support UDA134X wired to a S3C24XX
<*>Buildall ASoC CODEC drivers
<*>ASoCSimple sound card support
这样声卡驱动的移植就完成了。在启动系统的时候,系统会自动打印出下列信息:
S3C24XX_UDA134X SoC Audio driver
soc-audio soc-audio: ASoC machineS3C24XX_UDA134X should use snd_soc_register_card()
UDA134X SoC Audio Codec
soc-audio soc-audio: uda134x-hifi <-> s3c24xx-iis mapping ok
在系统启动以后,我们也可以查看到声卡设备:
[root@zhaocj /]#cat /proc/asound/devices
2:[ 0- 0]: digital audio playback
3:[ 0- 0]: digital audio capture
4:[ 0] : control
33: : timer
[root@zhaocj /dev]#ls /dev/pcm*
pcmC0D0c pcmC0D0p
Linux下的声卡驱动是基于ALSA的,ALSA提供了标准的库和工具可以用于测试Linux下的声卡。下面我们就来移植它们。
在http://www.alsa-project.org/网站内下载这两个文件:alsa-lib-1.0.25.tar.bz2和alsa-utils-1.0.25.tar.bz2。分别解压它们:
tar jxvf alsa-lib-1.0.25.tar.bz2
tar jxvf alsa-utils-1.0.25.tar.bz2
进入alsa-lib-1.0.25目录,执行下列命令:
./configure --host=arm-linux --prefix=/usr/share/arm-alsa --disable-python --with-alsa-devdir=/dev
make
make install
进入alsa-util-1.0.25目录,执行下列命令:
./configure --host=arm-linux--prefix=/usr/share/arm-alsa --with-alsa-inc-prefix=/usr/share/arm-alsa/include--with-alsa-prefix=/usr/share/arm-alsa/lib --disable-alsamixer --disable-xmlto
make
make install
然后把编译好的一些文件复制到根文件的相应目录下:
cp -r /usr/share/arm-alsa/share/* /home/zhaocj/root/rootfs/usr/share/arm-alsa/share/
cp /usr/share/arm-alsa/bin/* /home/zhaocj/root/rootfs/bin/
cp -d /usr/share/arm-alsa/lib/liba* /home/zhaocj/root/rootfs/lib
我的根文件系统在/home/zhaocj/root/rootfs目录下,另外在复制之前,还需要手动创建usr/share/arm-alsa/share/目录。运行ALSA需要一起库文件,如果根文件系统没有相关的库文件,还需要复制所需的库文件,具体内容请看我以前的文章——动态库的根文件系统的制作。
一切准备就绪,我们来测试一下声卡。启动开发板,上传一个wav音频文件到temp目录下,然后执行下列命令:
[root@zhaocj /temp]#aplay music.wav
我们会看到系统在放音的同时,会在命令行下显示该wav文件的相关信息。
我们写一段应用程序来对声卡放音进行测试,该文件名为myplay.c:
#include <alsa/asoundlib.h>
struct WAV_HEADER
{
char rld[4]; //"RIFF"标志
intrLen; //文件长度
charwld[4]; //"WAVE"标志
charfld[4]; //"fmt"标志
intfLen;
shortwFormatTag; //编码格式
shortwChannels; //声道数
intnSamplesPersec ; //采样频率
intnAvgBitsPerSample; //每秒的字节数 = 采样频率×每采样数据的字节数
shortwBlockAlign; //每采样数据的字节数 = 采样位数 × 声道数 / 8
shortwBitsPerSample; //每个采样数据的位数(8位、16位等)
chardld[4]; //"data"标记符
intwSampleLength; //音频数据的大小
} wav_header;
int main(int argc,char *argv[])
{
intfd, size, err;
snd_pcm_t*playback_handle;
snd_pcm_hw_params_t*hw_params;
snd_pcm_uframes_tframes;
char*buffer;
if(argc!=2)
{
printf("使用规则:myplay + WAV文件名\n");
return-1;
}
fd= open(argv[1],O_RDONLY);
if(fp< 0)
{
perror("openfile failed.\n");
return-1;
}
if(read(fd,&wav_header, sizeof(wav_header)) < 0 )
{
printf("readwave file header error!");
return-1;
}
printf("%s文件文件大小:%d\n",argv[1], wav_header.rLen);
printf("声道数:%d\n",wav_header.wChannels);
printf("采样频率:%d\n",wav_header.nSamplesPersec);
printf("采样的位数:%d\n",wav_header.wBitsPerSample);
printf("音频数据大小:%d\n",wav_header.wSampleLength);
//打开PCM设备
if(snd_pcm_open(&playback_handle,"default",SND_PCM_STREAM_PLAYBACK,0)< 0)
{
printf("cantopen audio device %s \n",argv[1]);
return-1;
}
//分配snd_pcm_hw_params_t结构体
if(snd_pcm_hw_params_malloc(&hw_params)< 0)
{
printf("cantopen allocate parameter structure. \n");
return-1;
}
//初始化snd_pcm_hw_params_t结构体
if(snd_pcm_hw_params_any(playback_handle,hw_params) < 0)
{
printf("snd_pcm_hw_params_any.\n");
return-1;
}
//初始化访问权限
if(snd_pcm_hw_params_set_access(playback_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
{
printf("cantset access type. \n");
return-1;
}
//采样位数
switch(wav_header.wBitsPerSample/ 8)
{
case1:
snd_pcm_hw_params_set_format(playback_handle,hw_params,SND_PCM_FORMAT_U8);
break;
case2:
snd_pcm_hw_params_set_format(playback_handle,hw_params,SND_PCM_FORMAT_S16_LE);
break;
case3:
snd_pcm_hw_params_set_format(playback_handle,hw_params,SND_PCM_FORMAT_S24_LE);
break;
default:
return-1;
}
//设置声道,1表示单声道,2表示立体声
if(snd_pcm_hw_params_set_channels(playback_handle,hw_params, wav_header.wChannels) < 0)
{
printf("snd_pcm_hw_params_set_channels.\n");
return-1;
}
//设置采样频率
if(snd_pcm_hw_params_set_rate_near(playback_handle,hw_params, &wav_header.nSamplesPersec, 0) < 0)
{
printf("snd_pcm_hw_params_set_rate_near.\n");
return-1;
}
//设置参数
if(snd_pcm_hw_params(playback_handle,hw_params) < 0)
{
printf("snd_pcm_hw_params.\n");
return-1;
}
//获得周期大小;
if(snd_pcm_hw_params_get_period_size(hw_params,&frames, 0) < 0)
{
printf("snd_pcm_hw_params_get_period_size.\n");
return-1;
}
size= frames * wav_header.wBlockAlign;
buffer=(char*)malloc(size);
lseek(fd,58,SEEK_SET);//定位歌曲到数据区,,44
while(1)
{
memset(buffer,0,sizeof(buffer));
if(read(fd,buffer, size) == 0)
{
printf("音频播放完毕\n");
break;
}
//写音频数据到PCM设备
if(err=snd_pcm_writei(playback_handle,buffer, frames) == -EPIPE)
{
printf("XRUN.\n");
snd_pcm_prepare(playback_handle);
}
elseif(err < 0)
{
printf("ERROR.cannotwrite to PCM device.%s\n",snd_strerror(err));
}
}
snd_pcm_drain(playback_handle);
snd_pcm_close(playback_handle);
free(buffer);
return0;
}
需要说明的是,通过snd_pcm_hw_params_get_period_size函数可以得到周期,即一帧数据的采样个数frames,也就是PCM设备在一次中断所能处理的采样数。而PCM设备一次中断所能处理的字节数量为一帧的采样个数×每个采样的字节数,即size= frames * wav_header.wBlockAlign。每次我们都从WAV文件中读取size个字节数的数据,然后交给PCM设备去处理,直到所有数据从WAV文件中读取完为止。向PCM设备写数据(snd_pcm_writei)可以实现放音,该函数的第二个参数就是从WAV文件所读取的数据,第三个参数就是周期大小。
使用下列命令编译该文件:
arm-linux-gcc -lasound -L/usr/share/arm-alsa/lib -I/usr/share/arm-alsa/include -lm -ldl -lpthread -o myplay myplay.c
最后需要强调一点的是,本次声卡驱动的移植,仅能够保障放音功能的基本正常。对我的开发板来说,录音功能还实现不了。我认为硬件应该是没有问题的,那一定是驱动的问题。但很遗憾,本人能力有限,还无法找到问题所在。