zoukankan      html  css  js  c++  java
  • ALSA声卡10_从零编写之数据传输_学习笔记

    1、引言

    (1)应用程序使用声卡的时候,数据流程是:应用程序把数据发送给驱动,驱动把数据发送给硬件声卡,声卡把数据转换成声音数据播放出去。

    (2)可以使用两种方式发送数据

    第一种:app发数据,等驱动处理完后再发下一段(处理完再发下一段就会导致声音会断断续续 )

    第二种:应用程序不断地发数据,驱动程序不断地取数据,不断地发给硬件。解决了声音断续的问题,但是要创建一个非常大的缓冲区(在驱动程序里面申请的 ,称其为buffer)



    一个采样点的数据包括左声道数据和右声道数据


    这里hw_ptr是指针( 更新是指指针向后移)


    2、怎么写驱动(s3c2440_dma.c(platform))

    (1)负责数据传输的是平台部分里面的DMA文件,修改s3c2440_dma.c



    3、分配/释放buffer

    创建声卡时,s3c2440_dma_new函数被调用,函数里分配dma buffer.销毁声卡时,释放dma buffer


    (1)分配DMA BUFFER

    static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)
    {
    struct snd_card *card = rtd->card->snd_card;
    struct snd_pcm *pcm = rtd->pcm;
    struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
    struct snd_dma_buffer *buf = &substream->dma_buffer;


    int ret = 0;


        /* 1. 分配DMA BUFFER */
    if (!card->dev->dma_mask)
    card->dev->dma_mask = &dma_mask;
    if (!card->dev->coherent_dma_mask)
    card->dev->coherent_dma_mask = DMA_BIT_MASK(32);


    if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {//播放功能
       playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card-   >dev, s3c2440_dma_hardware.buffer_bytes_max,
      &playback_dma_info.phy_addr, GFP_KERNEL);//分配DMA BUFFER   //virt_addr 是buffer的虚拟地址,buffer_bytes_max是分配缓冲区的大小,phy_addr是所分配缓冲区的物理地址
            if (!playback_dma_info.virt_addr)//如果虚拟地址为空,返回错误
            {
                return -ENOMEM; //返回没有内存的错误标志
            }
            playback_dma_info.buf_max_size = s3c2440_dma_hardware.buffer_bytes_max;//分配成功,记录分配buffer的大小,尽管分配128K,但用到多少是app决定的 

        //把buffer信息告诉alsa的核心层
        buf->dev.type = SNDRV_DMA_TYPE_DEV;
        buf->dev.dev = pcm->card->dev;
        buf->private_data = NULL;
            buf->area = playback_dma_info.virt_addr;//播放相关的dma  buffer  的信息的结构体
            buf->bytes = playback_dma_info.buf_max_size;//buf的大小
    }//


    return ret;
    }

    (2)释放DMA BUFFER

    static void s3c2440_dma_free(struct snd_pcm *pcm)
    {
    dma_free_writecombine(pcm->card->dev, playback_dma_info.buf_max_size,
         (void *)playback_dma_info.virt_addr, playback_dma_info.phy_addr);//释放的是playback(播放)部分的dma buffer(buffer大小,buffer的虚拟地址,buffer的物理地址)
    }



    4、request_irq

    (1)open函数

    static int s3c2440_dma_open(struct snd_pcm_substream *substream)
    {
    struct snd_pcm_runtime *runtime = substream->runtime;
        int ret;
        /* 注册中断 */
        ret = request_irq(IRQ_DMA2,s3c2440_dma2_irq, IRQF_DISABLED, "myalsa for playback",substream);
        if (ret)   //IRQ_DMA2是中断号,s3c2440_dma2_irq是中断处理函数, IRQF_DISABLED是标志(当发生中断时,在中断处理过程中,中断是保持屏蔽的), "myalsa for playback"是中断名字,substream是device ID 
        {
            printk("request_irq error! ");
            return -EIO;
        }


    return 0;
    }

    (2)中断请求函数(数据传输成功后,更新hw_ptr等信息)

    static irqreturn_t s3c2440_dma2_irq(int irq, void *devid) //左边参数是中断号,右边是设备ID
    {
        struct snd_pcm_substream *substream = devid;//在open函数调用的请求中断函数 request_irq中的给中断处理函数s3c2440_dma2_irq的参数substream
            
        /* 更新状态信息 */  当传输完一个DMA后,发生中断
        playback_dma_info.dma_ofs += playback_dma_info.period_size;//偏移地址向后移一个period
        if (playback_dma_info.dma_ofs >= playback_dma_info.buffer_size)//如果偏移地址超过buffer 的范围
            playback_dma_info.dma_ofs = 0;//就指向buffer的开头
        
        /* 更新hw_ptr等信息,(驱动部分)
         * 并且判断:如果buffer里没有数据了,则调用trigger来停止DMA 
         */

    传完一个DMA,
        snd_pcm_period_elapsed(substream);  


        if (playback_dma_info.be_running)
        {
            /* 如果还有数据,be_running是1表明在运行
             * 1. 加载下一个period 
             * 2. 再次启动DMA传输
             */

            load_dma_period();
            s3c2440_dma_start();
        }


        return IRQ_HANDLED;
    }



    5、 s3c2440_dma_hw_params(准备DMA传输的参设设置)

    static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream,
    struct snd_pcm_hw_params *params)
    {
    struct snd_pcm_runtime *runtime = substream->runtime;
    unsigned long totbytes = params_buffer_bytes(params);//所使用的buffer大小由应用程序传进来的参数params决定的
        
        /* 根据params设置DMA */

    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);


        /* s3c2440_dma_new(驱动程序里面)分配了很大的DMA BUFFER,应用程序会用多大是由应用程序决定的,传进来的参数params
         * params决定使用多大
         */

    runtime->dma_bytes            = totbytes;//记录所使用的大小,以后会用到,应用程序写到尾部时会返回来,尾部在哪里由尾部决定的。
        playback_dma_info.buffer_size = totbytes;//buffer_size
        playback_dma_info.period_size = params_period_bytes(params);//period_size,buffer里面,一次DMA传输是以period传输的


        return 0;
    }


    6、prepare函数(准备DMA传输的数据)


    static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)
    {
        /* 准备DMA传输 */

        /* 复位各种状态信息(清零) */

        playback_dma_info.dma_ofs = 0;//偏移值设置为0,以后DMA传输从开始处开始
        playback_dma_info.be_running = 0;//表示DMA尚未启动,还没运行
        
        /* 加载第1个period */
        load_dma_period();


    return 0;
    }





    return ret;
    }


    7、加载需要传输的数据

    /* 数据传输: 源,目的,长度 */
    static void load_dma_period(void)
    {       
    /* 把源,目的,长度告诉DMA */
    dma_regs->disrc      = playback_dma_info.phy_addr + playback_dma_info.dma_ofs;       /* 源的物理地址 */物理地址+偏移地址
    dma_regs->disrcc     = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
    dma_regs->didst      = 0x55000010;        /* 目的的物理地址 */IIC控制器
    dma_regs->didstc     = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB总线, 目的地址不变 */


        /* bit22: 1-noreload *表示传完一段数据后会重新加载某些值,自己启动DMA,在我们的中断程序是自己启动DMA的,所以不让它自动下载,因而把它设置为1,自动加载的话在中断函数还没有处理前就自动传输数据/解决播放存在杂音的问题
    dma_regs->dcon       = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<22)|(1<<20)|(playback_dma_info.period_size/2);  /* 使能中断,单个传输,硬件触发 */period_size长度,每次传输不是传输整个buffer,是传输里面的某个period,period_size大小。buffer_size是应用程序所使用buffer的大小,buf_max_size是驱动程序分配的buffer大小,应用程序只可能使用里面的一部分。除以2是因为(1<<20)也就是2个字节,因而以字节为单位所以要除以2。也就是传输多少次,每次传输2个字节。
    }



    8、dma_trigger函数(触发DMA传输,传输完成后,产生中断,进入中断处理函数s3c2440_dma2_irq


    static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)
    {
    int ret = 0;


        /* 根据cmd启动或停止DMA传输 */
    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
            /* 启动DMA传输 */
            playback_dma_info.be_running = 1;//还有数据的状态信息
            s3c2440_dma_start();
    break;


    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
            /* 停止DMA传输 */
            playback_dma_info.be_running = 0;//传完数据的状态信息
            s3c2440_dma_stop();
    break;


    default:
    ret = -EINVAL;
    break;
    }

    9、dma_pointer函数(表示下一次DMA传输的位置(每一次DMA传输一个period),hw_ptr指针是根据DMA位置来确定的)

    /* 返回结果是frame(单位是frame) */
    static snd_pcm_uframes_t s3c2440_dma_pointer(struct snd_pcm_substream *substream)
    {
    return bytes_to_frames(substream->runtime, playback_dma_info.dma_ofs);//playback_dma_info.dma_ofs是DMA传输的偏移地址,如果偏移地址超出buffer的范围,就应该回到开始的位置,假如一个period里面有3个frame,如果位置指向period开始处,返回值是3,指向右边是6.也就是
    }

    10、总结

    (1)创建声卡时导致 s3c2440_dma_new函数被调用

    (2)s3c2440_dma_new函数里面分配DMA  BUFFER(分配最大的,128K,但并不表示一定要用完(根据应用程序参数决定))

    (3)应用程序打开设备,播放声音的时候,在open函数(s3c2440_dma_open),设置属性,注册中断;在close函数中,释放中断

    (4)设置参数(在s3c2440_dma_hw_params函数中),驱动程序分配了很大一块空间,确定buffer_size(应用程序传进来的params参数决定的),period_size(buffer里是逐个peroid传输的)

    一次DMA传输是以period传输的

    (5)准备DMA传输(s3c2440_dma_prepare),加载第一个period(load_dma_period)

    (6)启动DMA 传输(s3c2440_dma_trigger),一个period传输完后会产生中断)调用中断处理函数(s3c2440_dma2_irp更新偏移地址。更新alsa驱动的其他状态信息。如果还有数据,再次加载period,再次启动


    转自:http://blog.csdn.net/qingkongyeyue/article/details/54633478

  • 相关阅读:
    HDU 1269 迷宫城堡
    HDU 4771 Stealing Harry Potter's Precious
    HDU 4772 Zhuge Liang's Password
    HDU 1690 Bus System
    HDU 2112 HDU Today
    HDU 1385 Minimum Transport Cost
    HDU 1596 find the safest road
    HDU 2680 Choose the best route
    HDU 2066 一个人的旅行
    AssetBundle管理机制(下)
  • 原文地址:https://www.cnblogs.com/alan666/p/8311866.html
Copyright © 2011-2022 走看看