【问题标题】:How to record sound in buffer using ALSA如何使用 ALSA 在缓冲区中录制声音
【发布时间】:2014-05-28 04:52:01
【问题描述】:

我开始学习 linux 和 ALSA,我想知道是否有办法将我从麦克风 a 录制的声音直接存储到缓冲区。我在这里阅读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;
}

【问题讨论】:

  • 您是否只需在 while 循环内以非阻塞模式读取键盘然后退出循环?目前,缓冲区在每个时期都会被覆盖。如果要将所有内容都记录到缓冲区中,则必须将其放大很多,并在每个周期将缓冲区指针推进大小。
  • snd_pcm_readi 已经这样做了。
  • @CL。你可以再详细一点吗?还有 snd_pcm_readn 是做什么的?
  • snd_pcm_readi 将样本读入缓冲区。在您展示的程序中,该变量恰好被命名为buffer
  • @CL。是的,它确实。但请阅读整个问题。我有点笨,不知道如何进行修改以使循环在键盘输入处停止。

标签: c audio while-loop recording alsa


【解决方案1】:

这段代码向您展示了如何在一个 while 循环中从 C++ 中的 ALSA 捕获: 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;

【讨论】:

    【解决方案2】:

    这是我用 python 做的。经测试可以在我的桌面 Debian 上使用 USB Plantronics 耳机工作。您需要安装 python-qt4python-pyaudio 软件包才能使其工作。

    另外,您需要将输入设备设置为麦克风。在 GNOME 中,我通过 System Tools -&gt; System Settings -> Sound 切换了输入和输出设备。如果你的树莓派上有Raspbian,而不是Debian,就像我一样,它会更难,因为有LXDE 而不是GNOME。您可以使用alsamixer和那里的F6按钮来设置声卡,但问题是ALSA是低级接口,而most Linuxes use some sound server on top of it,例如PulseAudio或JACK。您需要一些运气/花时间来确保将输入/输出设备切换到麦克风/耳机。

    如果您使用 Jack 麦克风,通过 Raspberry Pi 的 Jack 输入插入,请注意 Raspberry 的 Jack 仅为输入,因此您将无法播放录音并需要一些 USB 耳机来收听您的 wav。

    就我个人而言,我觉得 ALSA 的文档记录很差(我想这是故意的 job security),我不喜欢处理它。

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

    【讨论】:

    • stackoverflow.com/questions/23056532/… 你能在这里看看这个帖子吗?如果您发现它有问题,请告诉我?
    • @AiureaAdicatotYO 好吧,我现在还没有永远用 C 编程,但是在你提供的代码中,key 变量设置为 0 值,在 while 循环中你说 while (key != 0) ,所以你永远不会进入循环。
    • @Bob... 亲爱的上帝。谢谢你。我讨厌容易滑倒。
    猜你喜欢
    • 1970-01-01
    • 2012-05-19
    • 2014-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 2015-11-30
    相关资源
    最近更新 更多