2
votes

I'm trying to use pyaudio to play some wave files but I'm always having slow/crackling/garbled outputs.

When I play this wave file as described bellow, the audio plays just fine:

$ wget http://www.freespecialeffects.co.uk/soundfx/sirens/police_s.wav
$ aplay police_s.wav

However, when I open the 'play_wave.py' example from /pyaudio/test, the audio is so slow and garbled that is useless for any application.

"""PyAudio Example: Play a wave file."""

import pyaudio
import wave
import sys

CHUNK = 1024

#if len(sys.argv) < 2:
#    print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
#    sys.exit(-1)

wf = wave.open('police_s.wav', 'rb')

# instantiate PyAudio (1)
p = pyaudio.PyAudio()

# open stream (2)
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
            output=True)

# read data
data = wf.readframes(CHUNK)

# play stream (3)
while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

# stop stream (4)
stream.stop_stream()
stream.close()

# close PyAudio (5)
p.terminate()

To reproduce a similar poor quality on your laptop/PC, just make the CHUNK = 1 (the output is pretty similar on my Ubuntu)

 

 

Additional information:

What I tried:

1- Another Raspberry Pi B+.

2- Change the audio samples per buffer:

As I was supposing the problem was the audio samples per buffer (the CHUNK variable in this example), I made a loop to increment the CHUNK by 1 and played the audio for each increment. I could notice a slight difference for some CHUNK values, but nothing even close to the quality that I get when I play it by aplay. However, I could notice a big difference between this two files:

1- police_s.wav = 8 bits , 22000Hz, Mono , 176 kb/s -> Way better than the beat.wav played by the same CHUNK (2048)

2- beat.wav = 16bits , 44100Hz, Stereo, 1411 kb/s

When I play the same audio through the example /pyaudio/test/play_wave_callback.py, the output is almost perfect, excepting some interruptions at the end of the audio. So I saw that it doesn't set the CHUNK. It uses the frame_count parameter in the callback function, so I printed it and saw that it was 1024 ¬¬, the same default value that came with the example /pyaudio/test/play_wave.py and that results in a garbled audio.

3- pyaudio 0.2.4: Since hayderOICO mentioned he was using pyaudio 0.2.4 and said "I'm using PyAudio fine.", I decided to give a try on that older version but I got the same result...

4- Added disable_audio_dither=1 to config.txt

I'm using:     Raspberry Pi B+     Raspbian     python 2.7.3     pyaudio v0.2.8     portaudio19-dev     TRRS analog audio

How I installed everything:

1st try:

$ sudo apt-get update
$ sudo apt-get -y install python-dev python-numpy cython python-smbus portaudio19-dev
$ git clone http://people.csail.mit.edu/hubert/git/pyaudio.git
$ cd pyaudio
$ sudo python setup.py install

2nd try:

$ sudo apt-get install python-pyaudio

3rd try:

From GitHub: https://github.com/jleb/pyaudio

 

 

It's very frustrating having the library's example not working properly on Pi. I don't think it's a hardware limitation since the same audio plays well with aplay and other libraries like pygame and SDL2.

I am new to Raspberry Pi and audio programming, so I hope to be doing something stupid... As I am already using a specific wrapper for pyaudio, I really would like to keep using it instead of moving to another library... I appreciate any help, suggestions and advice.

Thanks in advance!

3
I've noticed that the playback is smooth if I play the whole stream at once, but garbled if I do it by chunks in a loop. It seems that while loop can't keep up with the playback, so it skips between chunks. I tried setting frames_per_buffer higher, so it would buffer several chunks before playback, but that just shortens the playback time.orodbhen

3 Answers

1
votes

I had a similar problem on my raspberry pi (and on my mac). In my experience the pyaudio library is a pain to work with (after 2 weeks of battling it, I ended up using pygame). What worked for me was to check the default sample rate of the audio out and check that its the same and play sounds back as numpy arrays.

So on the RPi, I'd check (extrapolating from ubuntu here...) the files

/etc/pulse/daemon.conf
and

/etc/asound.conf or 
~/.asoundrc

Does direct playback of numpy arrays work? If so you could do it in a roundabout way... Here is some code to test if you like

import pyaudio 
import numpy as np


def gensin(frequency, duration, sampRate):

    """ Frequency in Hz, duration in seconds and sampleRate
        in Hz"""

    cycles = np.linspace(0,duration*2*np.pi,num=duration*sampRate)
    wave = np.sin(cycles*frequency,dtype='float32')
    t = np.divide(cycles,2*np.pi)

    return t, wave

frequency=8000 #in Hz
duration=1 #in seconds
sampRate=44100 #in Hz

t, sinWav = gensin(frequency,duration,sampRate)

p = pyaudio.PyAudio()

stream = p.open(format = pyaudio.paInt32, 
                channels = 1, 
                rate = sampRate, 
                output = True)

stream.start_stream()

stream.write(sinWav)

This worked on my RPi and mac, but as said before I ended up using pygame because even with on and off ramps I couldn't get rid of crackling at the beginning and end and sample rate wasn't something that could be easily changed. I would really recommend against pyaudio, but if you are set on it I wish you the best of luck! :)

0
votes

I'm facing same issue on Raspberry Pi with pyaudio. Using a higher value of chunk size (e.g. 40960) and passing it by frames_per_buffer upon p.open would make music playback much smoother, less popping and static noise comparing to the smaller value of chunk size (e.g. 1024).

-1
votes

I had the same problem. But this fixed it: As the first response says, just pass the "frames_per_buffer" argument to the p.open call:

CHUNK=4096
wf = wave.open(filename, 'rb')
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
    channels=wf.getnchannels(),
    rate=wf.getframerate(),
    output=True, frames_per_buffer=CHUNK)
data = wf.readframes(CHUNK)
while len(data) > 0:
    print(len(data))
    stream.write(data)
    data = wf.readframes(CHUNK)