Alberto Roman Alberto Roman - 29 days ago 17
Python Question

cracking sound sine tone in pyaudio

I am using python and pyaudio to stream a pure sine tone using a callback method, in order to later modulate the sound via user input. Everything is fine except that when i run the code, i get 1-2 seconds of a cracking-buzzing sound associated to the warning message
ALSA lib pcm.c:7339:(snd_pcm_recover) underrun occurred
After that, the sine tone is streamed correctly. Any hints about how to remove the initial popping sound?
here is the code that stream the sound for one second

import pyaudio
import time
import numpy as np

CHANNELS = 1
RATE = 44100
freq = 600
CHUNK = 1024
lastchunk = 0
def sine(current_time):
global freq,lastchunk
length = CHUNK
factor = float(freq)*2*np.pi/RATE
this_chunk = np.arange(length)+lastchunk
lastchunk = this_chunk[-1]
return np.sin(this_chunk*factor)

def get_chunk():
data = sine(time.time())
return data * 0.1


def callback(in_data, frame_count, time_info, status):
chunk = get_chunk() * 0.25
data = chunk.astype(np.float32).tostring()
return (data, pyaudio.paContinue)

p = pyaudio.PyAudio()

stream = p.open(format=pyaudio.paFloat32,
channels=CHANNELS,
rate=RATE,
output=True,
stream_callback=callback)

stream.start_stream()
time.sleep(1)




stream.stop_stream()
stream.close()


Cheers

Answer

PortAudio (the library behind PyAudio) allows you to specify a block size, which is typically called CHUNK in the PyAudio examples. If you don't specify one, the default is 0, which in PortAudio terms means that the block size will be chosen automatically and will even change from callback to callback!

To check that, try printing frame_count (which is another name for the block size) within the callback. I suspect that PortAudio chooses a too small block size in the beginning and when that causes underruns, it increases the block size. Am I right?

To avoid this, you should specify a fixed block size from the beginning, using:

stream = p.open(..., frames_per_buffer=CHUNK, ...)

... where frames_per_buffer is yet another name for the block size.

This also makes more sense since up to now you use length = CHUNK in your code without knowing the actual block size!

If this still leads to underruns, you can try further increasing the block size to 2048.

Finally, let me take the liberty to make a shameless plug for my own PortAudio wrapper, the sounddevice module. It basically does the same as PyAudio, but it's easier to install, IMHO has a nicer API and it supports NumPy directly, without you having to do the manual conversions.

Comments