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 }
本文来自博客园,作者:dolinux,未经同意,禁止转载

浙公网安备 33010602011771号