C++ 在 Linux 中录制 PCM 流

Posted

技术标签:

【中文标题】C++ 在 Linux 中录制 PCM 流【英文标题】:C++ recording PCM stream in Linux 【发布时间】:2021-09-03 00:18:52 【问题描述】:

我在 Ubuntu 的默认 PCM 设备上播放 .Wav 文件,我可以听到耳机中的 PCM 声音,现在我需要录制 PCM 流(将默认 PCM 设备中播放的 PCM 数据写入本地文件)用 C++。我尝试了 ALSA snd_pcm_readi(capture_handle, buffer, buffer_frames) 方法如下,程序运行良好但缓冲区中的值都是 0。我认为有几种可能性:

我没有选择正确的 PCM 设备(但我仔细检查了 Wav 文件是在默认 PCM 设备中播放的,snd_pcm_open () 设备参数确实是“默认”) 正如this 提到的那样,ALSA 的“捕获”方法不支持播放 PCM 流的“记录”

有什么帮助吗?谢谢

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
          
int main (int argc, char *argv[])

  int i;
  int err;
  char *buffer;
  int buffer_frames = 128;
  unsigned int rate = 44100;
  snd_pcm_t *capture_handle;
  snd_pcm_hw_params_t *hw_params;
    snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;

  if ((err = snd_pcm_open (&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) 
    fprintf (stderr, "cannot open audio device %s (%s)\n", 
             argv[1],
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "audio interface opened\n");
           
  if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) 
    fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params allocated\n");
                 
  if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) 
    fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params initialized\n");
    
  if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) 
    fprintf (stderr, "cannot set access type (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params access setted\n");
    
  if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, format)) < 0) 
    fprintf (stderr, "cannot set sample format (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params format setted\n");
    
  if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, &rate, 0)) < 0) 
    fprintf (stderr, "cannot set sample rate (%s)\n",
             snd_strerror (err));
    exit (1);
  
    
  fprintf(stdout, "hw_params rate setted\n");

  if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, 2)) < 0) 
    fprintf (stderr, "cannot set channel count (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params channels setted\n");
    
  if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) 
    fprintf (stderr, "cannot set parameters (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "hw_params setted\n");
    
  snd_pcm_hw_params_free (hw_params);

  fprintf(stdout, "hw_params freed\n");
    
  if ((err = snd_pcm_prepare (capture_handle)) < 0) 
    fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
             snd_strerror (err));
    exit (1);
  

  fprintf(stdout, "audio interface prepared\n");

  buffer = malloc(128 * snd_pcm_format_width(format) / 8 * 2);

  fprintf(stdout, "buffer allocated\n");

  for (i = 0; i < 10; ++i) 
    if ((err = snd_pcm_readi (capture_handle, buffer, buffer_frames)) != buffer_frames) 
      fprintf (stderr, "read from audio interface failed %d (%s)\n",
               err, snd_strerror (err));
      exit (1);
    
    fprintf(stdout, "read %d done\n", i);
  

  free(buffer);

  fprintf(stdout, "buffer freed\n");
    
  snd_pcm_close (capture_handle);
  fprintf(stdout, "audio interface closed\n");

  exit (0);

【问题讨论】:

@TedLyngmo 感谢我刚刚修复了这些错误的建议。这次缓冲区记录了一些噪音而不是 0,仍然不确定 Alsa 是否可以从正在播放的 PCM 流中记录音频 您的硬件真的支持从播放流中录制吗?如果没有,您必须使用 PulseAudio。 是的,我切换到 PluseAudio 的 module-null-sink 作为我的输出设备,仍然没有弄清楚 C++ 是否可以从 module-null-sink 读取/记录数据,有什么提示吗?谢谢。@CL。 askubuntu.com/questions/60837/… 我想打开一个PulseAudio module-null-sinkALSA snd_pcm_open(),我看到了你的related answer。 aplay -L 仅列出硬件设备,没有虚拟设备。您对为ALSA 打开获取正确的虚拟接收器设备名称有任何想法吗?谢谢。@CL。 【参考方案1】:

将输出音频捕获到文件的最简单方法是使用内置的 ALSA 文件插件。为此,请创建一个具有以下内容的文件 ~/.asoundrc:

@hooks [
    
        func load
        files [
            "~/.asoundrc"
        ]
        errors false
    
]

pcm.writeFile 
    type file
    slave 
        pcm card0 # Now write to the actual sound card
    
    file "/tmp/capture.wav"
    format "wav"


pcm.!default 
  type plug
  slave.pcm "writeFile"


ctl.!default 
  type hw
  card 0

它将强制 alsa 播放音频并将其默认保存到 /tmp/capture.wav 文件中。音频也会输出到系统上的 card 0 声卡。

如果您真的想在播放时将音频写入文件,那么您将需要创建一个 ALSA 插件来读取输入音频,然后将其写入文件。这实质上实现了插件。

如果您确实想编写自己的插件,那么您可以从this C++ external plugin class provided by gtkiostream 开始。您将希望以类似于this audio file capture application 中的 sox.write 方法的方式将音频文件输出添加到“传输”方法。

【讨论】:

非常感谢您的详细回答,我只是找到了一种将播放音频流读入 C++ 缓冲区的方法。 ok - 你想通过默认的 C++ ALSA 插件路由所有音频吗?在该插件中,您可以将输出音频加载到 C++ 缓冲区中? 实际上我将音频输出通过管道传输到系统输入设备并使用 ALSA snd_pcm_readi 方法将输入字节读入 C++ 缓冲区。【参考方案2】:

经过几天的搜索,我终于找到了方法。我的声音输出设备中播放了一个音频,我需要将该 PCM 流读入 C++ 缓冲区以进行进一步操作。我发布的前代码完成了一半的工作,正如this tutorial 提到的,snd_pcm_open (&amp;capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0) 方法确实打开了默认 PCM 设备以从中读取流数据,但是这个 SND_PCM_STREAM_CAPTURE 参数表明它打开了default capture device(例如麦克风输入)将外部模拟语音转换为数字位以供阅读,我的音频在default playback device中播放,如果我将此参数更改为snd_pcm_open (&amp;capture_handle, "default", SND_PCM_STREAM_PLAYBACK, 0),它将不会从中读取任何内容。所以另一半的工作是用pavucontrol将playback device中播放的音频流传输到capture device,一旦输出流传输到capture device,我可以在C++程序中读取它。

【讨论】:

以上是关于C++ 在 Linux 中录制 PCM 流的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ ASIO 库用 C# 录制音频流

无法在 Android 中播放使用 AudioRecord 录制的 pcm 文件

在 Python 中检测并录制音频为 PCM 格式

如何在 iOS 上录制 PCM 音频文件?

基于AudioTrack、AudioRecord获取分贝值、录制时长、PCM解码与编码

Android录制和播放PCM数据