How to record sound in buffer using ALSA

Aiurea Adica tot YO picture Aiurea Adica tot YO · Apr 14, 2014 · Viewed 9.5k times · Source

I'm begining to learn linux and ALSA and I was wondering if there is a way to store the sound I record from a microphone a directly to the buffer. I read here http://www.linuxjournal.com/article/6735?page=0,2 how to make my recording program. But what I need is a little more complex. I need to record sound untill I hit a key. The reason I need this is because I'm messing with a RaspberryPI(debian on it) and to see if I could turn it into a sound monitoring/detecting device.

My main problem is now that when I try to use it to record (./Rec >name.raw ) it does nothing. It just ouputs an empty .raw file.

#define ALSA_PCM_NEW_HW_PARAMS_API
#include <termios.h>
#include <alsa/asoundlib.h>

struct termios stdin_orig;  // Structure to save parameters

void term_reset() {
        tcsetattr(STDIN_FILENO,TCSANOW,&stdin_orig);
        tcsetattr(STDIN_FILENO,TCSAFLUSH,&stdin_orig);
}

void term_nonblocking() {
        struct termios newt;
        tcgetattr(STDIN_FILENO, &stdin_orig);
        fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // non-blocking
        newt = stdin_orig;
        newt.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &newt);

        atexit(term_reset);
}

int main() {
  int key=0;
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

  /* Open PCM device for recording (capture). */
  rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) {
    fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* Set the desired hardware parameters. */

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

  /* One channel (mono) */
  snd_pcm_hw_params_set_channels(handle, params, 1);

  /* 16000 bits/second sampling rate */
  val = 16000;
  snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

  /* Set period size to 2048 frames. */
  frames = 2048;
  snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params, &frames, &dir);
  size = frames * 2; /* 2 bytes/sample, 1 channels */
  buffer = (char *) malloc(size);

  while (key == 0) 
  {

    rc = snd_pcm_readi(handle, buffer, frames);
    if (rc == -EPIPE) 
    {
      /* EPIPE means overrun */
      fprintf(stderr, "overrun occurred\n");
      snd_pcm_prepare(handle);
    } 
    else if (rc < 0)
    {
      fprintf(stderr, "error from read: %s\n", snd_strerror(rc));
    } 
    else if (rc != (int)frames) 
    {
      fprintf(stderr, "short read, read %d frames\n", rc);
    }

    rc = write(1, buffer, size);

    if (rc != size)
      fprintf(stderr, "short write: wrote %d bytes\n", rc);
    key = getchar();
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}

Answer

Boris Burkov picture Boris Burkov · Apr 14, 2014

Here is how I did that with python. Tested to work on my desktop Debian with USB Plantronics headphones. You need to install python-qt4 and python-pyaudio packages for this to work.

Also, you'll need to set your input device to microphone. In GNOME I switched both input and output devices via System Tools -> System Settings -> Sound. If you have Raspbian, not Debian on your Raspberry, as I do, it's gonna be tougher, cause there's LXDE instead of GNOME. You can use alsamixer and F6 button there to set audio cards, but the problem is that ALSA is low-level interface, while most Linuxes use some sound server on top of it, such as PulseAudio or JACK. You'll need some luck/spent time to make sure you switched input/output device to your mic/headphones.

If you use Jack microphone, plugged in through Jack input of your Raspberry Pi, note that Raspberry's Jack is input only, so you won't be able to play back your recordings and need some USB headphones to listen to your wav.

Personally, I feel that ALSA is very poorly documented (I suppose it's intentional job security) and I don't like to deal with it.

import pyaudio
import wave
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

# This is Qt part: we create a window, which has a "stop" flag.
# Stop flag defaults to False, but is set to True, when you press a key.
# Value of that flag is checked in main loop and loop exits when flag is True.

app = QApplication(sys.argv)
class MyWindow(QWidget):
    def __init__(self):
        super(QWidget, self).__init__()
        self.stop = False
    def keyPressEvent(self, event):
        print "keyPressedEvent caught!"
        self.stop = True

window = MyWindow()
window.show()

# This is sound processing part: we create an input stream to read from microphone.

p = pyaudio.PyAudio()
stream = p.open(format = p.get_format_from_width(2),
        channels = 2,
        rate=44100,
        input=True,
        output=False,
        frames_per_buffer=1024)

# This is main loop: we iteratively poll audio and gui: audio data are stored in output_buffer,
# whereas gui is checked for stop flag value (if keyPressedEvent happened, flag will be set
# to True and break our main loop).

output_buffer = ""
while True:
    app.processEvents()
    data = stream.read(1024)
    output_buffer += data
    if window.stop: break

stream.stop_stream()
stream.close()

# Here we output contents of output_buffer as .wav file
output_wav = wave.open("output.wav", 'w')
output_wav.setparams((2, 2, 44100, len(output_buffer),"NONE","not compressed"))
output_wav.writeframesraw(output_buffer)

p.terminate()