#1
  1. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Aug 2012
    Posts
    1
    Rep Power
    0

    Question Pyaudio - buffer overflow errors


    I am converting a program in python that is written and used on another OS that starts with the letter w.

    The script as written works on that OS, but on Linux it seems to fall flat on its face.

    First, I do not have access to any non Linux systems. I am strictly 100% Linux and Linux only. (Hence the need to correct this script.)

    Second, I need to determine if a few things are even possible or I should just stop now and not waste my time trying to convert it.

    a. - Can python via pyaudio listen to the mic/line in monitor for a trigger event, record audio after the trigger event, while still monitoring for additional trigger events that would trigger another recording, and playback whats being recorded while monitoring? All with a SINGLE audio card?

    If the answer to the above is NO? Its interesting that this can be done on a non Linux system.

    Third, right now I have 2 issues:

    a) buffer overflow on reading

    b) audio recorded sounds like a chipmunk with the audio recorded at about 20 times normal rate.

    Lastly, I think most of these issues can be overcome on Linux, I don't know why not.

    So some basic info. I program with all kinds of thinks from assembler to HTML, to PHP to SQL. Python is relatively new.

    Development testing system is an old 32bit system using a Kubuntu Precise distro (its more that its customized to include tons of stuff that officially is not included like codex, java etc...)

    Python and Tk is all the versions from the Kubuntu repos. Nothing compiled from sources, just apt-get install or synaptic.

    I have added: Tk, ffmpeg, numpy, pyaudio, pyserial etc. as this required by this script, along with any dependencies they had to install.

    NOTE: Pulseaudio is REMOVED on my distro and all other systems, I do not use it. Strictly just ALSA, and I don't want to make its use required. Nor require others to remove it. pyaudio should deal with that be it ALSA or whatever.

    Bug #1: - IOError: [Errno Input overflowed] -9981

    Code:
     Exception in Tkinter callback
    Traceback (most recent call last):
      File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1413, in __call__
        return self.func(*args)
      File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 498, in callit
        func(*args)
      File "T_mybeta.py", line 896, in start
        nextstate()
      File "T_mybeta.py", line 547, in Atone1
        data = record(.2,0,1)
      File "T_mybeta.py", line 341, in record
        data = stream.read(chunk)
      File "/usr/lib/pymodules/python2.7/pyaudio.py", line 564, in read
        return pa.read_stream(self._stream, num_frames)
    IOError: [Errno Input overflowed] -9981
    The code which causes this error:

    Code:
    ORIGINAL
    def record(seconds,playback,chan):
    #function to record audio
    #inputs:  seconds - number of seconds to record, playback - value of 1 will send audio to output while recording, 0 will not
    #outputs:  data - recorded audio data
    	RECORD_SECONDS = seconds
    	all = []
    	global RATE
    	for i in range(0, int(RATE / chunk * RECORD_SECONDS)):
    		data = stream.read(chunk)
    		if playback == 1:
    			stream.write(data)
    		all.append(data)
    	data = ''.join(all)
    	return data
    
    MY ALTERATION to deal with the error:
    
    def record(seconds,playback,chan):
    #function to record audio
    #inputs:  seconds - number of seconds to record, playback - value of 1 will send audio to output while recording, 0 will not
    #outputs:  data - recorded audio data
    	RECORD_SECONDS = seconds
    	all = []
    	global RATE
    	for i in range(0, int(RATE / chunk * RECORD_SECONDS)):
    		#print "i: ", i, "top",int(RATE / chunk * RECORD_SECONDS)
    		try:
    		    data = stream.read(chunk)
    		except IOError as ex:
    		       if ex[1] != pyaudio.paInputOverflowed:
    			   raise
    		       print "error read step 1"
    		       #time.sleep(0.05)
    		       wait_sec(0.05)
    #		       data = stream.read(chunk) # data = '\x00' * chunk
    		if playback == 1:
    			stream.write(data)
    		all.append(data)
    	data = ''.join(all)
    	return data
    I added a wait for the buffer to fill? via a function that was included in the code handle when time.sleep might get used in multiple sub-processes. I've had time.sleep in place of wait_sec.

    It seems to handle the overflow.. but the delay causes the trigger mechanism to not trigger unless this error condition does not occur.

    I tried to use larger buffer sizes, but it causes two issues, the script uses a FFT based on the chunk size and sample rate. Increasing the buffer past 2176 causes another portion of the sript to fail with another error. I changed this back to the originals 1024, and slowly decreased this wait time to find a point it didn't occur that much, but it still occurs, so this is obviously NOT the answer/solution.

    In the original script this error can occur immediately when starting up, crashing the script. Start again its fine till the error. Obviously as seen in my code one idea was just to fill the error condition with 00 which obviously won't trigger a thing.

    Any ideas?

    Bug #2 - Chipmunk/fast recording

    The original script attempted to open the sound card again and then record the audio via this new handle. Linux threw a fit and crashed on that plan.

    Code:
    Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1670
    Expression 'AlsaOpen( &alsaApi->baseHostApiRep, params, streamDir, &self->pcm )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1830
    Expression 'PaAlsaStreamComponent_Initialize( &self->capture, alsaApi, inParams, StreamDirection_In, NULL != callback )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2092
    Expression 'PaAlsaStream_Initialize( stream, alsaHostApi, inputParameters, outputParameters, sampleRate, framesPerBuffer, callback, streamFlags, userData )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2764
    Unhandled exception in thread started by <function alert at 0xa6b6684>
    Traceback (most recent call last):
      File "T_mybeta.py", line 395, in alert
        output_device_index = output_device_indices[output_device.get()])
      File "/usr/lib/pymodules/python2.7/pyaudio.py", line 714, in open
        stream = Stream(self, *args, **kwargs)
      File "/usr/lib/pymodules/python2.7/pyaudio.py", line 396, in __init__
        self._stream = pa.open(**arguments)
    IOError: [Errno Device unavailable] -9985
    So I commented out the new open statement, which solves the issue, and it records now.... but I now have this issue with the chipmunk recording. Which is probably related to the original handle being processed through the FFT.

    So is there a way to open the card again for input to record it?

    I've tested recording using the same settings, 1024 chunk, 11025 sample, thats fine, in a sample/test program.

    Bug #1 also occurs when I apply my fix (comment out the 2nd attempt to open the card), so I applied the same concept try, wait_sec option... no buffer errors, but the chipmunks are there.

    To record the audio coming in AND monitor for the trigger event, ie there could be multiple trigger events that happening at the same time. ie: trigger 1, trigger2, trigger 3, trigger 4, audio (Yes I am being vague on the trigger event, for a reason. The trigger logic seems to work if I can get past the buffer issue and then resolve the recording issue.)

    Ideas? ?

    Thank in advance.
  2. #2
  3. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,995
    Rep Power
    481
    Terribly sorry, I don't begin to know how to run your code fragment. Python is supposed to have batteries included; you'd expect platform independence. If this were my project, I'd learn about the alsa driver/mixer whatever it is using the bash command line:
    Code:
    $ apropos alsa
    aconnect (1)         - ALSA sequencer connection manager
    alsactl (1)          - advanced controls for ALSA soundcard driver
    alsactl_init (7)     - alsa control management - initialization
    alsaloop (1)         - command-line PCM loopback
    alsamixer (1)        - soundcard mixer for ALSA soundcard driver, with ncurse...
    amidi (1)            - read from and write to ALSA RawMIDI ports
    amixer (1)           - command-line mixer for ALSA soundcard driver
    aplay (1)            - command-line sound recorder and player for ALSA soundc...
    arecord (1)          - command-line sound recorder and player for ALSA soundc...
    aseqdump (1)         - show the events received at an ALSA sequencer port
    aseqnet (1)          - ALSA sequencer connectors over network
    speaker-test (1)     - command-line speaker test tone generator for ALSA
    Browsing Gnu solfege, an ear training program written in python might be useful to you.

    Or, put on your lisp programming hat, try nyquist or audacity. (audacity might depend on nyquist.)
    [code]Code tags[/code] are essential for python code and Makefiles!
  4. #3
  5. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,995
    Rep Power
    481
    Hey! Did you resolve this problem? I'm writing software to visually display data recorded on the microphone. I'd like to connect with alsa directly. The only thing I've managed so far is to open a pipe to arecord and read data from that. Thanks, Dave.
    Code:
    #python3import math
    import array
    import tkinter
    import subprocess
    
    TAU=2*math.pi # http://www.youtube.com/watch?v=jG7vhMMXagQ
    
    def interpolate(x,p0,p1):
        '''
            # provide 2D linear interplation
            >>> interpolate(0.5,(0,0),(1,1))
            0.5
            >>> interpolate(0.5,(1,0),(0,1))
            0.5
            >>> interpolate(3,(1,0),(5,2))
            1.0
        '''
        (x0,y0) = p0
        (x1,y1) = p1
        slope = (y1-y0)/(x1-x0)
        intercept = y0-slope*x0
        return slope*x+intercept
    
    def coordinates(radii,trig,scale,center):
        '''
            convert amplitude,time data to radius, angle
        '''
        (sin,cos) = trig
        coords = array.array('I',(0,)*(2*len(sin)))
        for (i,(s,c,r)) in enumerate(zip(sin,cos,radii)):
            coords[2*i] = int(center+scale*r*c)
            coords[2*i+1] = int(center+scale*r*s)
        return coords
    
    def average(a,c,L):
        '''
            return list of averages length L centered at intervals spaced c apart.
        '''
        result = []
        h = L//2
        H = h+1
        for i in range(0,len(a),c):
            d = a[max(i-h,0):i+H]
            result.append(sum(d)/len(d))
        return result
    
    class CircleGraph:
    
        def __init__(self,R = 256):
            '''
                R: maximum radius
            '''
            self.tk = tkinter.Tk()
            self.tk.wm_geometry(newGeometry='%dx%d+20+20'%(2*R,2*R))
            self.canvas = tkinter.Canvas(self.tk)
            self.canvas.pack(expand=tkinter.YES,fill=tkinter.BOTH)
            self.radius = R
            R = float(self.radius)
            rreciprocal = 1.0/R
            sin = array.array('f',range(len(self)))   # precompute re-used trig
            cos = sin[:]
            self.hftrig = sin,cos
            for a in range(len(self)):
                angle = a*rreciprocal
                sin[a] = math.sin(angle)
                cos[a] = math.cos(angle)
            self.lftrig = sin[::5], cos[::5]
            #self.hf = self.canvas.create_polygon(
            #    *coordinates((0.5 for i in sin),self.hftrig,R,R),
            #    outline='red')
            self.hf = [self.canvas.create_line(
                *coordinates((0.5 for i in sin),self.hftrig,R,R),fill='red')
                for i in range(3)
                ]
            self.lf = self.canvas.create_line(
                *coordinates((0.5 for i in self.lftrig[0]),self.lftrig,R,R),
                fill='darkblue',width=3)
            self.controls()
    
        def terminate(self):
            self.PROCEED = False
    
        def controls(self):
            L = len(self)
            master = tkinter.Toplevel(self.tk)
            master.wm_geometry(newGeometry='+%d+20'%(40+2*self.radius))
            tkinter.Button(master,text='exit',command=self.terminate).pack(side=tkinter.BOTTOM)
            rate = tkinter.Scale(master,from_=0,to=100,
                                 label='sample rate',orient=tkinter.HORIZONTAL)
            rate.pack(side=tkinter.BOTTOM)
            rate.set(50)
            avgspan = tkinter.Scale(master,from_=1,to=L//30,
                                    label='filter width',orient=tkinter.HORIZONTAL)
            avgspan.pack(side=tkinter.BOTTOM)
            avgspan.set(min(30,L//30))
            self.controlbox = master
            self.PROCEED = True
            self.rate = rate
            self.avgspan = avgspan
            self.controlBox = master
    
        def __len__(self):
            return int(self.radius*TAU)
    
        def __call__(self):
            global coordinates # might run faster with this unnecessary statement
            L = len(self)
            rate = self.rate.get
            RATE = rate()
            a = int(0.5+math.exp(interpolate(RATE,
                                                (0,math.log(2000)),
                                                (100,math.log(192000)))))
            with subprocess.Popen(
                    'arecord --format=U8 --rate=%d'%(a), # --format=U8 byte, --rate=8000 Hz,  --mmap could be faster
                    bufsize=L,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=True) as arecord:
                tk = self.tk
                hf = self.hf
                HFTally = len(hf)
                hfn = 0
                hftrig = self.hftrig
                lf = self.lf
                lftrig = self.lftrig
                R = self.radius
                read = arecord.stdout.read
                coords = self.canvas.coords
                itemconfig = self.canvas.itemconfig
                avgspan = self.avgspan.get
                scale = 1.0/256.0
                arr = array.array
                while self.PROCEED and (RATE == rate()):
                    data = arr('f',(scale*r for r in read(L)))
                    coords(lf,*coordinates(average(data,5,avgspan()),lftrig,R/2,R))
                    itemconfig(hf[(hfn+2)%HFTally],fill='#f55')
                    itemconfig(hf[(hfn+1)%HFTally],fill='#faa')
                    coords(hf[hfn],*coordinates(data,hftrig,R,R))
                    itemconfig(hf[hfn],fill='#f00')
                    hfn = (hfn + 1) % HFTally
                    #                itemconfig(hf[hfn],fill='darkred')
                    tk.update()
                return self.PROCEED
    
    def main():
        cg = CircleGraph()
        while cg():
            pass
    
    if '__main__' == __name__:
        main()
    [code]Code tags[/code] are essential for python code and Makefiles!
  6. #4
  7. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2012
    Posts
    6
    Rep Power
    0
    Python and Tk is all the versions from the Kubuntu repos. Nothing compiled from sources, just apt-get install or synaptic.







IMN logo majestic logo threadwatch logo seochat tools logo