/*1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2025 Goran Mekić4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/mman.h>28#include <sys/soundcard.h>2930#include <err.h>31#include <fcntl.h>32#include <stdio.h>33#include <stdlib.h>34#include <string.h>35#include <unistd.h>3637/*38* Minimal configuration for OSS. For real world applications, this structure39* will probably contain many more fields40*/41struct config {42char *device;43int mode;44int fd;45int format;46int sample_count;47int sample_rate;48int sample_size;49int chsamples;50int mmap;51void *buf;52oss_audioinfo audio_info;53audio_buf_info buffer_info;54};5556/*57* The buffer size used by OSS is (2 ^ exponent) * number_of_fragments.58* Exponent values range between 4 and 16, so this function looks for the59* smallest exponent which can fit a buffer of size "x". The fragments60* determine in how many chunks the buffer will be sliced into, hence if the61* exponent is 4, and number of fragments is 2, the requested size will be 2^462* * 2 = 32. Please note that the buffer size is in bytes, not samples. For63* example, a 24-bit sample will be represented with 3 bytes. If you're porting64* an audio application from Linux, you should be aware that 24-bit samples on65* it are represented with 4 bytes (usually int). The idea of a total buffer66* size that holds number of fragments is to allow application to be67* number_of_fragments - 1 late. That's called jitter tolerance.68*69* Official OSS development howto:70* http://manuals.opensound.com/developer/DSP.html71*/72static inline int73size2exp(int x)74{75int exp = 0;7677while ((1 << exp) < x)78exp++;7980return (exp);81}8283static void84oss_init(struct config *config)85{86unsigned long request = SNDCTL_DSP_GETOSPACE;87int tmp = 0, prot = 0;8889if ((config->fd = open(config->device, config->mode)) < 0)90err(1, "Error opening the device %s", config->device);9192/* Get device information */93if (ioctl(config->fd, SNDCTL_ENGINEINFO, &config->audio_info) < 0)94err(1, "Unable to get device info");9596/* Get device capabilities */97if (ioctl(config->fd, SNDCTL_DSP_GETCAPS, &config->audio_info.caps) < 0)98err(1, "Unable to get capabilities");99100/* Check if device supports triggering */101if (!(config->audio_info.caps & PCM_CAP_TRIGGER))102errx(1, "Device doesn't support triggering!\n");103104/* Handle memory mapped mode */105if (config->mmap) {106if (!(config->audio_info.caps & PCM_CAP_MMAP))107errx(1, "Device doesn't support mmap mode!\n");108tmp = 0;109if (ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp) < 0)110err(1, "Unable to set cooked mode");111}112113/* Set sample format */114tmp = config->format;115if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp) < 0)116err(1, "Unable to set sample format");117if (tmp != config->format)118warnx("Format: requested=%08x, got=%08x", config->format, tmp);119config->format = tmp;120121/* Set sample channels */122tmp = config->audio_info.max_channels;123if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0)124err(1, "Unable to set channels");125if (tmp != config->audio_info.max_channels)126warnx("Channels: requested=%d, got=%d", config->audio_info.max_channels, tmp);127config->audio_info.max_channels = tmp;128129/* Set sample rate */130tmp = config->sample_rate;131if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0)132err(1, "Unable to set sample rate");133if (tmp != config->sample_rate)134warnx("Sample rate: requested=%d, got=%d", config->sample_rate, tmp);135config->sample_rate = tmp;136137/* Calculate sample size */138switch (config->format) {139case AFMT_S8:140case AFMT_U8:141config->sample_size = 1;142break;143case AFMT_S16_BE:144case AFMT_S16_LE:145case AFMT_U16_BE:146case AFMT_U16_LE:147config->sample_size = 2;148break;149case AFMT_S24_BE:150case AFMT_S24_LE:151case AFMT_U24_BE:152case AFMT_U24_LE:153config->sample_size = 3;154break;155case AFMT_S32_BE:156case AFMT_S32_LE:157case AFMT_U32_BE:158case AFMT_U32_LE:159case AFMT_F32_BE:160case AFMT_F32_LE:161config->sample_size = 4;162break;163default:164errx(1, "Invalid audio format %d", config->format);165break;166}167168/*169* Set fragment and sample size. This part is optional as OSS has170* default values. From the kernel's perspective, there are few things171* OSS developers should be aware of:172*173* - For each sound(4)-created channel, there is a software-facing174* buffer, and a hardware-facing one.175* - The sizes of the buffers can be listed in the console with "sndctl176* swbuf hwbuf".177* - OSS ioctls only concern software-facing buffer fragments, not178* hardware.179*180* For USB sound cards, the block size is set according to the181* hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 *182* 48000 = 96 samples per block. Block size should be set as multiple183* of 96, in this case. The OSS driver insists on reading/writing a184* certain number of samples at a time, one fragment full of samples.185* It is bound to do so at a fixed time frame, to avoid under- and186* overruns during communication with the hardware.187*/188config->buffer_info.fragments = 2;189tmp = size2exp(config->sample_size * config->audio_info.max_channels);190tmp = ((config->buffer_info.fragments) << 16) | tmp;191if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0)192err(1, "Unable to set fragment size");193194/* Get buffer info */195if ((config->mode & O_ACCMODE) == O_RDONLY)196request = SNDCTL_DSP_GETISPACE;197if (ioctl(config->fd, request, &config->buffer_info) < 0)198err(1, "Unable to get buffer info");199if (config->buffer_info.fragments < 1)200config->buffer_info.fragments = config->buffer_info.fragstotal;201if (config->buffer_info.bytes < 1)202config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize;203if (config->buffer_info.bytes < 1) {204errx(1, "OSS buffer error: buffer size can not be %d\n",205config->buffer_info.bytes);206}207config->sample_count = config->buffer_info.bytes / config->sample_size;208config->chsamples = config->sample_count / config->audio_info.max_channels;209210printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "211"samples: %d, channels: %d, sample size: %d, sample rate: %d, "212"format: %08x\n",213config->buffer_info.bytes, config->buffer_info.fragments,214config->buffer_info.fragsize, config->buffer_info.fragstotal,215config->sample_count, config->audio_info.max_channels,216config->sample_size, config->sample_rate, config->format);217218/* Set trigger direction and mmap protection */219switch (config->mode & O_ACCMODE) {220case O_RDONLY:221tmp = PCM_ENABLE_INPUT;222prot = PROT_READ;223break;224case O_WRONLY:225tmp = PCM_ENABLE_OUTPUT;226prot = PROT_WRITE;227break;228case O_RDWR:229tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;230prot = PROT_READ | PROT_WRITE;231break;232default:233errx(1, "Invalid mode %d", config->mode);234break;235}236237/* Map or allocate the buffer */238if (config->mmap) {239config->buf = mmap(NULL, config->buffer_info.bytes, prot, MAP_SHARED, config->fd, 0);240if (config->buf == MAP_FAILED)241err(1, "Memory map failed");242} else {243if ((config->buf = malloc(config->buffer_info.bytes)) == NULL)244err(1, "Allocating buffer failed");245}246247/* Set the trigger */248if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0)249err(1, "Failed to set trigger");250}251252253