问题描述
我正在 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++程序中读取它。