CJF
CJF

Reputation: 51

Threading issue using python with pyaudio in callback mode

I'm using pyaudio to record audio which I would then like to process in chunks (say, every 5 seconds). I am using pyaudio in callback mode with a callback function that is called for every 1500 samples of audio that are recorded and within that function, those samples are added to a queue. I am also saving the samples to a wav file as they are being recorded just to verify that they are what is expected.

q = Queue.Queue()
flag = False
waveFile = wave.open('recording.wav', 'wb')
waveFile.setnchannels(1)
waveFile.setsampwidth(2)
waveFile.setframerate(RATE)


def callback(in_data, frame_count, time_info, status):
    silent = is_silent(in_data)
    if silent == False:
        numpydata = np.fromstring(in_data, dtype=np.int16)
        waveFile.writeframes(in_data)
        q.put(numpydata)
        callback_flag = pyaudio.paContinue

    elif silent == True and flag == True:
        numpydata = np.fromstring(in_data, dtype=np.int16)
        if len(numpydata) != 0:
            waveFile.writeframes(in_data)
            q.put(numpydata)
        waveFile.close()
        callback_flag = pyaudio.paComplete
    else:
        callback_flag = pyaudio.paContinue

    return (in_data, callback_flag)

p=pyaudio.PyAudio() # start the PyAudio class
stream=p.open(format=pyaudio.paInt16,channels=1,rate=RATE,input=True,frames_per_buffer=1500, stream_callback = callback) #uses default input device

Once the queue is no longer empty, I enter a while loop which adds data from the queue to a seperate buffer and then processes the data in that buffer whenever 5 seconds of audio has been added.

while q.empty() == True:
if q.empty() == False:
    break

buf = []

while 1:
    flag = True
    try:
        snd_data = q.get(True,timeout = 4)
        buf.extend(snd_data)
        if len(buf) >= 220500:

          process audio...


    except Queue.Empty:

My problem is that I am noticing strange unexpected glitches in the audio signal as if the program briefly stops recording for a few samples and then starts back up again. I can see this by opening the recorded wav file in Audacity and zooming in on the signal. I think it is happening because pyaudio calls the callback function in a seperate thread for every 1500 samples and sometimes it might try calling the callback function but the thread for the previous callback function is still open and this callback function is not finished yet, data is still being added to the queue etc (Apologies if this is an incoherent explanation, I'm not quite sure of what terminology to use).

Does anyone know how I can solve this problem? Is there a way of synchronizing it so that each callback function will only be called after the previous callback function has finished processing?

Upvotes: 2

Views: 4152

Answers (1)

Matthias
Matthias

Reputation: 4884

You should be very careful about what you do in your callback function. Most notably, you shouldn't read from or write to files in there. You mentioned that you are writing the file only for debugging, but this may actually cause the glitches.

Also, you should always check for the status argument of your callback function. This tells you if there were overruns or underruns. If you are experiencing overruns or underruns, you can try to increase the block size (and latency) until they disappear.

Here is an example script I made that records into a sound file: rec_unlimited.py. And here is an example that plots the input signal: plot_input.py. You can adapt this to do any other processing instead of plotting.

Is there a way of synchronizing it so that each callback function will only be called after the previous callback function has finished processing?

This is actually the case already, the calls to the callback function will never overlap. But if one invocation of the callback function takes too long, it will be too late to send the generated output data to the sound card and this audio data has to be discarded, leading to audible drop-outs. This is called an "output underrun". If the previous invocation of the callback function took too long, there will be too much audio input data available, some of which will have to be discarded, again leading to drop-outs. This is called an "input overrun".

Upvotes: 4

Related Questions