bastibe/PySoundCard

Issues with short block sizes?

Opened this issue · 11 comments

MrMho commented

Although I use a recent computer (Windows 10, Core i5-6500, 8 GB RAM) as well as a good audio interface (Edirol UA-101), I am encountering buffer underflows when using block lengths < 50 ms.
I would expect my setup to process much shorter block lengths without any problems.

Example:
The following code gives me lots of "Underflow!" messages, whereas a blocksize of 50 ms works fine.

from pysoundcard import Stream, continue_flag
import time

fs = 44100
blocksize = int(0.04 * fs)

def callback(in_data, out_data, time_info, status):
    if status != 0:
        print("Underflow!")
    return continue_flag

oStream = Stream(samplerate=fs, blocksize=blocksize, callback=callback)
oStream.start()
time.sleep(3)
oStream.stop()

Do you have any idea why that is?

Thanks in advance!

Which Device-API are you using? You should be using WASAPI (or ASIO, but that is more effort). Also, the callback API might be somewhat slower than direct reading/writing.

I can typically use block lengths as short as 4 samples with little trouble.

MrMho commented

I have been using MME. Now I am trying to switch to WASAPI. However, even though the devices I want to specify only differ from the old ones in terms of the used API, Python is raising an error:
"RuntimeError: 0.0000: Invalid number of channels"

By the way, am I right in assuming that I have to specify the API by specifying the device ID? Is this the correct way to do it in the first place?

Yes, you are correct in your assumption. This is an unfortunate implementation detail of Portaudio.

Sometimes, audio devices register in different Windows systems with different channel configurations. Are you using the same number of channels as the device specifies?

MrMho commented

Yes.
This is what the device_info function returns:

In [3]: device_info(14)
Out[3]: 
{'default_high_input_latency': 0.01,
 'default_high_output_latency': 0.0,
 'default_low_input_latency': 0.003,
 'default_low_output_latency': 0.0,
 'default_samplerate': 192000.0,
 'hostapi': 1,
 'max_input_channels': 2,
 'max_output_channels': 0,
 'name': u'1-2 (UA-101)'}

# hostapi=1 corresponds to WASAPI

When I am trying to create a stream object like this, it raises the error:

oStream = Stream(samplerate=fs,
                 blocksize=blocksize,
                 callback=callback,
                 device=14,
                 channels=(2,0))

Can you try opening an InputStream instead? Maybe that would work.

MrMho commented

Using an InputStream does not work either, it returns an error saying
"RuntimeError: 0.0000: Invalid device"

I'm still using the same device as before.

These are portaudio errors. I don't know what the problem is. At this point, pysoundcard is only passing these values to portaudio.

MrMho commented

Ok, it turned out that the sample rate I used for the InputStream did not match the sample rate in the Windows device manager. I fixed that and now it seems to work fine.
However, I noticed that even at ridiculously short block lengths the status variable remains zero, which is why I cannot detect buffer underflows.

Example:

from pysoundcard import InputStream, continue_flag
import time

fs = 44100
blocksize = 1

def callback(in_data, time_info, status):    
    time.sleep(0.01) # some processing
    if status != 0:
        print("Underflow!")   
    return continue_flag

oStream = InputStream(samplerate=fs,
                 blocksize=blocksize,
                 callback=callback,
                 device=37,
                 channels=2)

oStream.start()
time.sleep(3)
oStream.stop()

It might be that an InputStream is never actually underflowing, since portaudio is running in its own thread, which might not block for callbacks. Did you check whether your callback is actually receiving incomplete data? Portaudio might be handing you longer blocks if your callbacks take too long.

MrMho commented

I checked that by writing the incoming data into numpy arrays. Portaudio does not change the block size. Instead, it appears to me that some of the incoming blocks are skipped. It would be nice to have some kind of indicator when that happens.
But then again, the improvement in performance due to the change of the API is huge, so I think pysoundcard is now fast enough for my needs.
Thanks for your help!

Sadly, this is just another internal quirk of portaudio, and there is nothing we can do about it on the pysoundcard side. There is a chance that this is a bug in portaudio, though. You could try reporting it on the portaudio mailing list, and maybe someone over there can help you.