How to record sound in buffer using ALSA

11,232

Solution 1

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()

Solution 2

This code shows you how to capture from ALSA in C++ in a while loop : https://github.com/flatmax/gtkiostream/blob/master/test/ALSACaptureTest.C#L95

You can alter the loop there to record forever, replace :

while (N>0){

with

while (1){

Now you'll need some extra code. Firstly to read a character non-blocking add this to the top of the file :

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define ngetc(c) (read (0, (c), 1))

ngetc is a non-blocking read from stdin. It returns -1 when nothing is read. It returns > 1 when you press the enter key.

So finally putting this all together, change :

while (N>0){

with

while (1){
  int enter=ngetc(&ch);
  if (enter>0)
    break;
Share:
11,232
Aiurea Adica tot YO
Author by

Aiurea Adica tot YO

begining to understand more about programming

Updated on June 04, 2022

Comments

  • Aiurea Adica tot YO
    Aiurea Adica tot YO almost 2 years

    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;
    }
    
    • Martin Drautzburg
      Martin Drautzburg about 10 years
      Wouln't you simply have to read the keyboard in non-blocking mode inside your while loop and then exit the loop? Also currently the buffer gets overwritten each period. If you want to record everything into the buffer, you have to make it a lot larger and advance the buffer pointer by size each period.
    • Wirsing
      Wirsing about 10 years
      That's what snd_pcm_readi already does.
    • Aiurea Adica tot YO
      Aiurea Adica tot YO about 10 years
      @CL. Could you be more specific? And also what does snd_pcm_readn do?
    • Wirsing
      Wirsing about 10 years
      snd_pcm_readi reads samples into a buffer. In the program you've shown, that variable happens to be named buffer.
    • Aiurea Adica tot YO
      Aiurea Adica tot YO about 10 years
      @CL. yes... it does. But please read the whole question. I'm a bit noobish and don't know how to make the modifications required for the loop to stop at keyboard input.
    • Aiurea Adica tot YO
      Aiurea Adica tot YO about 10 years
      @CL. was moved from linux.
  • Aiurea Adica tot YO
    Aiurea Adica tot YO about 10 years
    stackoverflow.com/questions/23056532/… could you please take a look at this thread here? And tell me if you find something wrong with it?
  • Boris Burkov
    Boris Burkov about 10 years
    @AiureaAdicatotYO well, I've not programmed in C forever now, but in the code, you supplied, key variable is set to 0 value, and in the while loop you say while (key != 0), so you never enter the loop.
  • Aiurea Adica tot YO
    Aiurea Adica tot YO about 10 years
    @Bob... dear God. Thank you. I hate the easy slips.