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

linux+audio(OSS)驱动的读写buff分析(jz4760B)

2013-12-15 18:45 489 查看
/article/8576648.html

ADUIO OSS 的buff 分析

接上文的OSS驱动分析,现在分析OSS中的播放与录音缓冲,

作者: [b]http://blog.csdn.net/dyron
,欢迎大家来讨论

[/b]

统一术语

1. JZ 君正4760b mips

2. Audio buff 就是整个list_head+ list_node *4 + 整个音频区

3. APP 应用程序

整体使用框图



Buff分配流程

首先在probe JZ mixer设备的时候进行初始化DMA及缓冲buff. 在函数init_jz_i2s中调

用audio_init_endpoint进行实始化。

[cpp]
view plaincopy

fragsize= JZCODEC_RW_BUFFER_SIZE * PAGE_SIZE;
fragstotal= JZCODEC_RW_BUFFER_TOTAL;

audio_init_endpoint(&out_endpoint,fragsize, fragstotal);
audio_init_endpoint(&in_endpoint,fragsize, fragstotal);

进入audio_init_endpoint函数,在这里先表一下in_endpoint和out_ endpoint结构体, 初始化为0.

[cpp]
view plaincopy

static audio_pipe out_endpoint = {
.mem = 0,
.savenode = 0,
.fragsize = 0,
.fragstotal = 0,
.trans_state = 0,
};

static audio_pipe in_endpoint= {
.mem = 0,
.savenode = 0,
.fragsize = 0,
.fragstotal = 0,
.trans_state = 0,
};

[cpp]
view plaincopy

void audio_init_endpoint(audio_pipe*endpoint, unsigned int pagesize,
unsigned int count)
{
audio_resizemem_endpoint(endpoint,pagesize, count);
spin_lock_init(&endpoint->lock);
init_waitqueue_head(&endpoint->q_full);
endpoint->avialable_couter= 0;
endpoint->filter= NULL;

if(endpoint == &in_endpoint) {
init_audio_audiodma(endpoint,CODEC_RMODE);
//INIT_WORK(&endpoint->work, audio_in_endpoint_work);
endpoint->handle= handle_in_endpoint_work;
}
if(endpoint == &out_endpoint) {
init_audio_audiodma(endpoint,CODEC_WMODE);
//INIT_WORK(&endpoint->work, audio_out_endpoint_work);
endpoint->handle= handle_out_endpoint_work;
}
}

int audio_resizemem_endpoint(audio_pipe*endpoint, unsigned int pagesize,
unsigned int count)
{
intret = init_audio_node(&endpoint->mem, pagesize, count,(int*)&endpoint->
fragmem_start);
if(ret) {
endpoint->fragsize= pagesize;
endpoint->fragstotal= count;
endpoint->memsize= ret;
}
returnret;
}

我们进入audio_resizemem_endpoint分析内存的分配, 其中调用init_audio_node进行实质的

分配, 先介绍一下这里的变量意义,了解了这些意义,分析起来就得心应手了,迫不及待

了。

[cpp]
view plaincopy

JZCODEC_RW_BUFFER_SIZE // 1 * 4096
JZCODEC_RW_BUFFER_TOTAL; // 4个buff

unsignedint fact; //分配物理页的order
audio_node *pbuff; //代表1个audio node
audio_head *phead; //代表整个audiobuff 链表
unsignedint *mem; //分配得到整个audiobuff的虚拟地址
structlist_head *audio_wfree; //freebuff 链表
structlist_head *audio_wuse; //usebuff 链表
int memsize; //链表头+节点+audiobuff占用总空间
int datasize; //audiobuff占用的总空间
int headlistsize; //链表头+4个节点占用总空间

[cpp]
view plaincopy

//Alloc memory first, to avail fail
datasize = ALIGN_PAGE_SIZE(pagesize * count);
headlistsize = ALIGN_PAGE_SIZE(count *sizeof(audio_node) + sizeof(audio_head));
memsize = headlistsize + datasize;
fact = get_order(memsize);

mem= (unsigned int *)__get_free_pages(GFP_KERNEL | GFP_DMA, fact);

首先获得整个audiobuff的大小 datasize, 再获得list_head + list_node * 4的大小

headlistsize, 然后计算总大小memsize, 用__get_free_pages分配连续的物理地址空间,这里

注意一下,返回的是物理地址。

[cpp]
view plaincopy

//Free old buffer
if(*memory) {
phead = (audio_head *)*memory;
fact = phead->fact;
free_pages((unsignedlong)*memory, fact);
*memory = NULL;
}
*memory= mem;

这里检查一下是不是有分配audiobuff, 如果有就释放掉,再将新分配的地址赋值给memory.

在mixer的ioctl中SNDCTL_DSP_SETFRAGMENT也会重新分配大小的。

[cpp]
view plaincopy

phead = (audio_head *)*memory;
phead->fact = fact;
phead->listsize = headlistsize;
phead->datasize = datasize;

audio_wuse = &(phead->use);
audio_wfree = &(phead->free);
INIT_LIST_HEAD(audio_wuse);
INIT_LIST_HEAD(audio_wfree);

接下来就是给listhead赋值,然后初始化audio_wuse和audio_wfree链表。

[cpp]
view plaincopy

pbuff= (audio_node *)((unsigned int)*memory + sizeof(audio_head));
*fragmem_start= (int)((unsigned int)*memory + headlistsize);
for(i = 0; i < count; i++) {
pbuff->pBuf = (unsigned int)*memory + headlistsize +pagesize * i;
pbuff->phyaddr = (unsigned int)virt_to_phys((void*)pbuff->pBuf);
pbuff->start = 0;
pbuff->end = 0;
DEBUG
pbuff->pBufID = i;

DPRINT_Q("audio_notebuffer[%d] = %x\n", i, (unsigned int)pbuff->pBuf);
list_add(&pbuff->list,audio_wfree);
pbuff++;
}

在这里将audio_wfree 链表全部初始化,注意此时wfree链表中有4个结点,wuse中

没有结点。

[cpp]
view plaincopy

if (ret) {
endpoint->fragsize =pagesize;
endpoint->fragstotal =count;
endpoint->memsize = ret;
}

接下来返回到audio_resizemem_endpoint, 将endpoint结构赋值。

Audio buff分配好后,再返回到audio_init_endpoint对DMA进行初始化, 这里根据传

入的是play或者record进行初始化,大致上是雷同的,这里只分析 replay的初始化

[cpp]
view plaincopy

if ((ch =jz_request_aic_dma(DMA_ID_I2S_TX,"audio dac", jz_i2s_dma_irq,IRQF_DISABLED,
endpoint)) < 0) {
printk(KERN_ERR "%s:can't reqeust DMA DAC channel.\n", __FUNCTION__);
return -1;
}

chan->io = i;
chan->dev_id = dev_id;
chan->dev_str = dev_str;
chan->fifo_addr = CPHYSADDR(AIC_DR);

switch (dev_id) {
case DMA_ID_AIC_TX:
chan->mode = DMA_AIC_TX_CMD_UNPACK | DMA_MODE_WRITE;
chan->source = DMAC_DRSR_RS_AICOUT;
break;
case DMA_ID_AIC_RX:
chan->mode = DMA_32BIT_RX_CMD | DMA_MODE_READ;
chan->source = DMAC_DRSR_RS_AICIN;
break;
default:
printk("JZ AIC: %s:%d,need fix !!!\n", __FUNCTION__, __LINE__);
BUG_ON(1);
}

调用jz_request_aic_dma函数注册DMA中断,填写chan的模式与地址填充,最后启动

AIC DMA的时钟,再返回到init_audio_replaydma中,初始化寄存器,DMA就初始化OK了。

至此整个AUDIO BUFF就初始化完成了。

播放流程分析

先上图来说话:



首先进入jz_audio_write函数, 里边有两个分支,根据模式进行区分的,第1种是使用

mmap方式传输的,由于我们要分析audio buff, 就不关注mmap方式了,各位如有想了解

的话,可以发与我讨论,
http://blog.csdn.net/dyron,
下边就直接进入audiobuff 方式的

jz_audio_write_data函数分析。

[cpp]
view plaincopy

while(count >= pout_endpoint->fragsize) {
bat_cnt= endpoint_put_userdata(pout_endpoint,
&(buffer[usecount]),
pout_endpoint->fragsize);
//Prepare data success.
if(bat_cnt > 0) {
usecount+= bat_cnt;
count-= bat_cnt;
DPRINT("bat_cnt= %d\n", bat_cnt);
}
//Perhaps non node is avialable.
elseif (bat_cnt == 0) {
DPRINT("bat_cnt== 0\n");
break;
}
//Error occured.
else{
//break and handle prepared data.
if(usecount > 0) {
DPRINT("bat_cnt< 0, usecount > 0\n");
break;
}
//Has not prepared any data and return error when prepared data.
else{
DPRINT("bat_cnt< 0, usecount == 0\n");
returnbat_cnt;
}
}
}

首先检查传入音频块的大小,必须大于fragsize(4096),先来整体分析这个函数,然后

再进入内部分析细节,bat_cnt 根据返回值进行处理,返回0表明没有wfree节点,就直接

跳出,进行播放录音了。

[cpp]
view plaincopy

DPRINT("<<<<put_userdata\n");
node= endpoint_get_outnode(endpoint);
if(!node)
return0;
if(copy_from_user((void *)node->pBuf, buffer, count)) {
printk("JZI2S: copy_from_user failed !\n");
return-EFAULT;
}
LEAVE();
returnendpoint_post_outnode(endpoint,node,count);

用endpoint_get_outnode获得一个wfreenode,获得一个后就从wfree list上删除掉当

前节点, 播放完成后就返回结点,这样就形成了一个环形缓冲区。

最后返回到jz_audio_write_data, 最后如果当前while 循环结束后还有剩余的数据,就

重新做一次播放。

录音流程分析

由于播放和录音基本上是雷同的,故在此不做分析了,各位看官如想了解的话,欢迎与

我讨论, http://blog.csdn.net/dyron

本文完。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: