0

I am trying to write a c program (on linux), that plays a sine wave from the speakers, using the alsa kernel interface:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sound/asound.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <sys/mman.h>
#include <math.h>
#define SND_MASK_MAX 64 // idk why, alsalib used this value
#define MASK_OFS(i) ((i) >> 5)
#define MASK_BIT(i) (1U << ((i) & 31))
#ifndef ALSA_BUFFER_SIZE
#define ALSA_BUFFER_SIZE 4096
#endif
#ifndef ALSA_PERIOD_SIZE
#define ALSA_PERIOD_SIZE 1024
#endif
#define fail(err) {errno = EINVAL;return -1;}
#define success(operation) {operation;return 0;}
#define pexit(s) {perror(s);exit(1);}
#define complain(f) {f;exit(1);}
#define is_mask_empty(m) (m->bits[0] && m->bits[1])
#define is_interval_empty(i) (i->min > i->max || (i->min == i->max && (i->openmin || i->openmax)))
int set_param_mask(struct snd_pcm_hw_params* params, int var, int val) {
 if (var-SNDRV_PCM_HW_PARAM_FIRST_MASK < 0) fail(EINVAL);
 struct snd_mask* mask = &(params->masks[var-SNDRV_PCM_HW_PARAM_FIRST_MASK]);
 if (is_mask_empty(mask)) fail(EFAULT);
 unsigned int v;
 if (val > SND_MASK_MAX) fail(EFAULT);
 mask->bits[MASK_OFS(val)] &= MASK_BIT(val);
 mask->bits[!MASK_OFS(val)] = 0;
 if (!(mask->bits[MASK_OFS(val)])) fail(EFAULT);
 return 0;
}
// dest - refined, src - set from
int snd_interval_refine(struct snd_interval* dest, const struct snd_interval *src)
{
 int changed = 0;
 if (dest->empty)
 fail(ENOENT);
 if (dest->min < src->min) {
 dest->min = src->min;
 dest->openmin = src->openmin;
 changed = 1;
 } else if (dest->min == src->min && !dest->openmin && src->openmin) {
 dest->openmin = 1;
 changed = 1;
 }
 if (dest->max > src->max) {
 dest->max = src->max;
 dest->openmax = src->openmax;
 changed = 1;
 } else if (dest->max == src->max && !dest->openmax && src->openmax) {
 dest->openmax = 1;
 changed = 1;
 }
 if (!dest->integer && src->integer) {
 dest->integer = 1;
 changed = 1;
 }
 if (dest->integer) {
 if (dest->openmin) {
 dest->min++;
 dest->openmin = 0;
 }
 if (dest->openmax) {
 dest->max--;
 dest->openmax = 0;
 }
 } else if (!dest->openmin && !dest->openmax && dest->min == dest->max)
 dest->integer = 1;
 if (is_interval_empty(dest)) {
 dest->empty = 1;
 fail(EFAULT);
 }
 return changed;
}
int set_param_interval(struct snd_pcm_hw_params* params, int var, int val) {
 if (var-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL < 0) fail(EINVAL);
 struct snd_interval* interval = &(params->intervals[var-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
 struct snd_interval test;
 test.empty = 0;
 test.min = test.max = val;
 test.openmin = test.openmax = 0;
 test.integer = 1;
 return snd_interval_refine(interval, &test);
}
int set_param(struct snd_pcm_hw_params* params, int var, int val) {
 if (var <= SNDRV_PCM_HW_PARAM_LAST_MASK) return set_param_mask(params, var, val);
 else if (var <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL) return set_param_interval(params,var,val);
 fail(EINVAL);
}
int get_param_int(struct snd_pcm_hw_params *params, snd_pcm_hw_param_t var, unsigned int *val)
{
 struct snd_interval *i = &(params->intervals[var-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
 if (i->integer) success(*val = i->max);
 fail(EINVAL);
}
int main() {
 // open fd
 int fd = open("/dev/snd/pcmC0D0p", O_RDWR | __O_CLOEXEC);
 if (fd < 0) pexit("open /dev/snd/pcmC0D0p (O_RDWR | __O_CLOEXEC)");
 // setup params
 struct snd_pcm_hw_params* params = calloc(1,sizeof(struct snd_pcm_hw_params));
 for (unsigned i = 0; i <= SNDRV_PCM_HW_PARAM_LAST_MASK-SNDRV_PCM_HW_PARAM_FIRST_MASK; i++)
 memset(&(params->masks[i]), 0xff, SND_MASK_MAX * sizeof(__u32));
 for (unsigned i = 0; i <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; i++) {
 (params->intervals)[i].min = 0;
 (params->intervals)[i].openmin = 0;
 (params->intervals)[i].max = 0xffffffffU;
 (params->intervals)[i].openmax = 0;
 (params->intervals)[i].integer = 0;
 (params->intervals)[i].empty = 0;
 }
 params->rmask = ~0U;
 params->cmask = 0;
 params->info = ~0U;
 // set specific params
 if (ioctl(fd,SNDRV_PCM_IOCTL_HW_REFINE,params) < 0) pexit("ioctl SNDRV_PCM_IOCTL_HW_REFINE");
 if (set_param(params,SNDRV_PCM_HW_PARAM_ACCESS,SNDRV_PCM_ACCESS_RW_INTERLEAVED) < 0 ) pexit("set param SNDRV_PCM_HW_PARAM_ACCESS");
 if (set_param(params,SNDRV_PCM_HW_PARAM_FORMAT,SNDRV_PCM_FORMAT_S16_LE) < 0 ) pexit("set param SNDRV_PCM_HW_PARAM_FORMAT");
 if (set_param(params,SNDRV_PCM_HW_PARAM_CHANNELS,2) < 0 ) pexit("set param SNDRV_PCM_HW_PARAM_CHANNELS");
 if (set_param(params,SNDRV_PCM_HW_PARAM_RATE,48000) < 0 ) pexit("set param SNDRV_PCM_HW_PARAM_RATE");
 if (ioctl(fd,SNDRV_PCM_IOCTL_HW_PARAMS,params) < 0) pexit("ioctl SNDRV_PCM_IOCTL_HW_PARAMS");
 // get period size
 unsigned int period_size;
 if (get_param_int(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,&period_size) < 0) pexit("set param SNDRV_PCM_HW_PARAM_PERIOD_SIZE");
 
 // set sw_params
 struct snd_pcm_sw_params* sparams = calloc(1,sizeof(struct snd_pcm_sw_params));
 sparams->tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
 sparams->period_step = 1;
 sparams->avail_min = period_size;
 sparams->start_threshold = ALSA_BUFFER_SIZE - period_size;
 sparams->stop_threshold = ALSA_BUFFER_SIZE;
 sparams->xfer_align = period_size / 2;
 if(ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, sparams) < 0) pexit("ioctl SNDRV_PCM_IOCTL_SW_PARAMS");
 // create mmaps for sound playing
 snd_pcm_uframes_t boundary = sparams->boundary;
 struct snd_pcm_mmap_status* mmap_status = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
 if(!mmap_status || mmap_status == MAP_FAILED) pexit("mmap PROT_READ MAP_SHARED SNDRV_PCM_MMAP_OFFSET_STATUS");
 struct snd_pcm_mmap_control* mmap_control = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
 if(!mmap_control || mmap_control == MAP_FAILED) pexit("mmap PROT_READ|PROT_WRITE MAP_SHARED SNDRV_PCM_MMAP_OFFSET_CONTROL");
 if(ioctl(fd, SNDRV_PCM_IOCTL_PREPARE) < 0) {
 perror("ioctl SNDRV_PCM_IOCTL_PREPARE");
 munmap(mmap_status, 4096); munmap(mmap_control, 4096);
 exit(1);
 }
 unsigned int numframes=48000;
 short* samples = calloc(numframes,sizeof(short));
 // generate frames
 for (int i = 0; i < numframes; i++) {
 samples[i] = 30000 * sinf(2 * M_PI * 200 *((float)i / (numframes)));
 }
 unsigned char *data = (unsigned char*)samples;
 numframes*=4; // (sizeof(int16_t)/sizeof(byte))
 struct snd_xferi xfer = { 0 };
 int ret, avail;
 if (ioctl(fd,SNDRV_PCM_IOCTL_PREPARE) < 0) pexit("ioctl SNDRV_PCM_IOCTL_PREPARE");
 // play the frames
 int correct_count = 0;
 do {
 xfer.buf = data;
 xfer.frames = numframes > period_size ? period_size : numframes;
 xfer.result = 0;
 if(!(ret = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xfer))) {
 avail = mmap_status->hw_ptr + ALSA_BUFFER_SIZE - mmap_control->appl_ptr;
 if(avail < 0) avail += boundary; else
 if((unsigned int)avail >= boundary) avail -= boundary;
 numframes -= xfer.result;
 data += xfer.result * 4; // 4==channels*(int16_t/byte)
 ++correct_count;
 } else if (errno == EPIPE) {
 if (ioctl(fd,SNDRV_PCM_IOCTL_PREPARE) < 0) pexit("ioctl SNDRV_PCM_IOCTL_PREPARE");
 } else {
 ioctl(fd, SNDRV_PCM_IOCTL_DRAIN);
 close(fd);
 fprintf(stderr,"ioctl SNDRV_PCM_IOCTL_WRITEI_FRAMES failed after successfully sending %li bytes in %d portions: ", ((size_t)data-(size_t)samples),correct_count);
 pexit("");
 }
 } while(numframes > 0);
 // clean up
 free(sparams);
 free(params);
 munmap(mmap_status, 4096);
 munmap(mmap_control, 4096);
 ioctl(fd, SNDRV_PCM_IOCTL_DRAIN);
 close(fd);
}

And after a few iterations of the while loop and emmiting ~0.1 seconds of (always the same) sound on my laptop, but not in the qemu vm, the program always exits with:

ioctl SNDRV_PCM_IOCTL_WRITEI_FRAMES failed after successfully sending 4096 bytes in 32 portions: Input/output error

... instead of writing the whole 48000 bytes in the buffer

(and for some reason 4096 is the same number as the ALSA_BUFFER_SIZE macro)

Does anyone know why this could happen (what I'm doing wrong) and how to fix it?

By the way I'm testing it on a laptop with ubuntu gnome x11 and a fedora quemu vm without pulseaudio or JACK or even asoundlib, and I have checked, that sound works in the vm.

asked Aug 13, 2025 at 13:20
2
  • I did not find an explanation to this problem, but I did find a workaround - writing frames to the fd with write and playing them with ioctl(fd, SNDRV_PCM_IOCTL_START) and also litterly ignoring the "failiure" when errno == EIO || errno == EBADFD (this suggested doing that with the prior: github.com/ralph-irving/squeezelite/issues/35 and it still threw error on EBADFD so I made it not quit on it either). The full working code is here: gist.github.com/stas-badzi/73e1a46e2ea1d55b746694afb013ca5c/… Commented Aug 14, 2025 at 21:34
  • 1
    The code in the question is not complete. Please show a minimal reproducible example so that others can reproduce your problem. Links to external resources are not suitable for questions as they can change or disappear, making the question invalid. Commented Aug 15, 2025 at 8:00

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.