ALSA driver--HW Buffer
当app在调用snd_pcm_writei时,alsa core将app传来的数据搬到HW buffer(即DMA buffer)中,alsa driver从HW buffer中读取数据传输到硬件播放。
ALSA buffer是采用ring buffer来实现的。ring buffer有多个HW buffer组成。
HW buffer一般是在alsa driver的hw_params函数中分配的一块大小为buffer size的DMA buffer.
之所以采用多个HW buffer来组成ring buffer,是防止读写指针的前后位置频繁的互换(即写指针到达HW buffer边界时,就要回到HW buffer起始点)。
ring buffer = n * HW buffer.通常这个n比较大,在数据读写的过程中,很少会出现读写指针互换的情况。
下图是ALSA buffer的实现以及读写指针更新的方法,

hw_ptr_base是当前HW buffer在Ring buffer中的起始位置。当读指针到达HW buffer尾部时,hw_ptr_base按buffer size移动.
hw_ptr即HW buffer的读指针。alsa driver将数据从HW buffer中读走并送到声卡硬件时,hw_ptr就会移动到新位置。
appl_ptr即HW buffer的写指针。app在调用snd_pcm_write写数据,alsa core将数据copy到HW buffer后,appl_ptr就更新。
boundary即Ring buffer边界。
hw_ofs是读指针在当前HW buffer中的位置。由alsa driver的pointer()返回。
appl_ofs是写指针在当前HW buffer中的位置。
hw_ptr的更新是通过调用snd_pcm_update_hw_ptr0完成。此函数在app写数据时会调用,也会在硬件中断时通过snd_pcm_peroid_elapsed调用。
static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
				  unsigned int in_interrupt)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_pcm_uframes_t pos;
	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
	snd_pcm_sframes_t hdelta, delta;
	unsigned long jdelta;
	unsigned long curr_jiffies;
	struct timespec curr_tstamp;
	struct timespec audio_tstamp;
	int crossed_boundary = 0;
old_hw_ptr = runtime->status->hw_ptr;//保存上一次的hw_ptr,在此函数中将更新hw_ptr
/*
	 * group pointer, time and jiffies reads to allow for more
	 * accurate correlations/corrections.
	 * The values are stored at the end of this routine after
	 * corrections for hw_ptr position
	 */
	  pos = substream->ops->pointer(substream);//获取hw_ptr在当前HW buffer中的偏移
	  curr_jiffies = jiffies;
	  if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {//获取当前的time stamp
		    if ((substream->ops->get_time_info) &&
			     (runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
			        substream->ops->get_time_info(substream, &curr_tstamp,&audio_tstamp,&runtime->audio_tstamp_config,&runtime->audio_tstamp_report);
        /* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */
			        if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)
				          snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
		        } else
			          snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
	        }
  if (pos == SNDRV_PCM_POS_XRUN) {//发生XRUN
		    xrun(substream);
		    return -EPIPE;
	  }
	  if (pos >= runtime->buffer_size) {//pos大于buffer size,出现异常
		    if (printk_ratelimit()) {
			      char name[16];
			      snd_pcm_debug_name(substream, name, sizeof(name));
			      pcm_err(substream->pcm,"invalid position: %s, pos = %ld, buffer size = %ld, period size = %ld\n",name, pos, runtime->buffer_size,
				runtime->period_size);
		    }
		    pos = 0;
	  }
	  pos -= pos % runtime->min_align;
	  trace_hwptr(substream, pos, in_interrupt);
	  hw_base = runtime->hw_ptr_base;//当前的hw_base
	  new_hw_ptr = hw_base + pos;//当前的hw_ptr
	  if (in_interrupt) {//如果是因为硬件中断调用的此函数
		  /* we know that one period was processed */
		  /* delta = "expected next hw_ptr" for in_interrupt != 0 */
		  delta = runtime->hw_ptr_interrupt + runtime->period_size;
		  if (delta > new_hw_ptr) {//如果本次通过中断位置加上period_size计算出来的hw_ptr比当前hw_ptr大的话,则说明上一次中断没处理,有可能hw_base需要更新到下一个HW buffer的基地址。
			    /* check for double acknowledged interrupts */
			    hdelta = curr_jiffies - runtime->hw_ptr_jiffies;
			    if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) {//距离上一次的jiffies大于整个buffer 的jiffies的一半。
				      hw_base += runtime->buffer_size;
				      if (hw_base >= runtime->boundary) {
					        hw_base = 0;
					        crossed_boundary++;
				      }
				      new_hw_ptr = hw_base + pos;
				      goto __delta;
			    }
		  }
	}
	  /* new_hw_ptr might be lower than old_hw_ptr in case when */
	  /* pointer crosses the end of the ring buffer */
	  if (new_hw_ptr < old_hw_ptr) {//如果当前的hw_ptr比上一次的hw_ptr小,hw_ptr超过了HW buffer边界。hw_base需要更新到下一个HW buffer的基地址。hw_ptr也要同步更新。
		    hw_base += runtime->buffer_size;
		    if (hw_base >= runtime->boundary) {//如果hw_base > boundary,那hw_base回跳到Ring Buffer起始位置。
			      hw_base = 0;
			      crossed_boundary++;
		    }
		    new_hw_ptr = hw_base + pos;
	  }
      __delta:
	  delta = new_hw_ptr - old_hw_ptr;
	  if (delta < 0)//如果当前的hw_ptr任然比上一的hw_ptr小,说明hw_ptr走完了Ring buffer一圈。
		    delta += runtime->boundary;
if (runtime->no_period_wakeup) {//如果硬件处理完一个peroid数据后不产生中断,就不更新hw_ptr
    snd_pcm_sframes_t xrun_threshold;
		/*
		 * Without regular period interrupts, we have to check
		 * the elapsed time to detect xruns.
		 */
		    jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
		    if (jdelta < runtime->hw_ptr_buffer_jiffies / 2)
			      goto no_delta_check;
		    hdelta = jdelta - delta * HZ / runtime->rate;
		    xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1;
		    while (hdelta > xrun_threshold) {
			      delta += runtime->buffer_size;
			      hw_base += runtime->buffer_size;
			      if (hw_base >= runtime->boundary) {
				        hw_base = 0;
				        crossed_boundary++;
			      }
			      new_hw_ptr = hw_base + pos;
			      hdelta -= runtime->hw_ptr_buffer_jiffies;
		    }
		    goto no_delta_check;
	  }
/* something must be really wrong */
	  if (delta >= runtime->buffer_size + runtime->period_size) {//如果当前hw_ptr比较上一次相差buffer size + peroid size,说明有错误。
		    hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr",
			         "(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
			         substream->stream, (long)pos,
			         (long)new_hw_ptr, (long)old_hw_ptr);
		    return 0;
	  }
  /* Do jiffies check only in xrun_debug mode */
	  if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
		    goto no_jiffies_check;
/* Skip the jiffies check for hardwares with BATCH flag.
	 * Such hardware usually just increases the position at each IRQ,
	 * thus it can't give any strange position.
	 */
	  if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
		    goto no_jiffies_check;
	  hdelta = delta;
	  if (hdelta < runtime->delay)
		    goto no_jiffies_check;
	  hdelta -= runtime->delay;
	  jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
	  if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
		    delta = jdelta /(((runtime->period_size * HZ) / runtime->rate)+ HZ/100);
		    /* move new_hw_ptr according jiffies not pos variable */
		    new_hw_ptr = old_hw_ptr;
		    hw_base = delta;
		    /* use loop to avoid checks for delta overflows */
		    /* the delta value is small or zero in most cases */
		    while (delta > 0) {
			      new_hw_ptr += runtime->period_size;
			      if (new_hw_ptr >= runtime->boundary) {
				        new_hw_ptr -= runtime->boundary;
				        crossed_boundary--;
			      }
			      delta--;
		    }
		/* align hw_base to buffer_size */
		    hw_ptr_error(substream, in_interrupt, "hw_ptr skipping",
			         "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
			         (long)pos, (long)hdelta,
			         (long)runtime->period_size, jdelta,
			         ((hdelta * HZ) / runtime->rate), hw_base,
			         (unsigned long)old_hw_ptr,
			         (unsigned long)new_hw_ptr);
		    /* reset values to proper state */
		    delta = 0;
		    hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
	  }
 no_jiffies_check:
	  if (delta > runtime->period_size + runtime->period_size / 2) {//interupt丢失,delta(如果当前hw_ptr比较上一次之差)>1.5个peroid size
		    hw_ptr_error(substream, in_interrupt,
			         "Lost interrupts?",
			         "(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
			         substream->stream, (long)delta,
			         (long)new_hw_ptr,
			         (long)old_hw_ptr);
	  }
no_delta_check:
	  if (runtime->status->hw_ptr == new_hw_ptr) {//hw_ptr没变化
		    update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
		    return 0;
	  }
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&runtime->silence_size > 0)
		    snd_pcm_playback_silence(substream, new_hw_ptr);//播放silence
  if (in_interrupt) {//更新hw_ptr_interrupt
		    delta = new_hw_ptr - runtime->hw_ptr_interrupt;
		    if (delta < 0)
			      delta += runtime->boundary;
		    delta -= (snd_pcm_uframes_t)delta % runtime->period_size;
		    runtime->hw_ptr_interrupt += delta;
		    if (runtime->hw_ptr_interrupt >= runtime->boundary)
			      runtime->hw_ptr_interrupt -= runtime->boundary;
	  }
	  runtime->hw_ptr_base = hw_base;//将更新后的值保存到runtime中
	  runtime->status->hw_ptr = new_hw_ptr;
	  runtime->hw_ptr_jiffies = curr_jiffies;
	  if (crossed_boundary) {
		    snd_BUG_ON(crossed_boundary != 1);
		    runtime->hw_ptr_wrap += runtime->boundary;
	  }
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
return snd_pcm_update_state(substream, runtime);//通过本次更新的 件指针,检查是否XRUN,或唤醒等待在他上面的队列如poll,lib_write1
}
ALSA driver--HW Buffer的更多相关文章
- ALSA driver基本概念
		
https://blog.csdn.net/zyuanyun/article/details/59180272#t6 1.Card For each soundcard, a “card” recor ...
 - ALSA driver --PCM 实例创建过程
		
前面已经写过PCM 实例的创建框架,我们现在来看看PCM 实例是如何创建的. 在调用snd_pcm_new时就会创建一个snd_pcm类型的PCM 实例. struct snd_pcm { struc ...
 - ALSA 学习小记
		
对于playback snd_pcm_begin snd_pcm_commit, 貌似 commit给的frame才会使得alsa去把数据填充 转自 http://magodo.github.io/ ...
 - Introduction to Sound Programming with ALSA
		
ALSA stands for the Advanced Linux Sound Architecture. It consists of a set of kernel drivers, an ap ...
 - Linux ALSA声卡驱动之八:ASoC架构中的Platform
		
1. Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...
 - ALSA driver--PCM实例创建框架
		
在介绍PCM 之前,我们先给出创建PCM实例的框架. #include <sound/pcm.h> .... /* hardware definition */ static struct ...
 - ALSA lib基本概念
		
1.channel 通道,即我们熟知的声道数.左/右声道,5.1channel等等 2.sample A sample is a single value that describes the amp ...
 - Linux下实现视频读取(三)---Buffer的准备和数据读取
		
前面主要介绍的是:V4L2 的一些设置接口,如亮度,饱和度.曝光时间,帧数,增益.白平衡等.今天看看V4L2 得到数据的几个关键ioctl,Buffer的申请和数据的抓取. 1. 初始化 Memory ...
 - ALSA学习资料
		
一.内核文档 Linux Sound Subsystem Documentation 二.一些API 1.snd_pcm_period_elapsed 2.snd_pcm_lib_buffer_by ...
 
随机推荐
- Python 之路Day13
			
匿名函数 一行函数 lambda == def -- 关键字 lambda x:x x 是普通函数的形参(位置,关键字……)可以不接收参数,可以不写 :x 是普通函数的函数值(只能返回一个数据类型), ...
 - [CQOI2015] 网络吞吐量 - 最大流,最短路
			
在第i个点只能选A[i]次的情况下,能选出多少条1-n的最短路 Solution 我们造出最短路DAG,然后对每个点拆点限流,跑最大流即可 双向边警告!(有悖直觉 #include <bits/ ...
 - Harris角点检测理论
			
这样想象一下,直线上的一个点在垂直于直线的方向上有最强的梯度.沿着直线的方向梯度较低,意思是直线上的像素点与它周围的像素点看起来相似.我们进行的角点检测是梯度强度明显高于其他像素的点,可能就是目标处 ...
 - Python 序列化与反序列化
			
序列化是为了将内存中的字典.列表.集合以及各种对象,保存到一个文件中(字节流).而反序列化是将字节流转化回原始的对象的一个过程. json库 序列化:json.dumps() 反序列化:json.lo ...
 - PyQt5+Eric6开发的一个使用菜单栏、工具栏和状态栏的示例
			
前言 在做一个数据分析的桌面端程序遇到一些问题,这里简单整理下,分享出来供使用者参考. 1.网上查使用PyQt5工具栏的示例,发现很多只是一个简单的退出功能,如果有几个按钮如何处理?如何区分点击的究竟 ...
 - C#  asp.net  连接Mysql 数据库
			
首先添加 引用: using System.Data;using MySql.Data.MySqlClient; 代码: 这里只写了 后台代码 Mysql 连接和sql 连接代码几乎一样 只要把 My ...
 - python:复制文件及文件夹
			
#!/usr/bin/python# -*- coding:utf-8 -*- import shutil #shutil.copy(文件1,文件2)#将源内容复制到目标文件中.d.txt不存在则创建 ...
 - python UI自动化之处理多窗口
			
前言 有些页面的链接打开后,会重新打开一个窗口,想要在新页面上操作,就需要先切换窗口了.获取窗口的唯一标识用句柄表示,所以只需要切换句柄,我们就能在多个页面上灵活自如的操作了. 1.元素有属性,浏览器 ...
 - linq和匿名方法、委托、匿名委托、lambda
			
委托相当于JavaScript中的闭包,c++中的函数指针. c#为了引进这个函数指针,将其进行包装成“委托”,同时将非托管的变成托管的. 1.最初的委托该怎么用 弊端:写的代码量过多,还要写一个显示 ...
 - css样式读取
			
在做页面改写时,发现外部引入的样式表中一部分的样式起作用,另一部分的样式没有用.无论怎么修改都没有用.最后搜索了下答案,发现是css样式文件与需引入的文件编码不一致.导致样式读取不到或者读取到一半.