Reputation: 302
I'm using alsa-kernel. My configuration:
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
I have a vsynced game loop that is running at 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);
}
}
On the first iteration of SNDRV_PCM_IOCTL_WRITEI_FRAMES
I get no error.
All subsequent iterations, I get Broken Pipe error.
So, to counteract this, at the end of each frame I check for EPIPE
and call SNDRV_PCM_IOCTL_PREPARE
This removes the Broken Pipe error however no sound is heard.
How best to solve this issue?
Upvotes: 0
Views: 639
Reputation: 33601
And just storing 0x33 will produce silence. You need to vary the data to create a waveform – Craig Estey 20 mins ago
This produces a flat line graph/wave.
@CraigEstey Why will that produce silence? Isn't it the amplitude of the wave? – Ryan McClue 18 mins ago
No, as Marco said ...
@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
You have to generate your own waveform. Here is a crude example:
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;
}
UPDATE:
I believe your notion of a single sample value giving silence is wrong.
More than a notion [Side note: I've written commercial apps for sound before using 16 bit signed stereo PCM mode]. It is what is happening.
PCM devices expect a waveform. Going directly to /dev/snd/pcm*
may have additional issues. See below.
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.
You may be hearing a pop that seems like sound, but the buffer is not [just] a gain factor. It needs to vary. Don't know what your modified code is, but the pop/click could be (e.g.) seeing 0x33 with a zero interspersed (e.g. going beyond the end of the buffer???).
On my system, pcmC0D0p
is my analog speakers and pcmC1D0p
is my USB headphones. From the GTK sound menu under settings, I have to select the other device. Otherwise, it is "owned" by ALSA and/or pulseaudio. For example, to use the headset, I have to select analog speaker in the menu. Then, the app can access the headset. Otherwise, the corresponding /dev/snd/pcm*
device is considered busy.
I downloaded your app. When I just use your original code, (the 0x33), I get no sound--just silence. When I add the sin
code I posted, I get a tone. I can change the frequency by adjusting the rad
increment.
I'm trying to adapt this to continuously load frames instead of just a single 2 seconds. – Ryan McClue 23 hours ago
If it were me, before adding looping, I'd be sure I was getting the sound output corresponding to the buffer data for what I already have.
UPDATE #2:
I stand corrected. Here is the example I'm using: https://gist.github.com/ryan-mcclue/eba7dd459418d7c4d9d5322827dc16cf Run with g++ alsa-vsync.cpp -lX11 -lXrender -lXrandr -lXfixes -lXpresent
.
Colorful ... This version probes for an open device, so it may have issues with pulseaudio. You could hardwire the correct device as a temp workaround and do what I suggested above, but there may be a better way ...
I was unaware of ALSA owning devices (what exactly owns it?).
pulseaudio
is a sound server that acts as a traffic cop for sound applications. It sits between a compatible app and the lower level ALSA system. The apps talk to the sound devices through the pulseaudio APIs to a daemon rather than directly to /dev/snd/pcm*
See: 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
This would depend upon how you want to use the device. That is, just some personal testing/experimentation? Or, a production program of some sort? Because you mentioned another sound card, I'll assume that it's for personal use as you couldn't install another sound card on an arbitrary system.
The usual would probably be to use pulseaudio. Even if you added another physical card, it would initially be configured/probed by pulseaudio.
AFAICT, based on my tests, the daemon opens the /dev/snd/pcm*
devices using O_EXCL
. But, it appears to only open the one "active" device [as shown/selected from the settings menu for sound control].
So, the quick workaround is to do what I did. Via the settings menu, direct the daemon away from the device you want to use. This frees up /dev/snd/pcm*
for the device that you do want to use. Or, you could disable the service via systemd
But ...
I think that the pulseaudio API [because it uses ALSA as the backend] is ALSA compatible.
From the diagram in the above link, there is a libalsa pulse
, so you may be able to hook into pulseaudio without too many modifications.
Upvotes: 1