python-sounddevice icon indicating copy to clipboard operation
python-sounddevice copied to clipboard

Sound glitch at end of stream

Open mattgwwalker opened this issue 5 years ago • 5 comments

I found that, occasionally (4/10 times), a crack could be heard when the stream was closed. I was able to resolve this (0/10 tries) by writing a frame of zeros before calling sd.CallbackStop.

I was writing sine waves with a fade out and recording the resulting sound. Here is an example of the cracking sound. Data sent to outdata is shown (top track) against the recorded sound (bottom track). They have only been aligned by eye.

image

It seems that a solution was to write a frame of all zeros: outdata[:] = [[0,0]] * samples

And then in the next call of the callback: raise sd.CallbackStop

Operating system: macOS 10.13.6 PortAudio version: PortAudio V19.6.0-devel (installed via brew)

I hope that's helpful to someone else.

Cheers,

Matthew

mattgwwalker avatar Jun 14 '20 05:06 mattgwwalker

Thanks for the report!

In each and every invocation of the callback function, the output buffer has to be actively filled with meaningful data (whether or not you raise an exception at some later point).

The docs (https://python-sounddevice.readthedocs.io/en/0.3.15/api/streams.html#sounddevice.Stream) say:

The output buffer contains uninitialized data and the callback is supposed to fill it with proper audio data. If no data is available, the buffer should be filled with zeros (e.g. by using outdata.fill(0)).

Please note that if the available audio data is not long enough to fill the whole buffer, you still have to fill the rest with zeros.

I'm not sure if I understand, but are you saying that in the callback invocation where you raise sd.CallbackStop, you are already filling the whole output buffer with meaningful data (before raising the exception!), and you still experience a glitch?

You shouldn't have to wait for another callback invocation for raising an exception. And if you do so, in this additional callback invocation (where you raise an exception) you'll also have to fill the output buffer with zeros!

Can you please provide the code that produces the glitch?

Here's an example where an incomplete buffer is filled up with zeros:

https://github.com/spatialaudio/python-sounddevice/blob/822a00c74d085b439b8b5331dd71f711eb89d8ae/examples/play_long_file.py#L78-L81

In the lines before that, there is strictly speaking a bug, because the output buffer is not filled:

https://github.com/spatialaudio/python-sounddevice/blob/822a00c74d085b439b8b5331dd71f711eb89d8ae/examples/play_long_file.py#L75-L77

But since this is an error case (where there might be a glitch anyway), I guess I didn't care to fill the output buffer with zeros in this very case.

mgeier avatar Jun 14 '20 09:06 mgeier

Thank you for such a quick and detailed reply.

Unfortunately, I've since been working on that file for several hours and I've made many changes since I reported that issue.

I have tried to reproduce the issue again by reverting the changes, but I can no longer produce the clicks that prompted my original post. I can not even produce the clicks if I fail to fill the array with zeros.

I am fairly sure, but not certain, that in my original post I was filling the outdata array with zeros before raising the exception. However, given I cannot be sure that I recall correctly, and also given that I cannot reproduce the issue, I think it's fair to say that I may not have filled the array with zeros before raising the exception.

Thanks again for your very thoughtful reply.

It's greatly appreciated at this end,

Matthew

mattgwwalker avatar Jun 14 '20 11:06 mattgwwalker

I can not even produce the clicks if I fail to fill the array with zeros.

Yeah, that's the problem here. Depending on the host API (and probably many other factors), this might seem to work for a long time, but at some point, seemingly out of the blue, artifacts appear.

There doesn't seem to be a reliable way to test for this, you just have to pay close attention to always fill the output buffers.

If at some point you encounter those artifacts again (ideally in a reproducible way), please let me know!

mgeier avatar Jun 17 '20 14:06 mgeier

I have been looking out for this issue for the last two weeks and actively filling the buffer with zeros seems to be the solution.

I think this issue can be closed.

Cheers,

Matthew

mattgwwalker avatar Jun 27 '20 10:06 mattgwwalker

Thanks for the update! That's good news!

I'd like to keep this issue open for now as a reminder to make this problem more visible in the docs.

I think this should be at least mentioned in the documentation of the CallbackStop and CallbackAbort exceptions.

Any other ideas for improving the docs?

As always, I'm open for PRs ...

mgeier avatar Jun 30 '20 19:06 mgeier