bastibe/PySoundCard

Random Freeze

Closed this issue · 3 comments

I'm reading my soundcard with Stream() with a callback mode under Gnome with pulseaudio running.

I'm getting lots of
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
no matter what I tweak, and rarely, but commonly enough that it's frustrating, I'm getting this:

Expression 'err' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 3281
Expression 'ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 3808
Expression 'PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 4180
which unsets is_active(), but confusingly calling .start() again in that case gives
Traceback (most recent call last):
File "pyscope.py", line 81, in
s.start()
File "/usr/lib/python3.3/site-packages/pysoundcard.py", line 494, in start
self._handle_error(_pa.Pa_StartStream(self._stream[0]))
File "/usr/lib/python3.3/site-packages/pysoundcard.py", line 470, in _handle_error
raise RuntimeError("%.4f: %s" % (self.time(), errstr))
RuntimeError: 0.0000: Stream is not stopped

If I call .stop(); .start();, then the soundcard keeps giving me samples, until the next time it hangs. Do you think the underruns are related to the hang? It seems like an Exception but there's no way to get notified, is there?

My program is over at https://github.com/kousu/pyscope. I've worked around it with a watchdog thread, but my fix stinks to me.

This is probably an issue with portaudio. The Linux backends of portaudio seem to be a bit flaky sometimes.

However, there might be several things you can try to mitigate your problems. You are doing a lot of work in the callback. This is usually a bad idea and is probably the source of the buffer underruns you are seeing. Normally, buffer underruns should be handled gracefully by portaudio though. A buffer underrun means that your callback took longer than one block size and audio data had to be discarded between two calls to the callback.

In situations where buffer underruns are likely since you are doing a lot of calculations, I would recommend using stream.read() instead of the callback. Since read and write return instantly as long as you are requesting at most one block size, this has no downsides. Plus, you don't have to care about handling the output.

If you only use two channels of your 32, you can tell portaudio/pysoundcard to ignore the upper 30 channels by setting the number of channels in the device dict to 2 before starting the stream.

Drawing things on the screen within the callback is probably a very bad idea, since matplotlib can take quite a long time to draw a simple graph. However, there are ways in which you can speed up matplotlib significantly: http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
With that, I implemented something similar to what you seem to be doing here: https://github.com/bastibe/python-oscillator
This draws the waveform of the currently playing audio in real time using matplotlib (this probably only works on the pyqt/pyside backend of matplotlib).

I hope this helps.

On 22.12.2013, at 06:27, Nick notifications@github.com wrote:

I'm reading my soundcard with Stream() with a callback mode under Gnome with pulseaudio running.

I'm getting lots of
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
no matter what I tweak, and rarely, but commonly enough that it's frustrating, I'm getting this:

Expression 'err' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 3281
Expression 'ContinuePoll( self, StreamDirection_In, &pollTimeout, &pollCapture )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 3808
Expression 'PaAlsaStream_WaitForFrames( stream, &framesAvail, &xrun )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 4180
which unsets is_active(), but confusingly calling .start() again in that case gives
Traceback (most recent call last):
File "pyscope.py", line 81, in
s.start()
File "/usr/lib/python3.3/site-packages/pysoundcard.py", line 494, in start
self.handleerror(pa.PaStartStream(self.stream[0]))
File "/usr/lib/python3.3/site-packages/pysoundcard.py", line 470, in _handleerror
raise RuntimeError("%.4f: %s" % (self.time(), errstr))
RuntimeError: 0.0000: Stream is not stopped

If I call .stop(); .start();, then the soundcard keeps giving me samples, until the next time it hangs. Do you think the underruns are related to the hang? It seems like an Exception but there's no way to get notified, is there?

My program is over at https://github.com/kousu/pyscope. I've worked around it with a watchdog thread, but my fix stinks to me.


Reply to this email directly or view it on GitHub.

This really does! Thank you. We were sitting around chatting about projects we wanted to build, someone said "[..] but we'd need to drop a couple hundred on a 'scope," and I jumped up with the wisdom of the audiojack. I am amazed that I have made a complete and functional tool, mostly thanks to this library, in 8 hours.

In the meantime since I posted this, my underruns went away. I worked backwards and figured out that the difference was that I set output_device=False. I must not have been keeping up with the speaker's rate. And with that going away, the mysterious hang has gone away too. I guess I was overdriving ALSA and it was telling me off.

Thanks for the tip about stream.read() anyway. That would be useful to put into the docs. My intuition was that the callback would give me the optimal rate, because PortAudio/your code would decide what it wanted to give me. If reality is opposite it should be documented.

I'm not sure how to decide which channels I should be listening to. It seems like 0 and 2 are equal, and 1 and 3 are equal, and the remainder are a mystery of close-to-but-not-quite zeros. I didn't even know my machine had 32 channels. Does PA always give 32 channels?

python-oscillator is what I am doing, but I plan to target a wide range of useful features that real oscilloscopes have, like spectrum and energy content plots.

Yeah, the callback is kind of confusing. In portaudio, the callback is a bit faster than read/write. In Python though, there is probably next to no performance difference between the callback and read/write as most of the runtime is spent in converting data in Python anyway.

In future versions of Python, I might be able to use the buffer protocol which would speed up data conversion. Also, PySoundCard and PySoundFile are almost compatible with PyPy. There is only one function missing in NumPyPy.

The rate at which audio data is processed is defined by the block size. The callback always processes one block at a time. Similarly, read and write work best if you always provide one block at a time.

I'm not sure where those 32 channels come from. They don't show up on OSX or Windows. They might be related to Jack. Do you have Jack installed by any chance?

By the way, have a look at http://friture.org, which is just friggin awesome and written in PyQt, too (though friture uses qwt instead of Matplotlib).

On 22.12.2013, at 10:18, Nick notifications@github.com wrote:

This really does! Thank you. We were sitting around chatting about projects we wanted to build, someone said "[..] but we'd need to drop a couple hundred on a 'scope," and I jumped up with the wisdom of the audiojack. I am amazed that I have made a complete and functional tool, mostly thanks to this library, in 8 hours.

In the meantime since I posted this, my underruns went away. I worked backwards and figured out that the difference was that I set output_device=False. I must not have been keeping up with the speaker's rate. And with that going away, the mysterious hang has gone away too. I guess I was overdriving ALSA and it was telling me off.

Thanks for the tip about stream.read() anyway. That would be useful to put into the docs. My intuition was that the callback would give me the optimal rate, because PortAudio/your code would decide what it wanted to give me. If reality is opposite it should be documented.

I'm not sure how to decide which channels I should be listening to. It seems like 0 and 2 are equal, and 1 and 3 are equal, and the remainder are a mystery of close-to-but-not-quite zeros. I didn't even know my machine had 32 channels. Does PA always give 32 channels?

python-oscillator is what I am doing, but I plan to target a wide range of useful features that real oscilloscopes have, like spectrum and energy content plots.


Reply to this email directly or view it on GitHub.