PyAudio: How to access stream.read() data in callback/non-blocking mode

Viewed 1602

Not knowledgeable in programming but in following along with the wire and wire callback examples on the documentation website I am struggling to figure out how to access the stream.read(CHUNK) data in callback mode to be processed by audioop.rms() from the audioop library.

Below are two slightly modified examples. The former is the wire method in blocking mode and processed by the rms function successfully. The latter is the wire method in non-blocking mode which I do not know how to go about accessing the same data.

"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).
"""

import pyaudio
import audioop  # new

CHUNK = 1024
WIDTH = 2
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5

p = pyaudio.PyAudio()

stream = p.open(format=p.get_format_from_width(WIDTH),
                channels=CHANNELS,
                rate=RATE,
                input=True,
                output=True,
                frames_per_buffer=CHUNK)

print("* recording")

while True:  # <-----------------------------------------
    data = stream.read(CHUNK)
    stream.write(data, CHUNK)
    rms = audioop.rms(data, WIDTH)  # new
    print(rms)  # computer audio power

print("* done")

stream.stop_stream()
stream.close()

p.terminate()

non-blocking

"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).

This is the callback (non-blocking) version.
"""

import pyaudio
import time
import audioop  # new

WIDTH = 2
CHANNELS = 2
RATE = 44100

p = pyaudio.PyAudio()


def callback(in_data, frame_count, time_info, status):
    return in_data, pyaudio.paContinue


stream = p.open(format=p.get_format_from_width(WIDTH),
                channels=CHANNELS,
                rate=RATE,
                input=True,
                output=True,
                stream_callback=callback)

stream.start_stream()

while stream.is_active():  # <--------------------------------------------
    time.sleep(0.1)
    # data = stream.read(1024)    # the docs say not to call this
    data = stream.get_read_available()  # not sure what to do
    print(data)     # new
    rms = audioop.rms(data, WIDTH)  # compute audio power
    print(rms)  # new

stream.stop_stream()
stream.close()

p.terminate()
1 Answers

I see this is from almost a year ago, but it may help others. You don't need (and in fact, as you discovered, can't use) stream.read() when using non-blocking mode and the callback function. You don't need to. The callback function will be automatically called in a different thread when the stream you opened has data to input or output from the default sources.

Since you want to do something with each chunk (compute the rms value), you need to do that inside the callback function as it processes each chunk. However, if you try to do too much, it can't process it all before the next chunk is ready, and you lose data. File writes and print statements are therefore not something you should do in the callback. (Think of it like an interrupt handler: only do what's essential). So in the working code below, I just compute the rms in the callback function, and make it a global variable so that you can access it from the main program for printing.

I didn't try to compute the audio time for each chunk you're passing to the callback, so by keeping the time.sleep() value at 0.1 it's possible that the code below is missing intermediate rms values.

"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).

This is the callback (non-blocking) version.
"""

import pyaudio
import time
import audioop  # new

WIDTH = 2
CHANNELS = 2
RATE = 44100

p = pyaudio.PyAudio()

rms = None
def callback(in_data, frame_count, time_info, status):
    # print(in_data)     # takes too long in callback
    global rms
    rms = audioop.rms(in_data, WIDTH)  # compute audio power
    # print(rms)  # new # takes too long in callback
    return in_data, pyaudio.paContinue


stream = p.open(format=p.get_format_from_width(WIDTH),
                channels=CHANNELS,
                rate=RATE,
                input=True,
                output=True,
                stream_callback=callback)

stream.start_stream()

while stream.is_active():  # <--------------------------------------------
    print(rms)    # may be losing some values if sleeping too long, didn't check
    time.sleep(0.1)

stream.stop_stream()
stream.close()

p.terminate()
Related