ALSA driver--HW Buffer

转载: https://www.cnblogs.com/fellow1988/p/6204183.html

 

当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调用。

 

  1 static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
  2 unsigned int in_interrupt)
  3 {
  4 struct snd_pcm_runtime *runtime = substream->runtime;
  5 snd_pcm_uframes_t pos;
  6 snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
  7 snd_pcm_sframes_t hdelta, delta;
  8 unsigned long jdelta;
  9 unsigned long curr_jiffies;
 10 struct timespec curr_tstamp;
 11 struct timespec audio_tstamp;
 12 int crossed_boundary = 0;
 13 
 14   old_hw_ptr = runtime->status->hw_ptr;//保存上一次的hw_ptr,在此函数中将更新hw_ptr
 15 
 16 /*
 17 * group pointer, time and jiffies reads to allow for more
 18 * accurate correlations/corrections.
 19 * The values are stored at the end of this routine after
 20 * corrections for hw_ptr position
 21 */
 22   pos = substream->ops->pointer(substream);//获取hw_ptr在当前HW buffer中的偏移
 23   curr_jiffies = jiffies;
 24   if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {//获取当前的time stamp
 25     if ((substream->ops->get_time_info) &&
 26      (runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
 27         substream->ops->get_time_info(substream, &curr_tstamp,&audio_tstamp,&runtime->audio_tstamp_config,&runtime->audio_tstamp_report);
 28 
 29         /* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */
 30         if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)
 31           snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
 32         } else
 33           snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
 34         }
 35 
 36   if (pos == SNDRV_PCM_POS_XRUN) {//发生XRUN
 37     xrun(substream);
 38     return -EPIPE;
 39   }
 40   if (pos >= runtime->buffer_size) {//pos大于buffer size,出现异常
 41     if (printk_ratelimit()) {
 42       char name[16];
 43       snd_pcm_debug_name(substream, name, sizeof(name));
 44       pcm_err(substream->pcm,"invalid position: %s, pos = %ld, buffer size = %ld, period size = %ld\n",name, pos, runtime->buffer_size,
 45 runtime->period_size);
 46     }
 47     pos = 0;
 48   }
 49   pos -= pos % runtime->min_align;
 50   trace_hwptr(substream, pos, in_interrupt);
 51   hw_base = runtime->hw_ptr_base;//当前的hw_base
 52   new_hw_ptr = hw_base + pos;//当前的hw_ptr
 53   if (in_interrupt) {//如果是因为硬件中断调用的此函数
 54   /* we know that one period was processed */
 55   /* delta = "expected next hw_ptr" for in_interrupt != 0 */
 56   delta = runtime->hw_ptr_interrupt + runtime->period_size;
 57   if (delta > new_hw_ptr) {//如果本次通过中断位置加上period_size计算出来的hw_ptr比当前hw_ptr大的话,则说明上一次中断没处理,有可能hw_base需要更新到下一个HW buffer的基地址。
 58     /* check for double acknowledged interrupts */
 59     hdelta = curr_jiffies - runtime->hw_ptr_jiffies;
 60     if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) {//距离上一次的jiffies大于整个buffer 的jiffies的一半。
 61       hw_base += runtime->buffer_size;
 62       if (hw_base >= runtime->boundary) {
 63         hw_base = 0;
 64         crossed_boundary++;
 65       }
 66       new_hw_ptr = hw_base + pos;
 67       goto __delta;
 68     }
 69   }
 70 }
 71   /* new_hw_ptr might be lower than old_hw_ptr in case when */
 72   /* pointer crosses the end of the ring buffer */
 73   if (new_hw_ptr < old_hw_ptr) {//如果当前的hw_ptr比上一次的hw_ptr小,hw_ptr超过了HW buffer边界。hw_base需要更新到下一个HW buffer的基地址。hw_ptr也要同步更新。
 74     hw_base += runtime->buffer_size;
 75     if (hw_base >= runtime->boundary) {//如果hw_base > boundary,那hw_base回跳到Ring Buffer起始位置。
 76       hw_base = 0;
 77       crossed_boundary++;
 78     }
 79     new_hw_ptr = hw_base + pos;
 80   }
 81 __delta:
 82   delta = new_hw_ptr - old_hw_ptr;
 83   if (delta < 0)//如果当前的hw_ptr任然比上一的hw_ptr小,说明hw_ptr走完了Ring buffer一圈。
 84     delta += runtime->boundary;
 85 
 86   if (runtime->no_period_wakeup) {//如果硬件处理完一个peroid数据后不产生中断,就不更新hw_ptr
 87 
 88     snd_pcm_sframes_t xrun_threshold;
 89 /*
 90 * Without regular period interrupts, we have to check
 91 * the elapsed time to detect xruns.
 92 */
 93     jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
 94     if (jdelta < runtime->hw_ptr_buffer_jiffies / 2)
 95       goto no_delta_check;
 96     hdelta = jdelta - delta * HZ / runtime->rate;
 97     xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1;
 98     while (hdelta > xrun_threshold) {
 99       delta += runtime->buffer_size;
100       hw_base += runtime->buffer_size;
101       if (hw_base >= runtime->boundary) {
102         hw_base = 0;
103         crossed_boundary++;
104       }
105       new_hw_ptr = hw_base + pos;
106       hdelta -= runtime->hw_ptr_buffer_jiffies;
107     }
108     goto no_delta_check;
109   }
110 
111 /* something must be really wrong */
112   if (delta >= runtime->buffer_size + runtime->period_size) {//如果当前hw_ptr比较上一次相差buffer size + peroid size,说明有错误。
113     hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr",
114     "(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
115     substream->stream, (long)pos,
116     (long)new_hw_ptr, (long)old_hw_ptr);
117     return 0;
118   }
119 
120   /* Do jiffies check only in xrun_debug mode */
121   if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
122     goto no_jiffies_check;
123 
124 /* Skip the jiffies check for hardwares with BATCH flag.
125 * Such hardware usually just increases the position at each IRQ,
126 * thus it can't give any strange position.
127 */
128   if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
129     goto no_jiffies_check;
130   hdelta = delta;
131   if (hdelta < runtime->delay)
132     goto no_jiffies_check;
133   hdelta -= runtime->delay;
134   jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
135   if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
136     delta = jdelta /(((runtime->period_size * HZ) / runtime->rate)+ HZ/100);
137     /* move new_hw_ptr according jiffies not pos variable */
138     new_hw_ptr = old_hw_ptr;
139     hw_base = delta;
140     /* use loop to avoid checks for delta overflows */
141     /* the delta value is small or zero in most cases */
142     while (delta > 0) {
143       new_hw_ptr += runtime->period_size;
144       if (new_hw_ptr >= runtime->boundary) {
145         new_hw_ptr -= runtime->boundary;
146         crossed_boundary--;
147       }
148       delta--;
149     }
150 /* align hw_base to buffer_size */
151     hw_ptr_error(substream, in_interrupt, "hw_ptr skipping",
152     "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
153     (long)pos, (long)hdelta,
154     (long)runtime->period_size, jdelta,
155     ((hdelta * HZ) / runtime->rate), hw_base,
156     (unsigned long)old_hw_ptr,
157     (unsigned long)new_hw_ptr);
158     /* reset values to proper state */
159     delta = 0;
160     hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
161   }
162 no_jiffies_check:
163   if (delta > runtime->period_size + runtime->period_size / 2) {//interupt丢失,delta(如果当前hw_ptr比较上一次之差)>1.5个peroid size
164     hw_ptr_error(substream, in_interrupt,
165     "Lost interrupts?",
166     "(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
167     substream->stream, (long)delta,
168     (long)new_hw_ptr,
169     (long)old_hw_ptr);
170   }
171 
172 no_delta_check:
173   if (runtime->status->hw_ptr == new_hw_ptr) {//hw_ptr没变化
174     update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
175     return 0;
176   }
177 
178   if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&runtime->silence_size > 0)
179     snd_pcm_playback_silence(substream, new_hw_ptr);//播放silence
180 
181   if (in_interrupt) {//更新hw_ptr_interrupt
182     delta = new_hw_ptr - runtime->hw_ptr_interrupt;
183     if (delta < 0)
184       delta += runtime->boundary;
185     delta -= (snd_pcm_uframes_t)delta % runtime->period_size;
186     runtime->hw_ptr_interrupt += delta;
187     if (runtime->hw_ptr_interrupt >= runtime->boundary)
188       runtime->hw_ptr_interrupt -= runtime->boundary;
189   }
190   runtime->hw_ptr_base = hw_base;//将更新后的值保存到runtime中
191   runtime->status->hw_ptr = new_hw_ptr;
192   runtime->hw_ptr_jiffies = curr_jiffies;
193   if (crossed_boundary) {
194     snd_BUG_ON(crossed_boundary != 1);
195     runtime->hw_ptr_wrap += runtime->boundary;
196   }
197 
198   update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
199 
200   return snd_pcm_update_state(substream, runtime);//通过本次更新的 件指针,检查是否XRUN,或唤醒等待在他上面的队列如poll,lib_write1
201 
202 
203 }

 

完。
posted @ 2020-04-25 11:25  dolinux  阅读(899)  评论(0)    收藏  举报