diff options
author | Takashi Iwai <tiwai@suse.de> | 2009-03-03 11:00:15 -0500 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2009-03-09 07:56:49 -0400 |
commit | ed3da3d9a0ef13c6fe1414ec73c9c1be12747b62 (patch) | |
tree | e79103738c1d30f8528791af2a1fb44cef2c2371 /sound/core | |
parent | 2450cf51a1bdba7037e91b1bcc494b01c58aaf66 (diff) |
ALSA: Rewrite hw_ptr updaters
Clean up and improve snd_pcm_update_hw_ptr*() functions.
snd_pcm_update_hw_ptr() tries to detect the unexpected hwptr jumps
more strictly to avoid the position mess-up, which often results in
the bad quality I/O with pulseaudio.
The hw-ptr skip error messages are printed when xrun proc is set to
non-zero.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/pcm_lib.c | 128 |
1 files changed, 83 insertions, 45 deletions
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 921691080f35..86ac9ae9460e 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c | |||
@@ -125,19 +125,27 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram | |||
125 | } | 125 | } |
126 | } | 126 | } |
127 | 127 | ||
128 | #ifdef CONFIG_SND_PCM_XRUN_DEBUG | ||
129 | #define xrun_debug(substream) ((substream)->pstr->xrun_debug) | ||
130 | #else | ||
131 | #define xrun_debug(substream) 0 | ||
132 | #endif | ||
133 | |||
134 | #define dump_stack_on_xrun(substream) do { \ | ||
135 | if (xrun_debug(substream) > 1) \ | ||
136 | dump_stack(); \ | ||
137 | } while (0) | ||
138 | |||
128 | static void xrun(struct snd_pcm_substream *substream) | 139 | static void xrun(struct snd_pcm_substream *substream) |
129 | { | 140 | { |
130 | snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); | 141 | snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); |
131 | #ifdef CONFIG_SND_PCM_XRUN_DEBUG | 142 | if (xrun_debug(substream)) { |
132 | if (substream->pstr->xrun_debug) { | ||
133 | snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n", | 143 | snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n", |
134 | substream->pcm->card->number, | 144 | substream->pcm->card->number, |
135 | substream->pcm->device, | 145 | substream->pcm->device, |
136 | substream->stream ? 'c' : 'p'); | 146 | substream->stream ? 'c' : 'p'); |
137 | if (substream->pstr->xrun_debug > 1) | 147 | dump_stack_on_xrun(substream); |
138 | dump_stack(); | ||
139 | } | 148 | } |
140 | #endif | ||
141 | } | 149 | } |
142 | 150 | ||
143 | static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream, | 151 | static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream, |
@@ -182,11 +190,21 @@ static inline int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream | |||
182 | return 0; | 190 | return 0; |
183 | } | 191 | } |
184 | 192 | ||
193 | #define hw_ptr_error(substream, fmt, args...) \ | ||
194 | do { \ | ||
195 | if (xrun_debug(substream)) { \ | ||
196 | if (printk_ratelimit()) { \ | ||
197 | snd_printd("hda_codec: " fmt, ##args); \ | ||
198 | } \ | ||
199 | dump_stack_on_xrun(substream); \ | ||
200 | } \ | ||
201 | } while (0) | ||
202 | |||
185 | static inline int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) | 203 | static inline int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) |
186 | { | 204 | { |
187 | struct snd_pcm_runtime *runtime = substream->runtime; | 205 | struct snd_pcm_runtime *runtime = substream->runtime; |
188 | snd_pcm_uframes_t pos; | 206 | snd_pcm_uframes_t pos; |
189 | snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt; | 207 | snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt, hw_base; |
190 | snd_pcm_sframes_t delta; | 208 | snd_pcm_sframes_t delta; |
191 | 209 | ||
192 | pos = snd_pcm_update_hw_ptr_pos(substream, runtime); | 210 | pos = snd_pcm_update_hw_ptr_pos(substream, runtime); |
@@ -194,36 +212,47 @@ static inline int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *subs | |||
194 | xrun(substream); | 212 | xrun(substream); |
195 | return -EPIPE; | 213 | return -EPIPE; |
196 | } | 214 | } |
197 | if (runtime->period_size == runtime->buffer_size) | 215 | hw_base = runtime->hw_ptr_base; |
198 | goto __next_buf; | 216 | new_hw_ptr = hw_base + pos; |
199 | new_hw_ptr = runtime->hw_ptr_base + pos; | ||
200 | hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size; | 217 | hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size; |
201 | 218 | delta = new_hw_ptr - hw_ptr_interrupt; | |
202 | delta = hw_ptr_interrupt - new_hw_ptr; | 219 | if (hw_ptr_interrupt == runtime->boundary) |
203 | if (delta > 0) { | 220 | hw_ptr_interrupt = 0; |
204 | if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) { | 221 | if (delta < 0) { |
205 | #ifdef CONFIG_SND_PCM_XRUN_DEBUG | 222 | delta += runtime->buffer_size; |
206 | if (runtime->periods > 1 && substream->pstr->xrun_debug) { | 223 | if (delta < 0) { |
207 | snd_printd(KERN_ERR "Unexpected hw_pointer value [1] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2); | 224 | hw_ptr_error(substream, |
208 | if (substream->pstr->xrun_debug > 1) | 225 | "Unexpected hw_pointer value " |
209 | dump_stack(); | 226 | "(stream=%i, pos=%ld, intr_ptr=%ld)\n", |
210 | } | 227 | substream->stream, (long)pos, |
211 | #endif | 228 | (long)hw_ptr_interrupt); |
212 | return 0; | 229 | /* rebase to interrupt position */ |
230 | hw_base = new_hw_ptr = hw_ptr_interrupt; | ||
231 | delta = 0; | ||
232 | } else { | ||
233 | hw_base += runtime->buffer_size; | ||
234 | if (hw_base == runtime->boundary) | ||
235 | hw_base = 0; | ||
236 | new_hw_ptr = hw_base + pos; | ||
213 | } | 237 | } |
214 | __next_buf: | ||
215 | runtime->hw_ptr_base += runtime->buffer_size; | ||
216 | if (runtime->hw_ptr_base == runtime->boundary) | ||
217 | runtime->hw_ptr_base = 0; | ||
218 | new_hw_ptr = runtime->hw_ptr_base + pos; | ||
219 | } | 238 | } |
220 | 239 | if (delta > runtime->period_size) { | |
240 | hw_ptr_error(substream, | ||
241 | "Lost interrupts? " | ||
242 | "(stream=%i, delta=%ld, intr_ptr=%ld)\n", | ||
243 | substream->stream, (long)delta, | ||
244 | (long)hw_ptr_interrupt); | ||
245 | /* rebase hw_ptr_interrupt */ | ||
246 | hw_ptr_interrupt = | ||
247 | new_hw_ptr - new_hw_ptr % runtime->period_size; | ||
248 | } | ||
221 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && | 249 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && |
222 | runtime->silence_size > 0) | 250 | runtime->silence_size > 0) |
223 | snd_pcm_playback_silence(substream, new_hw_ptr); | 251 | snd_pcm_playback_silence(substream, new_hw_ptr); |
224 | 252 | ||
253 | runtime->hw_ptr_base = hw_base; | ||
225 | runtime->status->hw_ptr = new_hw_ptr; | 254 | runtime->status->hw_ptr = new_hw_ptr; |
226 | runtime->hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size; | 255 | runtime->hw_ptr_interrupt = hw_ptr_interrupt; |
227 | 256 | ||
228 | return snd_pcm_update_hw_ptr_post(substream, runtime); | 257 | return snd_pcm_update_hw_ptr_post(substream, runtime); |
229 | } | 258 | } |
@@ -233,7 +262,7 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) | |||
233 | { | 262 | { |
234 | struct snd_pcm_runtime *runtime = substream->runtime; | 263 | struct snd_pcm_runtime *runtime = substream->runtime; |
235 | snd_pcm_uframes_t pos; | 264 | snd_pcm_uframes_t pos; |
236 | snd_pcm_uframes_t old_hw_ptr, new_hw_ptr; | 265 | snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base; |
237 | snd_pcm_sframes_t delta; | 266 | snd_pcm_sframes_t delta; |
238 | 267 | ||
239 | old_hw_ptr = runtime->status->hw_ptr; | 268 | old_hw_ptr = runtime->status->hw_ptr; |
@@ -242,29 +271,38 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) | |||
242 | xrun(substream); | 271 | xrun(substream); |
243 | return -EPIPE; | 272 | return -EPIPE; |
244 | } | 273 | } |
245 | new_hw_ptr = runtime->hw_ptr_base + pos; | 274 | hw_base = runtime->hw_ptr_base; |
246 | 275 | new_hw_ptr = hw_base + pos; | |
247 | delta = old_hw_ptr - new_hw_ptr; | 276 | |
248 | if (delta > 0) { | 277 | delta = new_hw_ptr - old_hw_ptr; |
249 | if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) { | 278 | if (delta < 0) { |
250 | #ifdef CONFIG_SND_PCM_XRUN_DEBUG | 279 | delta += runtime->buffer_size; |
251 | if (runtime->periods > 2 && substream->pstr->xrun_debug) { | 280 | if (delta < 0) { |
252 | snd_printd(KERN_ERR "Unexpected hw_pointer value [2] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2); | 281 | hw_ptr_error(substream, |
253 | if (substream->pstr->xrun_debug > 1) | 282 | "Unexpected hw_pointer value [2] " |
254 | dump_stack(); | 283 | "(stream=%i, pos=%ld, old_ptr=%ld)\n", |
255 | } | 284 | substream->stream, (long)pos, |
256 | #endif | 285 | (long)old_hw_ptr); |
257 | return 0; | 286 | return 0; |
258 | } | 287 | } |
259 | runtime->hw_ptr_base += runtime->buffer_size; | 288 | hw_base += runtime->buffer_size; |
260 | if (runtime->hw_ptr_base == runtime->boundary) | 289 | if (hw_base == runtime->boundary) |
261 | runtime->hw_ptr_base = 0; | 290 | hw_base = 0; |
262 | new_hw_ptr = runtime->hw_ptr_base + pos; | 291 | new_hw_ptr = hw_base + pos; |
292 | } | ||
293 | if (delta > runtime->period_size && runtime->periods > 1) { | ||
294 | hw_ptr_error(substream, | ||
295 | "hw_ptr skipping! " | ||
296 | "(pos=%ld, delta=%ld, period=%ld)\n", | ||
297 | (long)pos, (long)delta, | ||
298 | (long)runtime->period_size); | ||
299 | return 0; | ||
263 | } | 300 | } |
264 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && | 301 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && |
265 | runtime->silence_size > 0) | 302 | runtime->silence_size > 0) |
266 | snd_pcm_playback_silence(substream, new_hw_ptr); | 303 | snd_pcm_playback_silence(substream, new_hw_ptr); |
267 | 304 | ||
305 | runtime->hw_ptr_base = hw_base; | ||
268 | runtime->status->hw_ptr = new_hw_ptr; | 306 | runtime->status->hw_ptr = new_hw_ptr; |
269 | 307 | ||
270 | return snd_pcm_update_hw_ptr_post(substream, runtime); | 308 | return snd_pcm_update_hw_ptr_post(substream, runtime); |