I’m no longer going to use the phrase like getting water from a stone, because it is probably easier than getting sound from a Raspberry Pi! Here’s some notes on how I’ve coaxed a few notes out of my Pi.
Preface
OK, I was a little harsh in the beginning; sound has come a long way with Linux, and I have been able to get the stock sound system working. My current project is to install Jasper on Jessie, and my specific problem is with Python audio.
System
I am using what I call Kamino Base, which is a clean installation of the latest Jessie distribution, including whatever gets updated with sudo apt-get update && sudo apt-get upgrade , along with installing vim
and screen
. Kamino Base is configured to use my home network, a US keyboard and timezone; essentially, all of the configuration stuff that I typically do is done and the image is saved. Kamino Base provides me with a “clean system” from which to try (and retry) installations and configurations without having to remember what is and isn’t on the system. Hardware-wise, it’s a version 2 Pi and I have a USB mic attached. My goal is to record audio from the mic using Python.
Software
To Kamino Base I’ve added sudo apt-get install python-audio python3-audio
cat /proc/asound/cards
looks like this
0 [ALSA ]: bcm2835 - bcm2835 ALSA bcm2835 ALSA 1 [Device ]: USB-Audio - USB PnP Sound Device C-Media Electronics Inc. USB PnP Sound Device at usb-3f980000.usb-1.2, full spe
There’s a lot of information on the web on how to change the order of the sound devices, most of which involves options in the file /etc/modprobe.d/alsa-base.conf
. Jessie has a new way of doing things, and these methods do not work (at least, not when I try them) so I have decided to leave the device order as is and move on from here.
Sanity check
First, I wanted to make sure that sound playing and recording is working properly. I do this with arecord -D plughw:1,0 -d 3 out.wav
. The -D
flag directs arecord to use the USB microphone and -d
provides a duration in seconds. No errors occur and I check the file with aplay out.wav
. I hear my voice and do a happy dance.
Insanity check
The pyaudio documentation has some sample programs to demonstrate how they are used. The first one, to play an audio file, is this:
"""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(sys.argv[1], 'rb') p = pyaudio.PyAudio() stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True) data = wf.readframes(CHUNK) while data != '': stream.write(data) data = wf.readframes(CHUNK) stream.stop_stream() stream.close() p.terminate()
Works wonderfully, out of the box. Nothing needs to be done. The recording example fails miserably. To debug, I opened the python shell and spent some time with the pyaudio function is_format_supported()
. I found that pyaudio was using the correct input device (as indicated by get_default_input_device_info()
) and that eventually, I had a valid format for recording input. Hint – pay close attention to the error produced by is_format_supported()
which indicates the problem (e.g. rate, channels, etc). Still, I get the dreaded underflow error and no sound is recorded. Lastly, I played around with the variable, CHUNK
in the program (below) which was originally set to 1024. Decreasing this to 512 did the trick for me. Here’s the code that finally worked for me.
"""PyAudio example: Record a few seconds of audio and save to a WAVE file.""" import pyaudio import wave CHUNK = 512 FORMAT = pyaudio.paInt16 CHANNELS = 1 RATE = 44100 RECORD_SECONDS = 2 WAVE_OUTPUT_FILENAME = "output.wav" p = pyaudio.PyAudio() print p.is_format_supported(input_format=FORMAT, input_channels=CHANNELS, rate=RATE,input_device=2) print p.get_default_input_device_info() print p.get_device_info_by_index(2) stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK, input_device_index=2) print("* recording") frames = [] for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): data = stream.read(CHUNK) frames.append(data) print("* done recording") stream.stop_stream() stream.close() p.terminate() wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') wf.setnchannels(CHANNELS) wf.setsampwidth(p.get_sample_size(FORMAT)) wf.setframerate(RATE) wf.writeframes(b''.join(frames)) wf.close()
Conclusion
So, several days (yikes) after starting this exercise, I finally have some idea of the problems occurring with pyaudio. I went down several wrong paths (order of devices was incorrect, default devices were not properly defined, incorrect format) took some time to work through. In hindsight, the underflow error being received should have indicated to me that there was a stream problem and grabbing smaller chunks of data would help address the issue. That said, I spent much of my time googling for other people’s solutions instead of thinking through my own. A mix of the two was necessary in this case.
Hello,
Could you please help, I’m interested in your Flickering LED Tealight candle but when connecting using the links on Thingiverse they don’t bring me to the instructions on your site.
Thank you,
Yeah, I ran in to some website problems and haven’t reposted all of the old articles yet. I’ll send you a private message.