ALSA之PCM分析

http://blog.youkuaiyun.com/crycheng/article/details/7095899

CODEC :音频芯片的控制,比如静音、打开(关闭)ADC(DAC)、设置ADC(DAC)的增益、耳机模式的检测等操作。
I2S   :数字音频接口,用于CPU和Codec之间的数字音频流raw data的传输。每当有playback或record操作时,snd_soc_dai_ops.prepare()会被调用,启动I2S总线。
PCM   :我不知道为什么会取这个模块名,它其实是定义DMA操作的,用于将音频数据通过DMA传到I2S控制器的FIFO中。

这里的PCM实际是就是更新和管理音频数据流的地址,分配DMA等等,将RAM中存放的音频数据的地址传给I2S,不是PCM协议。
音频数据流向:
     | DMA |                     | I2S/PCM/AC97 |
RAM --------> I2SControllerFIFO -----------------> CODEC ----> SPK/Headset


PCM模块初始化:

  1. struct snd_soc_platform rk29_soc_platform = { 
  2.     .name       = "rockchip-audio"
  3.     .pcm_ops    = &rockchip_pcm_ops, 
  4.     .pcm_new    = rockchip_pcm_new
  5.     .pcm_free   = rockchip_pcm_free_dma_buffers
  6. }; 
  7. EXPORT_SYMBOL_GPL(rk29_soc_platform); 
  8.  
  9. static int __init rockchip_soc_platform_init(void) 
  10.         DBG("Enter::%s, %d\n", __FUNCTION__, __LINE__); 
  11.     return snd_soc_register_platform(&rk29_soc_platform); 
  12. module_init(rockchip_soc_platform_init); 
  13.  
  14. static void __exit rockchip_soc_platform_exit(void) 
  15.     snd_soc_unregister_platform(&rk29_soc_platform); 
struct snd_soc_platform rk29_soc_platform = {
	.name		= "rockchip-audio",
	.pcm_ops 	= &rockchip_pcm_ops,
	.pcm_new	= rockchip_pcm_new,
	.pcm_free	= rockchip_pcm_free_dma_buffers,
};
EXPORT_SYMBOL_GPL(rk29_soc_platform);

static int __init rockchip_soc_platform_init(void)
{
        DBG("Enter::%s, %d\n", __FUNCTION__, __LINE__);
	return snd_soc_register_platform(&rk29_soc_platform);
}
module_init(rockchip_soc_platform_init);

static void __exit rockchip_soc_platform_exit(void)
{
	snd_soc_unregister_platform(&rk29_soc_platform);
}

调用snd_soc_register_platform()向ALSA core注册一个snd_soc_platform结构体。


成员pcm_new需要调用dma_alloc_writecombine()给DMA分配一块write-combining的内存空间,并把这块缓冲区的相关信息保存到substream->dma_buffer中,相当于构造函数。pcm_free则相反。这些成员函数都还算简单,看看代码即可以理解其流程。


snd_pcm_ops

接着我们看一下snd_pcm_ops结构体,该结构体的操作函数集的实现是本模块的主体。

  1. struct snd_pcm_ops { 
  2.     int (*open)(struct snd_pcm_substream *substream); 
  3.     int (*close)(struct snd_pcm_substream *substream); 
  4.     int (*ioctl)(struct snd_pcm_substream * substream, 
  5.              unsigned int cmd, void *arg); 
  6.     int (*hw_params)(struct snd_pcm_substream *substream, 
  7.              struct snd_pcm_hw_params *params); 
  8.     int (*hw_free)(struct snd_pcm_substream *substream); 
  9.     int (*prepare)(struct snd_pcm_substream *substream); 
  10.     int (*trigger)(struct snd_pcm_substream *substream, int cmd); 
  11.     snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); 
  12.     int (*copy)(struct snd_pcm_substream *substream, int channel, 
  13.             snd_pcm_uframes_t pos, 
  14.             void __user *buf, snd_pcm_uframes_t count); 
  15.     int (*silence)(struct snd_pcm_substream *substream, int channel,  
  16.                snd_pcm_uframes_t pos, snd_pcm_uframes_t count); 
  17.     struct page *(*page)(struct snd_pcm_substream *substream, 
  18.                  unsigned long offset); 
  19.     int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); 
  20.     int (*ack)(struct snd_pcm_substream *substream); 
  21. }; 
struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream);
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
	int (*copy)(struct snd_pcm_substream *substream, int channel,
		    snd_pcm_uframes_t pos,
		    void __user *buf, snd_pcm_uframes_t count);
	int (*silence)(struct snd_pcm_substream *substream, int channel, 
		       snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset);
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
	int (*ack)(struct snd_pcm_substream *substream);
};


我们主要实现open、close、hw_params、hw_free、prepare和trigger接口。


open

open函数为PCM模块设定支持的传输模式、数据格式、通道数、period等参数,并为playback/capture stream分配相应的DMA通道。其一般实现如下:

  1. static int rockchip_pcm_open(struct snd_pcm_substream *substream) 
  2.     struct snd_pcm_runtime *runtime = substream->runtime; 
  3.     struct rockchip_runtime_data *prtd; 
  4.  
  5.     DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); 
  6.  
  7.     snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware); 
  8.  
  9.     prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL); 
  10.     if (prtd == NULL) 
  11.         return -ENOMEM; 
  12.  
  13.     spin_lock_init(&prtd->lock); 
  14.  
  15.     runtime->private_data = prtd
  16.     return 0; 
static int rockchip_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct rockchip_runtime_data *prtd;

	DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);

	snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware);

	prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL);
	if (prtd == NULL)
		return -ENOMEM;

	spin_lock_init(&prtd->lock);

	runtime->private_data = prtd;
	return 0;
}


其中硬件参数要根据芯片的数据手册来定义,如:

  1. int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, 
  2.     const struct snd_pcm_hardware *hw) 
  3.     struct snd_pcm_runtime *runtime = substream->runtime; 
  4.     runtime->hw.info = hw->info; 
  5.     runtime->hw.formats = hw->formats; 
  6.     runtime->hw.period_bytes_min = hw->period_bytes_min; 
  7.     runtime->hw.period_bytes_max = hw->period_bytes_max; 
  8.     runtime->hw.periods_min = hw->periods_min; 
  9.     runtime->hw.periods_max = hw->periods_max; 
  10.     runtime->hw.buffer_bytes_max = hw->buffer_bytes_max; 
  11.     runtime->hw.fifo_size = hw->fifo_size; 
  12.     return 0; 
int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
	const struct snd_pcm_hardware *hw)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	runtime->hw.info = hw->info;
	runtime->hw.formats = hw->formats;
	runtime->hw.period_bytes_min = hw->period_bytes_min;
	runtime->hw.period_bytes_max = hw->period_bytes_max;
	runtime->hw.periods_min = hw->periods_min;
	runtime->hw.periods_max = hw->periods_max;
	runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
	runtime->hw.fifo_size = hw->fifo_size;
	return 0;
}


 

关于peroid的概念有这样的描述:The “period” is a term that corresponds to a fragment in the OSS world. The period defines the size at which a PCM interrupt is generated. peroid的概念很重要,建议去alsa官网找相关详细说明了解一下。

上层ALSA lib可以通过接口来获得这些参数的,如snd_pcm_hw_params_get_buffer_size_max()来取得buffer_bytes_max。

hw_free是hw_params的相反操作,调用snd_pcm_set_runtime_buffer(substream, NULL)即可。
注:代码中的dma_buffer是DMA缓冲区,它通过4个字段定义:dma_area、dma_addr、dma_bytes和dma_private。其中dma_area是缓冲区逻辑地址,dma_addr是缓冲区的物理地址,dma_bytes是缓冲区的大小,dma_private是ALSA的DMA管理用到的。dma_buffer是在pcm_new()中初始化的;当然也可以把分配dma缓冲区的工作放到这部分来实现,但考虑到减少碎片,故还是在pcm_new中以最大size(即buffer_bytes_max)来分配。

关于DMA的中断处理

另外留意open函数中的audio_dma_request(&s[0], audio_dma_callback);中的audio_dma_callback,这是dma的中断函数,这里以callback的形式存在,其实到dma的底层还是这样的形式:static irqreturn_t dma_irq_handler(int irq, void *dev_id),在DMA中断处理dma_irq_handler()中调用callback。这些跟具体硬件平台的DMA实现相关,如果没有类似的机制,那么还是要在pcm模块中实现这个中断。


 

  1. void rockchip_pcm_dma_irq(s32 ch, void *data) 
  2. {     
  3.         struct snd_pcm_substream *substream = data
  4.     struct rockchip_runtime_data *prtd; 
  5.     unsigned long flags; 
  6.      
  7.     DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); 
  8.  
  9.     prtd = substream->runtime->private_data; 
  10.     if (substream) 
  11.         snd_pcm_period_elapsed(substream); 
  12.     spin_lock(&prtd->lock); 
  13.     prtd->dma_loaded--; 
  14.     if (prtd->state & ST_RUNNING) { 
  15.         rockchip_pcm_enqueue(substream); 
  16.     } 
  17.         spin_unlock(&prtd->lock); 
  18.         local_irq_save(flags); 
  19.     if (prtd->state & ST_RUNNING) { 
  20.         if (prtd->dma_loaded) { 
  21.             if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 
  22.                 audio_start_dma(substream, DMA_MODE_WRITE); 
  23.             else 
  24.                 audio_start_dma(substream, DMA_MODE_READ); 
  25.         } 
  26.     } 
  27.     local_irq_restore(flags);    
void rockchip_pcm_dma_irq(s32 ch, void *data)
{    
        struct snd_pcm_substream *substream = data;
	struct rockchip_runtime_data *prtd;
	unsigned long flags;
	
	DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);

	prtd = substream->runtime->private_data;
	if (substream)
		snd_pcm_period_elapsed(substream);
	spin_lock(&prtd->lock);
	prtd->dma_loaded--;
	if (prtd->state & ST_RUNNING) {
		rockchip_pcm_enqueue(substream);
	}
        spin_unlock(&prtd->lock);
        local_irq_save(flags);
	if (prtd->state & ST_RUNNING) {
		if (prtd->dma_loaded) {
			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
				audio_start_dma(substream, DMA_MODE_WRITE);
			else
				audio_start_dma(substream, DMA_MODE_READ);
		}
	}
	local_irq_restore(flags);   
}

prepare

当pcm“准备好了”调用该函数。在这里根据channels、buffer_bytes等来设定DMA传输参数,跟具体硬件平台相关。注:每次调用snd_pcm_prepare()的时候均会调用prepare函数。


trigger

当pcm开始、停止、暂停的时候都会调用trigger函数。


 

  1. static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 
  2.     struct rockchip_runtime_data *prtd = substream->runtime->private_data; 
  3.     int ret = 0
  4.     /**************add by qiuen for volume*****/ 
  5.     struct snd_soc_pcm_runtime *rtd = substream->private_data; 
  6.     struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai; 
  7.     int vol = 0
  8.     int streamType = 0
  9.      
  10.     DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); 
  11.      
  12.     if(cmd==SNDRV_PCM_TRIGGER_VOLUME){ 
  13.         vol = substream->number % 100; 
  14.         streamType = (substream->number / 100) % 100; 
  15.         DBG("enter:vol=%d,streamType=%d\n",vol,streamType); 
  16.         if(pCodec_dai->ops->set_volume) 
  17.             pCodec_dai->ops->set_volume(streamType, vol); 
  18.     } 
  19.     /****************************************************/ 
  20.     spin_lock(&prtd->lock); 
  21.  
  22.     switch (cmd) { 
  23.     case SNDRV_PCM_TRIGGER_START: 
  24.             DBG(" START \n"); 
  25.         prtd->state |= ST_RUNNING; 
  26.         rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START); 
  27.         /* 
  28.         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 
  29.             audio_start_dma(substream, DMA_MODE_WRITE); 
  30.         } else { 
  31.             audio_start_dma(substream, DMA_MODE_READ); 
  32.         } 
  33.         */ 
  34. #ifdef CONFIG_ANDROID_POWER         
  35.         android_lock_suspend(&audio_lock); 
  36.         DBG("%s::start audio , lock system suspend\n" , __func__ ); 
  37. #endif       
  38.         break; 
  39.     case SNDRV_PCM_TRIGGER_RESUME: 
  40.         DBG(" RESUME \n"); 
  41.         break; 
  42.     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 
  43.         DBG(" RESTART \n"); 
  44.         break; 
  45.  
  46.     case SNDRV_PCM_TRIGGER_STOP: 
  47.     case SNDRV_PCM_TRIGGER_SUSPEND: 
  48.     case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 
  49.         DBG(" STOPS \n"); 
  50.         prtd->state &= ~ST_RUNNING; 
  51.         rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP); 
  52.         //disable_dma(prtd->params->channel); 
  53. #ifdef CONFIG_ANDROID_POWER         
  54.         android_unlock_suspend(&audio_lock ); 
  55.         DBG("%s::stop audio , unlock system suspend\n" , __func__ ); 
  56. #endif 
  57.          
  58.         break; 
  59.     default: 
  60.         ret = -EINVAL; 
  61.         break; 
  62.     } 
  63.  
  64.     spin_unlock(&prtd->lock); 
  65.     return ret; 
static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct rockchip_runtime_data *prtd = substream->runtime->private_data;
	int ret = 0;
	/**************add by qiuen for volume*****/
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai;
	int vol = 0;
	int streamType = 0;
	
	DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
	
	if(cmd==SNDRV_PCM_TRIGGER_VOLUME){
		vol = substream->number % 100;
		streamType = (substream->number / 100) % 100;
		DBG("enter:vol=%d,streamType=%d\n",vol,streamType);
		if(pCodec_dai->ops->set_volume)
			pCodec_dai->ops->set_volume(streamType, vol);
	}
	/****************************************************/
	spin_lock(&prtd->lock);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	        DBG(" START \n");
	    prtd->state |= ST_RUNNING;
	    rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START);
	    /*
	    if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		    audio_start_dma(substream, DMA_MODE_WRITE);
		} else {
		    audio_start_dma(substream, DMA_MODE_READ);
		}
		*/
#ifdef CONFIG_ANDROID_POWER        
        android_lock_suspend(&audio_lock);
        DBG("%s::start audio , lock system suspend\n" , __func__ );
#endif		
		break;
	case SNDRV_PCM_TRIGGER_RESUME:
	    DBG(" RESUME \n");
	    break;
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		DBG(" RESTART \n");
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	    DBG(" STOPS \n");
		prtd->state &= ~ST_RUNNING;
		rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP);
		//disable_dma(prtd->params->channel);
#ifdef CONFIG_ANDROID_POWER        
        android_unlock_suspend(&audio_lock );
        DBG("%s::stop audio , unlock system suspend\n" , __func__ );
#endif
		
		break;
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock(&prtd->lock);
	return ret;
}


 

Trigger函数里面的操作应该是原子的,不要在调用这些操作时进入睡眠,trigger函数应尽量小,甚至仅仅是触发DMA。


pointer

static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream)
PCM中间层通过调用这个函数来获取缓冲区的位置。一般情况下,在中断函数中调用snd_pcm_period_elapsed()或在pcm中间层更新buffer的时候调用它。然后pcm中间层会更新指针位置和计算缓冲区可用空间,唤醒那些在等待的线程。这个函数也是原子的。

snd_pcm_runtime
我们会留意到ops各成员函数均需要取得一个snd_pcm_runtime结构体指针,这个指针可以通过substream->runtime来获得。snd_pcm_runtime是pcm运行时的信息。当打开一个pcm子流时,pcm运行时实例就会分配给这个子流。它拥有很多多种信息:hw_params和sw_params配置拷贝,缓冲区指针,mmap记录,自旋锁等。snd_pcm_runtime对于驱动程序操作集函数是只读的,仅pcm中间层可以改变或更新这些信息。



更多 0

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值