SNDRV_PCM_IOCTL_WRITEI_FRAMES 的 ALSA 破管

ALSA Broken Pipe with SNDRV_PCM_IOCTL_WRITEI_FRAMES

我正在使用 alsa 内核。 我的配置:

SNDRV_PCM_HW_PARAM_ACCESS = SNDRV_PCM_ACCESS_RW_INTERLEAVED
SNDRV_PCM_HW_PARAM_FORMAT = SNDRV_PCM_FORMAT_S16_LE
SNDRV_PCM_HW_PARAM_SUBFORMAT = SNDRV_PCM_SUBFORMAT_STD
SNDRV_PCM_HW_PARAM_CHANNELS = 2
SNDRV_PCM_HW_PARAM_RATE = 48000

我有一个 vsynced 游戏循环 运行 在 60fps:

int num_base_samples = 48000 * (1 / 60);
int num_samples =  num_base_samples * 2;
int16_t buffer[num_samples] = {};

while (true) {
  int16_t *samples = buffer;
  for (int sample_i = 0; sample_i < num_base_samples; sample_i++) {
    *samples++ = 0x33;
    *samples++ = 0x33;
  }

  struct snd_xferi transfer = {};
  transfer.buf = buffer;
  transfer.frames = num_base_samples;
  int res = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &transfer);
  if (res == -1 && ernno == EPIPE) {
    ioctl(fd, SNDRV_PCM_IOCTL_PREPARE);
  }
}

SNDRV_PCM_IOCTL_WRITEI_FRAMES 的第一次迭代中,我没有收到任何错误。 所有后续迭代,我都会收到 Broken Pipe 错误。 因此,为了解决这个问题,在每一帧结束时我检查 EPIPE 并调用 SNDRV_PCM_IOCTL_PREPARE 这会消除 Broken Pipe 错误,但听不到任何声音。 如何最好地解决这个问题?

And just storing 0x33 will produce silence. You need to vary the data to create a waveform – Craig Estey 20 mins ago

这会产生一条扁平线 graph/wave。

@CraigEstey Why will that produce silence? Isn't it the amplitude of the wave? – Ryan McClue 18 mins ago

不,正如马可所说...

@RyanMcClue it's not. It's the value of each sample. If all samples have the same value then there is no wave, it's just a flat line which means silence. – Marco Bonelli 6 mins ago

您必须生成自己的波形。这是一个 粗略的 示例:

double rad = 0.0;
for (int sample_i = 0; sample_i < num_base_samples; sample_i++) {
    double val = sin(rad) * 32767;

    *samples++ = val;
    *samples++ = val;

    // some incremental value ...
    rad += 0.1;

    rad %= 2.0 * M_PI;
}

更新:

I believe your notion of a single sample value giving silence is wrong.

不仅仅是一个概念[旁注:在使用 16 位签名立体声 PCM 模式之前,我已经为声音编写了商业应用程序]。 正在发生的事情。

PCM 设备需要波形。直接转到 /dev/snd/pcm* 可能会有其他问题。见下文。

Take my gist: https://gist.github.com/ryan-mcclue/dc1ce8350125e9741faa14c7f9908e9d Run with gcc alsa-kernel.c -o alsa-kernel && ./alsa-kernel /dev/snd/pcmC0D0p. It loads a single sample value (the memset call) and sound is heard.

您可能会听到类似声音的爆破声,但缓冲区不是[只是]一个增益因子。它需要改变。不知道你的 modified 代码是什么,但是 pop/click 可能(例如)看到 0x33 散布着零(例如超出缓冲区的末尾??? ).

在我的系统上,pcmC0D0p 是我的模拟扬声器,pcmC1D0p 是我的 USB 耳机。在设置下的 GTK 声音菜单中,我必须 select other 设备。否则,它由 ALSA and/or pulseaudio“拥有”。例如,要使用耳机,我必须在菜单中选择 select 模拟扬声器。然后,应用程序可以访问耳机。否则,相应的/dev/snd/pcm*设备被认为忙。

我下载了你的应用。当我只使用您的原始代码(0x33)时,我听不到任何声音——只有寂静。当我添加我发布的 sin 代码时,我听到了提示音。我可以通过调整 rad 增量来改变频率。

I'm trying to adapt this to continuously load frames instead of just a single 2 seconds. – Ryan McClue 23 hours ago

如果是我,在添加循环之前,我会确定我得到的声音输出与我已有的缓冲区数据相对应。


更新#2:

我的意思是正确的。这是我正在使用的示例:https://gist.github.com/ryan-mcclue/eba7dd459418d7c4d9d5322827dc16cf 运行 和 g++ alsa-vsync.cpp -lX11 -lXrender -lXrandr -lXfixes -lXpresent.

Colorful ...此版本探测打开的设备,因此 pulseaudio 可能存在问题。您可以硬连接正确的设备作为临时解决方法并按照我上面的建议进行操作,但可能有更好的方法...

I was unaware of ALSA owning devices (what exactly owns it?).

pulseaudio 是一个声音服务器,充当声音应用程序的交通警察。它位于兼容的应用程序和较低级别的 ALSA 系统之间。这些应用程序通过 pulseaudio APIs 与守护程序而不是直接与 /dev/snd/pcm* 对话,请参阅:https://askubuntu.com/questions/581128/what-is-the-relation-between-alsa-and-pulseaudio-sound-architecture

It seems this may be the case, as it selects the second subdevice pcmC0D3p (using pcmC0D0p causes the program to stall). So, is the only way around this to use pulseaudio or another sound card? – Ryan McClue 14 hours ago

这取决于您希望如何使用该设备。也就是说,只是一些个人testing/experimentation?或者,某种生产程序?因为你提到了另一个声卡,我假设它是供个人使用的,因为你不能在任意系统上安装另一个声卡。

通常可能会使用 pulseaudio。即使您添加了另一张实体卡,最初也是 configured/probed by pulseaudio。

AFAICT,根据我的测试,守护进程使用 O_EXCL 打开 /dev/snd/pcm* 设备。但是,它似乎只从声音控制的设置菜单中打开一个“活动”设备[如shown/selected]。

所以,快速的解决方法是按照我所做的去做。通过设置菜单,将守护进程从您要使用的设备上移开。这为您 想要使用的设备释放了 /dev/snd/pcm*。或者,您可以通过 systemd

禁用该服务

但是...

认为 pulseaudio API [因为它使用 ALSA 作为后端] 与 ALSA 兼容。

从上图 link 中,有一个 libalsa pulse,因此您 可以 无需过多修改即可挂接到 pulseaudio。