bastibe / SoundCard

A Pure-Python Real-Time Audio Library
https://soundcard.readthedocs.io
BSD 3-Clause "New" or "Revised" License
680 stars 69 forks source link

latency? issue #73

Closed andreicociuba closed 4 years ago

andreicociuba commented 4 years ago

running ubuntu on my laptop here

i wrote a python script to check mic audio levels and adjust some playback volumes.

basically a compressor with sidechaining from the microphone. very handy to reduce the volume of the ambient music when i am talking to someone, or into the phone, not so handy when you have a mechanical keyboard and you start typing. :)

i can provide full code, if you like but the gist of it is:


one_mic = sc.get_microphone('Built-in')
with onemic.recorder(srate) as mic
   while True:
#read some variables from a config file, and then
#flush recorder cache of any stray samples
      baddata=mic.record()
      data=mic.record(srate*duration)
      rms=numpy.max(numpy.sqrt(numpy.mean(data**2)))
#then perform some calculations and set volume accordingly with
#     os.system("pactl with appropriate parameters")
#     construct some curses-style visualisations 
#     of certain parameters out of loops, and terminal characters, based on terminal width
       print(visualisations)

anyway, when under development, i kept pavucontrrol open to monitor what it actually does to the actual output volume of the system. i tweaked some code, some of the variable in the config files, etc... and i decided i was satisfied.

then one day i noticed the output of the cli-visualisations i printed got very jagged, with erratic jumps here and there, and the volume behaved a lot like my feedback loop had variable execution time (samplerate, in the discrete-control-system sense, not for the soundcard). i think the microphone data acquisition might have variable latency and be generally lazier.

i noticed that the problem goes away when i keep pavucontrol open.

any ideas on what might be going on and how i can adress this?

andreicociuba commented 4 years ago

meanwhile, i will just keep pavucontrol open....

bastibe commented 4 years ago

If I remember correctly, pulseaudio will dynamically adjust its internal block size if you don't read its data in time. So, if your visualization takes too long, pulse might think you can't keep up, and dynamically make things go slower. Real time waits for no-one.

In general, it's a good idea to keep recording as fast as possible, on its own thread if need be. Write the data to a queue, and do your visualization in a separate thread on your own time. Doing heavy work on the audio thread is a recipe for disaster and it's a credit to pulseaudio's design that it doesn't simply crash or generate buffer overflows all the time.

(And please don't use any of the async stuff. Audio is one case where you need preemption, and async doesn't do that.)

andreicociuba commented 4 years ago
rows, columns = os.popen('stty size', 'r').read().split()
        columns=int(columns)
        headercolumns=int(columns*(0-minlnmb)/(maxlnmb - minlnmb))
#       print(minlnmb,end='')
        output=output+str(minlnmb)
        for c in range(3, headercolumns+1):
#           print("-", end='')
            output=output+"-"
#       print("0",end='')
        output+="0"
        headercolumns=columns-headercolumns
        for c in range(2,headercolumns-2):
#           print("-",end='')
            output+="-"
#       print(maxlnmb,end='')
        output+=str(maxlnmb)

#       ccolumns=(columns*(lnmb-minlnmb)/(maxlnmb - minlnmb))
#       for c in range(1,int(ccolumns)):
#           print("_",end='')
#       print("*")
        output+="\n"
        ccolumns=int(columns*(lavg-minlnmb)/(maxlnmb - minlnmb))
        for c in range(1,ccolumns+1):
#           print(" ",end='')
            output+=" "
#       print("|soundlevel "+str(avg))
        output+="|soundlevel "+str(lnmb)[0:7]+"\n"

        ccolumns=int(columns*(lmin-minlnmb)/(maxlnmb - minlnmb))
        for c in range(1,ccolumns+1):
            output+=" "
        output+="|minimum "+str(lmin)[0:4]+" db\n"

        ccolumns=int((columns-10)*int(curvol)/100)
        for c in range(1,ccolumns):
            output+=" "
        output+="|Volume:"+str(curvol)

        print(output)

its no fancy stuff, just 4 lines of text, all buffered into a variable and only printed on the terminal at the end.

do you reckon it's worth moving it to another thread?

bastibe commented 4 years ago

It's worth disabling it for now and check if it indeed the problem. I'd rather think the problem is in controlling pactl, though. At least, you could try running that in a non-blocking way, say with subprocess.run.

You'll have to figure out the exact timings on your machine anyway. It tends to vary between installations, sound cards, and versions.

andreicociuba commented 4 years ago

i think i fixed it accidentally by trying to fix another issue completely.

i followed https://askubuntu.com/questions/707171/how-can-i-fix-choppy-audio

and did sudo nano /etc/pulse/default.pa

replace load-module module-udev-detect with load-module module-udev-detect tsched=0 pulseaudio -k

bastibe commented 4 years ago

Oh, fascinating! Do you have a link to the documentation of this feature? If it's generally applicable, I'd be grateful for a pull request with a new comment in the Latency section of the README.

andreicociuba commented 4 years ago

to be honest, i am just a google warrior script kiddie.

i had some stuttering issues with anbox, and simply googled "ubuntu fix stuttering audio", stumbled upon the link above, tried it out and noticed this effect.

i shall google some more to see if i find any documentation.

as for pull requests..... i have a very sparse understanding of git. so, you want me to make a pull request to you, add my comment in the how to section, and you approve it? i guess i have to google how to do that too...

andreicociuba commented 4 years ago

according to this archlinux related page

By default, PulseAudio uses timer-based scheduling. In this mode, fragments are not used at all, and so the default-fragments and default-fragment-size-msec parameters are ignored. To turn timer-based scheduling off add tsched=0 in /etc/pulse/default.pa:

when opening pavucontrol, or when using various other plugins for pulseaudio, such as pulseeffects, i suspect either this timer polling gets disabled by these applications, or gets force executed at far smaller intervals, for needing to display various vumeters and nice animations related to the sound on the screen.

andreicociuba commented 4 years ago

this thing here i suspect makes a lot more sense to you, i have to set it aside for later reading and understanding, i have other headaches to sort out at the moment.

bastibe commented 4 years ago

Interesting! Thank you for these links and explanations!

If you'd be willing to create a pull request, simply click the little edit-button at the top right of the README file right here on the Github website. Then you can add a little section about latencies on Linux, give a brief summary of the edits in the commit box at the bottom, and Github will take care of all the git-witchery for you.

But if you'd rather not do that, that's cool too. I like to offer people simple ways of contributing to my open source projects, just for the fun of it. That's all.

andreicociuba commented 4 years ago

:)) i do appreciate, and i do understand why github works the way it works. i also understand how immensely useful it is for programmers who code for a living.

while i can code something up in a pinch (or at least fail gracefully while searching stackoverflow), github still seems anything but simple to me.

i made an attempt, hope it works out. was it supposed to be ettiquette to create a branch for my modifications, or was it ok that i did it in the master branch?

bastibe commented 4 years ago

You did it exactly right! Github created a fork of SoundCard on your account, and a new branch with your changes, then opened a pull request with that branch on here. That's how it is supposed to go.

Thank you very much!