当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的更多相关文章

  1. ALSA driver基本概念

    https://blog.csdn.net/zyuanyun/article/details/59180272#t6 1.Card For each soundcard, a “card” recor ...

  2. ALSA driver --PCM 实例创建过程

    前面已经写过PCM 实例的创建框架,我们现在来看看PCM 实例是如何创建的. 在调用snd_pcm_new时就会创建一个snd_pcm类型的PCM 实例. struct snd_pcm { struc ...

  3. ALSA 学习小记

    对于playback snd_pcm_begin snd_pcm_commit, 貌似 commit给的frame才会使得alsa去把数据填充 转自 http://magodo.github.io/ ...

  4. Introduction to Sound Programming with ALSA

    ALSA stands for the Advanced Linux Sound Architecture. It consists of a set of kernel drivers, an ap ...

  5. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  6. ALSA driver--PCM实例创建框架

    在介绍PCM 之前,我们先给出创建PCM实例的框架. #include <sound/pcm.h> .... /* hardware definition */ static struct ...

  7. ALSA lib基本概念

    1.channel 通道,即我们熟知的声道数.左/右声道,5.1channel等等 2.sample A sample is a single value that describes the amp ...

  8. Linux下实现视频读取(三)---Buffer的准备和数据读取

    前面主要介绍的是:V4L2 的一些设置接口,如亮度,饱和度.曝光时间,帧数,增益.白平衡等.今天看看V4L2 得到数据的几个关键ioctl,Buffer的申请和数据的抓取. 1. 初始化 Memory ...

  9. ALSA学习资料

    一.内核文档  Linux Sound Subsystem Documentation 二.一些API 1.snd_pcm_period_elapsed 2.snd_pcm_lib_buffer_by ...

随机推荐

  1. USACO06DEC 牛奶模式

    题意:求最长的可重叠的 K重复子串 的长度 考虑二分长度s,转化为验证性问题. 对SA进行分组.保证组内Height最小为s.这样在组内RMQ就可以任意了,因为RMQ一定是大于S的. 只要组内元素个数 ...

  2. 324. 摆动排序 II(三路划分算法)

    题目: 给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序. 示例 1: 输入: nums = [ ...

  3. 文本中自动出现的 &#8203

    文本中自动出现的 &#8203   所借鉴原页面地址:https://blog.csdn.net/judyc/article/details/53097142 因判断容器内字符长度来做其它处理 ...

  4. mybatis(五):源码分析 - config文件加载流程

    详细的可以参考https://blog.csdn.net/weixin_33850890/article/details/88112849

  5. LED Decorative Light Supplier - LED Environmental Decorative Lighting Application

    Creating ambient lighting in the home can bridge the gap between the internal world and the outside ...

  6. MYSQL入门总结

    创建数据库及创建表 create schema/database ttest(名字); //创建数据库 create table ttest(建好的数据库名字).new_table(表名字) ( a ...

  7. thinkphp 二级域名绑定模块,导致设置的路由被多域名共用的问题解决方案

    问题背景: 在router.php路由配置文件设置了二级域名和路由设置 use think\Route; Route::domain('www','index'); Route::domain('ad ...

  8. Makefile文件(DE1-soc软件实验”hello_word")

    DE1-soc软件实验”hello_word"中,hello_word此程序很好理解,那Makefile文件又如何理解呢? 所要完成的Makefile 文件描述了整个工程的编译.连接等规则. ...

  9. JDK的卸载和安装

    Java入门 Java最大优势:基于JVM,跨平台 Java的几个版本 JavaSE:标准版,占领桌面,桌面程序,控制台开发等. JavaME:嵌入式开发,占领手机,手机,小家电等.(几乎死掉了) J ...

  10. SQL中 select count(1) count中的1 到底是什么意思呢?和count(*)的区别

    count(1),其实就是计算一共有多少符合条件的行. 1并不是表示第一个字段,而是表示一个固定值.其实就可以想成表中有这么一个字段,这个字段就是固定值1,count(1),就是计算一共有多少个1.同 ...