#include <sys/soundcard.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef SAMPLE_SIZE
#define SAMPLE_SIZE 16
#endif
#if SAMPLE_SIZE == 32
typedef int32_t sample_t;
int format = AFMT_S32_NE;
#elif SAMPLE_SIZE == 16
typedef int16_t sample_t;
int format = AFMT_S16_NE;
#elif SAMPLE_SIZE == 8
typedef int8_t sample_t;
int format = AFMT_S8_NE;
#else
#error Unsupported sample format!
typedef int32_t sample_t;
int format = AFMT_S32_NE;
#endif
typedef struct config {
char *device;
int channels;
int fd;
int format;
int frag;
int sample_count;
int sample_rate;
int sample_size;
int chsamples;
int mmap;
oss_audioinfo audio_info;
audio_buf_info buffer_info;
} config_t;
static inline void
check_error(const int value, const char *message)
{
if (value == -1)
err(1, "OSS error: %s\n", message);
}
static inline int
size2frag(int x)
{
int frag = 0;
while ((1 << frag) < x)
++frag;
return (frag);
}
static void
oss_split(config_t *config, sample_t *input, sample_t *output)
{
int channel, index, i;
for (i = 0; i < config->sample_count; ++i) {
channel = i % config->channels;
index = i / config->channels;
output[channel * index] = input[i];
}
}
static void
oss_merge(config_t *config, sample_t *input, sample_t *output)
{
int channel, index;
for (channel = 0; channel < config->channels; ++channel) {
for (index = 0; index < config->chsamples; ++index) {
output[index * config->channels + channel] =
input[channel * index];
}
}
}
static void
oss_init(config_t *config)
{
int error, tmp, min_frag;
config->fd = open(config->device, O_RDWR);
check_error(config->fd, "open");
config->audio_info.dev = -1;
error = ioctl(config->fd, SNDCTL_ENGINEINFO, &(config->audio_info));
check_error(error, "SNDCTL_ENGINEINFO");
printf("min_channels: %d\n", config->audio_info.min_channels);
printf("max_channels: %d\n", config->audio_info.max_channels);
printf("latency: %d\n", config->audio_info.latency);
printf("handle: %s\n", config->audio_info.handle);
if (config->audio_info.min_rate > config->sample_rate ||
config->sample_rate > config->audio_info.max_rate) {
errx(1, "%s doesn't support chosen samplerate of %dHz!\n",
config->device, config->sample_rate);
}
if (config->channels < 1)
config->channels = config->audio_info.max_channels;
if (config->mmap) {
tmp = 0;
ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp);
}
tmp = config->channels;
error = ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp);
check_error(error, "SNDCTL_DSP_CHANNELS");
if (tmp != config->channels) {
errx(1, "%s doesn't support chosen channel count of %d set "
"to %d!\n", config->device, config->channels, tmp);
}
config->channels = tmp;
tmp = config->format;
error = ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp);
check_error(error, "SNDCTL_DSP_SETFMT");
if (tmp != config->format) {
errx(1, "%s doesn't support chosen sample format!\n",
config->device);
}
tmp = config->sample_rate;
error = ioctl(config->fd, SNDCTL_DSP_SPEED, &tmp);
check_error(error, "SNDCTL_DSP_SPEED");
error = ioctl(config->fd, SNDCTL_DSP_GETCAPS, &(config->audio_info.caps));
check_error(error, "SNDCTL_DSP_GETCAPS");
if (!(config->audio_info.caps & PCM_CAP_DUPLEX))
errx(1, "Device doesn't support full duplex!\n");
if (config->mmap) {
if (!(config->audio_info.caps & PCM_CAP_TRIGGER))
errx(1, "Device doesn't support triggering!\n");
if (!(config->audio_info.caps & PCM_CAP_MMAP))
errx(1, "Device doesn't support mmap mode!\n");
}
min_frag = size2frag(config->sample_size * config->channels);
if (config->frag < min_frag)
config->frag = min_frag;
if (config->buffer_info.fragments < 0)
config->buffer_info.fragments = 2;
tmp = ((config->buffer_info.fragments) << 16) | config->frag;
error = ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp);
check_error(error, "SNDCTL_DSP_SETFRAGMENT");
error = ioctl(config->fd, SNDCTL_DSP_GETOSPACE, &(config->buffer_info));
check_error(error, "SNDCTL_DSP_GETOSPACE");
if (config->buffer_info.bytes < 1) {
errx(1, "OSS buffer error: buffer size can not be %d\n",
config->buffer_info.bytes);
}
config->sample_count = config->buffer_info.bytes / config->sample_size;
config->chsamples = config->sample_count / config->channels;
}
int
main(int argc, char *argv[])
{
int ret, bytes;
int8_t *ibuf, *obuf;
config_t config = {
.device = "/dev/dsp",
.channels = -1,
.format = format,
.frag = -1,
.sample_rate = 48000,
.sample_size = sizeof(sample_t),
.buffer_info.fragments = -1,
.mmap = 0,
};
oss_init(&config);
bytes = config.buffer_info.bytes;
ibuf = malloc(bytes);
obuf = malloc(bytes);
sample_t *channels = malloc(bytes);
printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
"samples: %d\n",
bytes, config.buffer_info.fragments,
config.buffer_info.fragsize, config.buffer_info.fragstotal,
config.sample_count);
for (;;) {
ret = read(config.fd, ibuf, bytes);
if (ret < bytes) {
fprintf(stderr, "Requested %d bytes, but read %d!\n",
bytes, ret);
break;
}
oss_split(&config, (sample_t *)ibuf, channels);
oss_merge(&config, channels, (sample_t *)obuf);
ret = write(config.fd, obuf, bytes);
if (ret < bytes) {
fprintf(stderr, "Requested %d bytes, but wrote %d!\n",
bytes, ret);
break;
}
}
free(channels);
free(obuf);
free(ibuf);
close(config.fd);
return (0);
}