关于linux的音频驱动(二)

3.       关于sep4020_audio_write函数:

这个是整个驱动的核心,也是难点,牵涉了dma操作,buffer ring的思想,linux中信号量的思想。一下内容读起来会有点吃力,请好好理解代码

●关于dma:

对dma的操作,在这里使用了一个buffer ring的思想,这里我们来看一下建立dma缓冲环的代码来理解这种buffer ring:

static int audio_setup_buf(audio_stream_t * s)

{

int frag;

int dmasize = 0;

char *dmabuf = 0;

dma_addr_t dmaphys = 0;

 

if (s->buffers)

return -EBUSY;

 

s->nbfrags = audio_nbfrags;     //音频缓冲区片数量

s->fragsize = audio_fragsize;   //音频缓冲区片大小

 

s->buffers = (audio_buf_t *)   //buffers是整体缓冲区,它是一次性分配的

kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);//分配nbfrags*audio_buf_t结构体大小的空间

if (!s->buffers)

goto err;//没有分配成功,跳至err

memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags);//将整体缓冲区置0

for (frag = 0; frag < s->nbfrags; frag++) //初始化每个缓冲片区

{

audio_buf_t *b = &s->buffers[frag];

 

if (!dmasize) //其实这个if语句只执行了一次,

{

dmasize = (s->nbfrags - frag) * s->fragsize;

do {

dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL|GFP_DMA);

if (!dmabuf)

dmasize -= s->fragsize;

} while (!dmabuf && dmasize);

if (!dmabuf)

goto err;

b->master = dmasize;//给每个缓冲区赋大小

}

 

b->start = dmabuf;

b->dma_addr = dmaphys;

sema_init(&b->sem, 1);//初始化信号量

DPRINTK("buf %d: start %p dma %d/n", frag, b->start, b->dma_addr);

 

dmabuf += s->fragsize;

dmaphys += s->fragsize;

dmasize -= s->fragsize;//这三条实现了给每个片赋物理地址,虚拟地址

}

 

s->buf_idx = 0;

s->buf = &s->buffers[0];//这两句实现了缓冲区的环形,buffers是整个缓冲区,buffer是当前使用的缓冲区

return 0;

 

err:

printk(AUDIO_NAME ": unable to allocate audio memory/n ");

audio_clear_buf(s);

return -ENOMEM;

}

这样就实现了audio_nbfrags个大小为audio_fragsize的buffer环,

 

●信号量的思想:

typedef struct {

int size; /* buffer size */

char *start; /* point to actual buffer */

dma_addr_t dma_addr; /* physical buffer address */

struct semaphore sem; /* down before touching the buffer */

int master; /* owner for buffer allocation, contain size when true */

} audio_buf_t;

在对于每个单独的音频缓冲片中都定义了一个信号量sem,就是通过对这个sem信号量的获取和释放来管理缓冲区。

 

首先在初始化缓冲片的时候将信号量初始化为1,就是可用,然后对每个缓冲片使用的时候,先看是否能获得信号量,如果不能获得信号量说明这片缓冲区还在使用不能对其重新写,如果能获得信号量说明这片缓冲区已经释放可以继续使用;

缓冲区的释放是在dma的中断中实现的,在dma每次传输完成后会发出一个完成中断,在这个中断的处理函数里面释放信号量;

由于三星为对于dma的操作写了一系列标准化的接口,对于我们来说甚至有点复杂和啰嗦了,我们在这里采用了一种简便的实现方法(也可以说是偷懒,呵呵),在dma寄存器配置好后,立刻释放信号量,因为我们有8片缓冲区,这次用完了还有7片可以用,在这个时间段里面,dma肯定搬运结束了。

 

     整个写函数的实现:

先来看一下2410的写函数是怎么实现的:

整个流程可以分析为,

     上层给写函数传递一个要放音的数据包,首地址为&buffer,大小为count;

     函数根据缓冲区的大小给缓冲区填充数据,如果缓冲区的大小大于传递的包,则先传输一次包的量,如果缓冲区的待续哦啊小于传输的包,则先传输一次缓冲区的数据量,无论怎样,都要保证开的缓冲区的所有空间被填满,(因为一旦没有填满,就会有空白区间,对应于音频输出就是有停顿了)。

     把上层传递过来的数据包拷贝至dma缓冲区

     将这片缓冲区放进dma队列使用,并使用下一片缓冲片

static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,

size_t count, loff_t * ppos)

{

  const char *buffer0 = buffer; //此处定义buffer0,主要是判断结束条件的

  audio_stream_t *s = &output_stream;

  int chunksize, ret = 0;

  DPRINTK("audio_write : start count=%d/n", count);

  switch (file->f_flags & O_ACCMODE) {

         case O_WRONLY:

         case O_RDWR:

         break;

         default:

                return -EPERM;

  }

 

  if (!s->buffers && audio_setup_buf(s))

         return -ENOMEM;

  count &= ~0x03;

  while (count > 0) {

         audio_buf_t *b = s->buf;//注意:一下b都是s的一个缓冲片

         if (file->f_flags & O_NONBLOCK) {

                ret = -EAGAIN;

                if (down_trylock(&b->sem))

                       break;

         } else {

                ret = -ERESTARTSYS;

                //一般程序都是使用阻塞的,这句信号量的使用是为了

                //保证单独缓冲片已可以再次使用,在完成了对一片缓冲片

                //的使用之后,会up这片,来说明可以再次使用

                if (down_interruptible(&b->sem)) 

                break;

         }

//貌似audio_channel是双声道的意思?

         if (audio_channels == 2) {

                chunksize = s->fragsize - b->size;//刚开始b->size is 0 ,

                if (chunksize > count)

                chunksize = count;

                DPRINTK("write %d to %d/n", chunksize, s->buf_idx);

                if (copy_from_user(b->start + b->size, buffer, chunksize)) {

                       up(&b->sem);

                       return -EFAULT;

                }

                b->size += chunksize;//at this step,bsize is equal to the  chunksize

         } else {

                chunksize = (s->fragsize - b->size) >> 1;

                if (chunksize > count)

                chunksize = count;

                DPRINTK("write %d to %d/n", chunksize*2, s->buf_idx);

                if (copy_from_user_mono_stereo(b->start + b->size,

buffer, chunksize)) {

                up(&b->sem);

                return -EFAULT;

                }

                b->size += chunksize*2;

         }

         buffer += chunksize;//将要传的字符流往后滑动chunksize

         count -= chunksize;//将要传的总数减去已传的chunksize

 

         //如果这次填充缓冲片没有填满,会释放这一片的信号量,

         //继续对这一片进行操作,直到填满开的缓冲片大小为止,

         //保证了缓冲片内不会有空白区间,这对音频是致命的

         if (b->size < s->fragsize) {

         up(&b->sem);

         break;

         }

         if((ret = s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size))) {

                printk(PFX"dma enqueue failed./n");

                return ret;

         }

         b->size = 0;

         NEXT_BUF(s, buf);

  }

  //buffer0是要传数据的首地址,固定不动的,buffer是现在传的地址

  //通过buffer-buffer0,可以得到传了多少,只有在返回0的时候是表明

  //这次传递的包全部传完,不然系统应该通过ret得到断点处并继续

  if ((buffer - buffer0))

  ret = buffer - buffer0;

  DPRINTK("audio_write : end count=%d/n/n", ret);

  return ret;

}

对应于sep4020的操作:

这里可以看到整个函数都比较好利用并按照我们芯片的规则实现,但是在dma队列这一块由于三星2410使用了大量的dma标准操作,我们实现起来比较困难,因此我们采取我们自己的实现方式,效果同样很好,但是简单了很多。

 

首先分析:什么是dma队列,dma队列的作用就是将写函数分为了两个独立的环节,★一个是不停的从上层接受数据包,并放到空闲的缓冲片里面;

★一个是不同的通过dma通道将缓冲片的数据搬运到i2s数据通道里。

所谓的队列就是保证以上两个环节并行操作,并且让dma时刻有数据搬运。

 

那对于我们没有这种标准函数的怎么写?怎么通过其他方式实现队列?

因此我们在配置好dma寄存器后,立即进行下一片缓冲片的搬运,此时dma也在同时向i2s数据通道写数据,——实现了并行操作,无等待。

而根据实验测试,dma搬运的速度远远小于从上层拷贝到缓冲片的速度,也就是说我在准备好下一片缓冲片的时候,dma肯定还没有搬好,那我只需要等待dma搬好,并把下一片的缓冲数据给他,让他继续搬运,——实现了dma的无缝持续搬运。

 

具体代码实现:(把三星代码中红色的部分替换为一下部分)

while(test)//设置一个全局变量,在系统的第一次不用查询,以后每次都要查询

         {

//如果中断完成状态为1,表示已经搬运完,

                if(((*(volatile unsigned long*)DMAC_INTTCSTATUS_V) & 0x2 ) == 0x2) break;

         }

         //清除要用的dma通道(dma1)上的传输完成中断寄存器

         *(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x2;

         *(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x0;    

         test = 1;

//继续下一次搬运

         *(volatile unsigned long*)DMAC_C1CONTROL_V  = ((8192>>2)<<14) + (1<<12) + (2<<9) +(2<<6) + (3<<3) + 3;

         *(volatile unsigned long*)DMAC_C1SRCADDR_V  = b->dma_addr;

               *(volatile unsigned long*)DMAC_C1DESTADDR_V  = I2S_DATA ;

               *(volatile unsigned long*)DMAC_C1CONFIGURATION_V = 0x200b ; //iis

        

//同时释放信号量,让两个环节并行

         up(&b->sem); //add 1110

         wake_up(&b->sem.wait);

 

4.       关于写i2s的一些小注意点:

在写音频驱动的时候很容易碰上得到的音乐有不断的“嗒嗒”声音,一般的想肯定是认为数据传输不及时,导致有空白间隔,所以出现这种貌似停顿的声音,但事实上是如果音乐数据有数据遗漏也会出现这种声音,比如你这次给了一段连续的数据,但是紧接着跳到之后600个字节的地方,虽然数据流是连续的,但是由于mp3的独特编码(包含了心理声学模型)但是听出来的效果像是有空白间隔一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值