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/soundcard.h> 29 30 #include <err.h> 31 #include <fcntl.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 37 /* 38 * Minimal configuration for OSS. For real world applications, this structure 39 * will probably contain many more fields 40 */ 41 struct config { 42 char *device; 43 int mode; 44 int fd; 45 int format; 46 int sample_count; 47 int sample_rate; 48 int sample_size; 49 int chsamples; 50 int mmap; 51 void *buf; 52 oss_audioinfo audio_info; 53 audio_buf_info buffer_info; 54 }; 55 56 /* 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 the 59 * smallest exponent which can fit a buffer of size "x". The fragments 60 * determine in how many chunks the buffer will be sliced into, hence if the 61 * exponent is 4, and number of fragments is 2, the requested size will be 2^4 62 * * 2 = 32. Please note that the buffer size is in bytes, not samples. For 63 * example, a 24-bit sample will be represented with 3 bytes. If you're porting 64 * an audio application from Linux, you should be aware that 24-bit samples on 65 * it are represented with 4 bytes (usually int). The idea of a total buffer 66 * size that holds number of fragments is to allow application to be 67 * number_of_fragments - 1 late. That's called jitter tolerance. 68 * 69 * Official OSS development howto: 70 * http://manuals.opensound.com/developer/DSP.html 71 */ 72 static inline int 73 size2exp(int x) 74 { 75 int exp = 0; 76 77 while ((1 << exp) < x) 78 exp++; 79 80 return (exp); 81 } 82 83 static void 84 oss_init(struct config *config) 85 { 86 unsigned long request = SNDCTL_DSP_GETOSPACE; 87 int tmp = 0; 88 89 if ((config->fd = open(config->device, config->mode)) < 0) 90 err(1, "Error opening the device %s", config->device); 91 92 /* Get device information */ 93 if (ioctl(config->fd, SNDCTL_ENGINEINFO, &config->audio_info) < 0) 94 err(1, "Unable to get device info"); 95 96 /* Get device capabilities */ 97 if (ioctl(config->fd, SNDCTL_DSP_GETCAPS, &config->audio_info.caps) < 0) 98 err(1, "Unable to get capabilities"); 99 100 /* Check if device supports triggering */ 101 if (!(config->audio_info.caps & PCM_CAP_TRIGGER)) 102 errx(1, "Device doesn't support triggering!\n"); 103 104 /* Handle memory mapped mode */ 105 if (config->mmap) { 106 if (!(config->audio_info.caps & PCM_CAP_MMAP)) 107 errx(1, "Device doesn't support mmap mode!\n"); 108 tmp = 0; 109 if (ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp) < 0) 110 err(1, "Unable to set cooked mode"); 111 } 112 113 /* Set sample format */ 114 if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &config->format) < 0) 115 err(1, "Unable to set sample format"); 116 117 /* Set sample channels */ 118 if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &config->audio_info.max_channels) < 0) 119 err(1, "Unable to set channels"); 120 121 /* Set sample rate */ 122 if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0) 123 err(1, "Unable to set sample rate"); 124 125 /* Calculate sample size */ 126 switch (config->format) { 127 case AFMT_S8: 128 case AFMT_U8: 129 config->sample_size = 1; 130 break; 131 case AFMT_S16_BE: 132 case AFMT_S16_LE: 133 case AFMT_U16_BE: 134 case AFMT_U16_LE: 135 config->sample_size = 2; 136 break; 137 case AFMT_S24_BE: 138 case AFMT_S24_LE: 139 case AFMT_U24_BE: 140 case AFMT_U24_LE: 141 config->sample_size = 3; 142 break; 143 case AFMT_S32_BE: 144 case AFMT_S32_LE: 145 case AFMT_U32_BE: 146 case AFMT_U32_LE: 147 case AFMT_F32_BE: 148 case AFMT_F32_LE: 149 config->sample_size = 4; 150 break; 151 default: 152 errx(1, "Invalid audio format %d", config->format); 153 break; 154 } 155 156 /* 157 * Set fragment and sample size. This part is optional as OSS has 158 * default values. From the kernel's perspective, there are few things 159 * OSS developers should be aware of: 160 * 161 * - For each sound(4)-created channel, there is a software-facing 162 * buffer, and a hardware-facing one. 163 * - The sizes of the buffers can be listed in the console with "sndctl 164 * swbuf hwbuf". 165 * - OSS ioctls only concern software-facing buffer fragments, not 166 * hardware. 167 * 168 * For USB sound cards, the block size is set according to the 169 * hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 * 170 * 48000 = 96 samples per block. Block size should be set as multiple 171 * of 96, in this case. The OSS driver insists on reading/writing a 172 * certain number of samples at a time, one fragment full of samples. 173 * It is bound to do so at a fixed time frame, to avoid under- and 174 * overruns during communication with the hardware. 175 */ 176 config->buffer_info.fragments = 2; 177 tmp = size2exp(config->sample_size * config->audio_info.max_channels); 178 tmp = ((config->buffer_info.fragments) << 16) | tmp; 179 if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) 180 err(1, "Unable to set fragment size"); 181 182 /* Get buffer info */ 183 if ((config->mode & O_ACCMODE) == O_RDONLY) 184 request = SNDCTL_DSP_GETISPACE; 185 if (ioctl(config->fd, request, &config->buffer_info) < 0) 186 err(1, "Unable to get buffer info"); 187 if (config->buffer_info.fragments < 1) 188 config->buffer_info.fragments = config->buffer_info.fragstotal; 189 if (config->buffer_info.bytes < 1) 190 config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize; 191 if (config->buffer_info.bytes < 1) { 192 errx(1, "OSS buffer error: buffer size can not be %d\n", 193 config->buffer_info.bytes); 194 } 195 config->sample_count = config->buffer_info.bytes / config->sample_size; 196 config->chsamples = config->sample_count / config->audio_info.max_channels; 197 config->buf = malloc(config->buffer_info.bytes); 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 the trigger */ 206 switch (config->mode & O_ACCMODE) { 207 case O_RDONLY: 208 tmp = PCM_ENABLE_INPUT; 209 break; 210 case O_WRONLY: 211 tmp = PCM_ENABLE_OUTPUT; 212 break; 213 case O_RDWR: 214 tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; 215 break; 216 default: 217 errx(1, "Invalid mode %d", config->mode); 218 break; 219 } 220 if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) 221 err(1, "Failed to set trigger"); 222 } 223