Sound from a Raspberry

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.

 

2 thoughts on “Sound from a Raspberry

  1. 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,

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.