C++ 在 Linux 中录制 PCM 流

问题描述

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

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

有什么帮助吗?谢谢

#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","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","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","hw_params access setted\n");
    
  if ((err = snd_pcm_hw_params_set_format (capture_handle,format)) < 0) {
    fprintf (stderr,"cannot set sample format (%s)\n","hw_params format setted\n");
    
  if ((err = snd_pcm_hw_params_set_rate_near (capture_handle,&rate,"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,2)) < 0) {
    fprintf (stderr,"cannot set channel count (%s)\n","hw_params channels setted\n");
    
  if ((err = snd_pcm_hw_params (capture_handle,"cannot set parameters (%s)\n","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","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_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);
}

解决方法

将输出音频捕获到文件的最简单方法是使用内置的 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 文件。音频也将输出到系统上的卡 0 声卡。

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

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

,

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