1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 Goran Mekić 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/mman.h> 29 #include <sys/soundcard.h> 30 31 #include <err.h> 32 #include <fcntl.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 38 /* 39 * Minimal configuration for OSS. For real world applications, this structure 40 * will probably contain many more fields 41 */ 42 struct config { 43 char *device; 44 int mode; 45 int fd; 46 int format; 47 int sample_count; 48 int sample_rate; 49 int sample_size; 50 int chsamples; 51 int mmap; 52 void *buf; 53 oss_audioinfo audio_info; 54 audio_buf_info buffer_info; 55 }; 56 57 /* 58 * The buffer size used by OSS is (2 ^ exponent) * number_of_fragments. 59 * Exponent values range between 4 and 16, so this function looks for the 60 * smallest exponent which can fit a buffer of size "x". The fragments 61 * determine in how many chunks the buffer will be sliced into, hence if the 62 * exponent is 4, and number of fragments is 2, the requested size will be 2^4 63 * * 2 = 32. Please note that the buffer size is in bytes, not samples. For 64 * example, a 24-bit sample will be represented with 3 bytes. If you're porting 65 * an audio application from Linux, you should be aware that 24-bit samples on 66 * it are represented with 4 bytes (usually int). The idea of a total buffer 67 * size that holds number of fragments is to allow application to be 68 * number_of_fragments - 1 late. That's called jitter tolerance. 69 * 70 * Official OSS development howto: 71 * http://manuals.opensound.com/developer/DSP.html 72 */ 73 static inline int 74 size2exp(int x) 75 { 76 int exp = 0; 77 78 while ((1 << exp) < x) 79 exp++; 80 81 return (exp); 82 } 83 84 static void 85 oss_init(struct config *config) 86 { 87 unsigned long request = SNDCTL_DSP_GETOSPACE; 88 int tmp = 0, prot = 0; 89 90 if ((config->fd = open(config->device, config->mode)) < 0) 91 err(1, "Error opening the device %s", config->device); 92 93 /* Get device information */ 94 if (ioctl(config->fd, SNDCTL_ENGINEINFO, &config->audio_info) < 0) 95 err(1, "Unable to get device info"); 96 97 /* Get device capabilities */ 98 if (ioctl(config->fd, SNDCTL_DSP_GETCAPS, &config->audio_info.caps) < 0) 99 err(1, "Unable to get capabilities"); 100 101 /* Check if device supports triggering */ 102 if (!(config->audio_info.caps & PCM_CAP_TRIGGER)) 103 errx(1, "Device doesn't support triggering!\n"); 104 105 /* Handle memory mapped mode */ 106 if (config->mmap) { 107 if (!(config->audio_info.caps & PCM_CAP_MMAP)) 108 errx(1, "Device doesn't support mmap mode!\n"); 109 tmp = 0; 110 if (ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp) < 0) 111 err(1, "Unable to set cooked mode"); 112 } 113 114 /* Set sample format */ 115 if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &config->format) < 0) 116 err(1, "Unable to set sample format"); 117 118 /* Set sample channels */ 119 if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &config->audio_info.max_channels) < 0) 120 err(1, "Unable to set channels"); 121 122 /* Set sample rate */ 123 if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0) 124 err(1, "Unable to set sample rate"); 125 126 /* Calculate sample size */ 127 switch (config->format) { 128 case AFMT_S8: 129 case AFMT_U8: 130 config->sample_size = 1; 131 break; 132 case AFMT_S16_BE: 133 case AFMT_S16_LE: 134 case AFMT_U16_BE: 135 case AFMT_U16_LE: 136 config->sample_size = 2; 137 break; 138 case AFMT_S24_BE: 139 case AFMT_S24_LE: 140 case AFMT_U24_BE: 141 case AFMT_U24_LE: 142 config->sample_size = 3; 143 break; 144 case AFMT_S32_BE: 145 case AFMT_S32_LE: 146 case AFMT_U32_BE: 147 case AFMT_U32_LE: 148 case AFMT_F32_BE: 149 case AFMT_F32_LE: 150 config->sample_size = 4; 151 break; 152 default: 153 errx(1, "Invalid audio format %d", config->format); 154 break; 155 } 156 157 /* 158 * Set fragment and sample size. This part is optional as OSS has 159 * default values. From the kernel's perspective, there are few things 160 * OSS developers should be aware of: 161 * 162 * - For each sound(4)-created channel, there is a software-facing 163 * buffer, and a hardware-facing one. 164 * - The sizes of the buffers can be listed in the console with "sndctl 165 * swbuf hwbuf". 166 * - OSS ioctls only concern software-facing buffer fragments, not 167 * hardware. 168 * 169 * For USB sound cards, the block size is set according to the 170 * hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 * 171 * 48000 = 96 samples per block. Block size should be set as multiple 172 * of 96, in this case. The OSS driver insists on reading/writing a 173 * certain number of samples at a time, one fragment full of samples. 174 * It is bound to do so at a fixed time frame, to avoid under- and 175 * overruns during communication with the hardware. 176 */ 177 config->buffer_info.fragments = 2; 178 tmp = size2exp(config->sample_size * config->audio_info.max_channels); 179 tmp = ((config->buffer_info.fragments) << 16) | tmp; 180 if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) 181 err(1, "Unable to set fragment size"); 182 183 /* Get buffer info */ 184 if ((config->mode & O_ACCMODE) == O_RDONLY) 185 request = SNDCTL_DSP_GETISPACE; 186 if (ioctl(config->fd, request, &config->buffer_info) < 0) 187 err(1, "Unable to get buffer info"); 188 if (config->buffer_info.fragments < 1) 189 config->buffer_info.fragments = config->buffer_info.fragstotal; 190 if (config->buffer_info.bytes < 1) 191 config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize; 192 if (config->buffer_info.bytes < 1) { 193 errx(1, "OSS buffer error: buffer size can not be %d\n", 194 config->buffer_info.bytes); 195 } 196 config->sample_count = config->buffer_info.bytes / config->sample_size; 197 config->chsamples = config->sample_count / config->audio_info.max_channels; 198 199 printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, " 200 "samples: %d\n", 201 config->buffer_info.bytes, config->buffer_info.fragments, 202 config->buffer_info.fragsize, config->buffer_info.fragstotal, 203 config->sample_count); 204 205 /* Set trigger direction and mmap protection */ 206 switch (config->mode & O_ACCMODE) { 207 case O_RDONLY: 208 tmp = PCM_ENABLE_INPUT; 209 prot = PROT_READ; 210 break; 211 case O_WRONLY: 212 tmp = PCM_ENABLE_OUTPUT; 213 prot = PROT_WRITE; 214 break; 215 case O_RDWR: 216 tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; 217 prot = PROT_READ | PROT_WRITE; 218 break; 219 default: 220 errx(1, "Invalid mode %d", config->mode); 221 break; 222 } 223 224 /* Map or allocate the buffer */ 225 if (config->mmap) { 226 config->buf = mmap(NULL, config->buffer_info.bytes, prot, MAP_SHARED, config->fd, 0); 227 if (config->buf == MAP_FAILED) 228 err(1, "Memory map failed"); 229 } else { 230 if ((config->buf = malloc(config->buffer_info.bytes)) == NULL) 231 err(1, "Allocating buffer failed"); 232 } 233 234 /* Set the trigger */ 235 if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) 236 err(1, "Failed to set trigger"); 237 } 238