hw_ptr的更新和xrun的判断

1 snd_pcm_update_hw_ptr0

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 timespec64 curr_tstamp;
    struct timespec64 audio_tstamp;
    int crossed_boundary = 0;

    old_hw_ptr = runtime->status->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);//从platfrom driver中获取hw搬走的data size
    curr_jiffies = jiffies;
    if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
        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, &curr_tstamp);
        } else
            snd_pcm_gettime(runtime, &curr_tstamp);
    }

    if (pos == SNDRV_PCM_POS_XRUN) {//如果xrun 无需更新
        __snd_pcm_xrun(substream);
        return -EPIPE;
    }
    if (pos >= runtime->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;
    new_hw_ptr = hw_base + pos;//更新新的hw_ptr
    if (in_interrupt) {//如果是中断触发的更新hw_ptr,就走这里
        /* 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) {
            /* check for double acknowledged interrupts */
            hdelta = curr_jiffies - runtime->hw_ptr_jiffies;
            if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) {
                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) {//如果new hw_ptr小于old hw_ptr表示绕回了要加上buf size往前移
        hw_base += runtime->buffer_size;
        if (hw_base >= runtime->boundary) {//超过boundary
            hw_base = 0;
            crossed_boundary++;//记录超过boundary的次数
        }
        new_hw_ptr = hw_base + pos;
    }
      __delta:
    delta = new_hw_ptr - old_hw_ptr;
    if (delta < 0)
        delta += runtime->boundary;

    if (runtime->no_period_wakeup) {
        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_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) {
        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) {
        runtime->hw_ptr_jiffies = curr_jiffies;
        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 size,就走清0的flow,防止读旧数据。注意是对于playback的,capture不需要

    if (in_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->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);//更新状态

2 snd_pcm_update_state

int snd_pcm_update_state(struct snd_pcm_substream *substream,
             struct snd_pcm_runtime *runtime)
{
    snd_pcm_uframes_t avail;

    avail = snd_pcm_avail(substream);
    if (avail > runtime->avail_max)
        runtime->avail_max = avail;
    if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
        if (avail >= runtime->buffer_size) {//表述dma中没数据了
            snd_pcm_drain_done(substream);//drain done的处理flow
            return -EPIPE;
        }
    } else {
        if (avail >= runtime->stop_threshold) {
            __snd_pcm_xrun(substream);//如果playback的dma数据不足,或者capture的dma data太多,会触发underrun或者overrun
            return -EPIPE;
        }
    }
    if (runtime->twake) {
        if (avail >= runtime->twake)
            wake_up(&runtime->tsleep);
    } else if (avail >= runtime->control->avail_min)
        wake_up(&runtime->sleep);
    return 0;
}

 

posted @ 2025-05-27 23:11  Action_er  阅读(29)  评论(0)    收藏  举报