Как записать звук в буфер с помощью ALSA

Я начинаю изучать Linux и ALSA, и мне было интересно, есть ли способ сохранить звук, который я записываю с микрофона, прямо в буфер. Я прочитал здесь http://www.linuxjournal.com/article/6735?page=0,2 как сделать свою программу записи. Но то, что мне нужно, немного сложнее. Мне нужно записывать звук, пока я не нажму клавишу. Причина, по которой мне это нужно, заключается в том, что я возился с RaspberryPI (debian на нем) и посмотреть, смогу ли я превратить его в устройство для мониторинга/обнаружения звука.

Моя главная проблема сейчас в том, что когда я пытаюсь использовать его для записи (./Rec >name.raw ), он ничего не делает. Он просто выводит пустой файл .raw.

#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;
}

person Aiurea Adica tot YO    schedule 14.04.2014    source источник
comment
Разве вам не нужно просто читать клавиатуру в неблокирующем режиме внутри цикла while, а затем выходить из цикла? Также в настоящее время буфер перезаписывается каждый период. Если вы хотите записать все в буфер, вы должны сделать его намного больше и перемещать указатель буфера на размер каждого периода.   -  person Martin Drautzburg    schedule 14.04.2014
comment
Это то, что snd_pcm_readi уже делает.   -  person CL.    schedule 14.04.2014
comment
@КЛ. Не могли бы вы быть более конкретным? А также что делает snd_pcm_readn?   -  person Aiurea Adica tot YO    schedule 14.04.2014
comment
snd_pcm_readi считывает образцы в буфер. В программе, которую вы показали, эта переменная называется buffer.   -  person CL.    schedule 14.04.2014
comment
@КЛ. Да. Но, пожалуйста, прочитайте весь вопрос. Я немного нуб и не знаю, как внести изменения, необходимые для остановки цикла при вводе с клавиатуры.   -  person Aiurea Adica tot YO    schedule 14.04.2014
comment
@КЛ. был перенесен из линукса.   -  person Aiurea Adica tot YO    schedule 14.04.2014


Ответы (2)


Вот как я сделал это с python. Протестировано для работы на моем настольном Debian с USB-наушниками Plantronics. Для этого вам необходимо установить пакеты python-qt4 и python-pyaudio.

Кроме того, вам нужно настроить устройство ввода на микрофон. В GNOME я переключал устройства ввода и вывода через System Tools -> System Settings -> Sound. Если у вас на Raspberry Raspbian, а не Debian, как у меня, будет сложнее, потому что вместо GNOME стоит LXDE. Вы можете использовать alsamixer и кнопку F6 для установки звуковых карт, но проблема в том, что ALSA является низкоуровневым интерфейсом, а большинство Linux используют какой-нибудь звуковой сервер поверх него, например PulseAudio или JACK. Вам понадобится немного удачи / потраченного времени, чтобы убедиться, что вы переключили устройство ввода / вывода на микрофон / наушники.

Если вы используете микрофон Jack, подключенный через вход Raspberry Pi, обратите внимание, что разъем Raspberry предназначен только для входа, поэтому вы не сможете воспроизводить свои записи и вам понадобятся USB-наушники для прослушивания wav.

Лично я считаю, что ALSA очень плохо документирована (я полагаю, это преднамеренно безопасность работы ) и я не люблю иметь дело с этим.

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()
person Boris Burkov    schedule 14.04.2014
comment
stackoverflow.com/ вопросов/23056532/ не могли бы вы взглянуть на эту тему здесь? И скажите мне, если вы найдете что-то не так с ним? - person Aiurea Adica tot YO; 14.04.2014
comment
@AiureaAdicatotYO хорошо, я не программировал на C навсегда, но в коде, который вы предоставили, для переменной key установлено значение 0, а в цикле while вы говорите while (key != 0), поэтому вы никогда не входите в цикл. - person Boris Burkov; 14.04.2014
comment
@Боб... боже мой. Спасибо. Я ненавижу легкие промахи. - person Aiurea Adica tot YO; 14.04.2014

В этом коде показано, как выполнять захват из ALSA в C++ в цикле while: ">https://github.com/flatmax/gtkiostream/blob/master/test/ALSACaptureTest.C#L95

Вы можете изменить цикл, чтобы записывать навсегда, замените:

while (N>0){

с участием

while (1){

Теперь вам понадобится дополнительный код. Во-первых, чтобы прочитать неблокирующий символ, добавьте это в начало файла:

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

ngetc — это неблокирующее чтение из стандартного ввода. Он возвращает -1, когда ничего не читается. Он возвращает> 1, когда вы нажимаете клавишу ввода.

Итак, наконец, собрав все это вместе, измените:

while (N>0){

с участием

while (1){
  int enter=ngetc(&ch);
  if (enter>0)
    break;
person Matt    schedule 26.03.2020