1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 Goran Mekić 5 * Copyright (c) 2024 The FreeBSD Foundation 6 * 7 * Portions of this software were developed by Christos Margiolis 8 * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/soundcard.h> 33 34 #include <err.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #ifndef SAMPLE_SIZE 43 #define SAMPLE_SIZE 16 44 #endif 45 46 /* Format can be unsigned, in which case replace S with U */ 47 #if SAMPLE_SIZE == 32 48 typedef int32_t sample_t; 49 int format = AFMT_S32_NE; /* Signed 32bit native endian format */ 50 #elif SAMPLE_SIZE == 16 51 typedef int16_t sample_t; 52 int format = AFMT_S16_NE; /* Signed 16bit native endian format */ 53 #elif SAMPLE_SIZE == 8 54 typedef int8_t sample_t; 55 int format = AFMT_S8_NE; /* Signed 8bit native endian format */ 56 #else 57 #error Unsupported sample format! 58 typedef int32_t sample_t; 59 int format = AFMT_S32_NE; /* Not a real value, just silencing 60 * compiler errors */ 61 #endif 62 63 /* 64 * Minimal configuration for OSS 65 * For real world applications, this structure will probably contain many 66 * more fields 67 */ 68 typedef struct config { 69 char *device; 70 int channels; 71 int fd; 72 int format; 73 int frag; 74 int sample_count; 75 int sample_rate; 76 int sample_size; 77 int chsamples; 78 int mmap; 79 oss_audioinfo audio_info; 80 audio_buf_info buffer_info; 81 } config_t; 82 83 /* 84 * Error state is indicated by value=-1 in which case application exits with 85 * error 86 */ 87 static inline void 88 check_error(const int value, const char *message) 89 { 90 if (value == -1) 91 err(1, "OSS error: %s\n", message); 92 } 93 94 95 /* Calculate frag by giving it minimal size of buffer */ 96 static inline int 97 size2frag(int x) 98 { 99 int frag = 0; 100 101 while ((1 << frag) < x) 102 ++frag; 103 104 return (frag); 105 } 106 107 /* 108 * Split input buffer into channels. Input buffer is in interleaved format 109 * which means if we have 2 channels (L and R), this is what the buffer of 8 110 * samples would contain: L,R,L,R,L,R,L,R. The result are two channels 111 * containing: L,L,L,L and R,R,R,R. 112 */ 113 static void 114 oss_split(config_t *config, sample_t *input, sample_t *output) 115 { 116 int channel, index, i; 117 118 for (i = 0; i < config->sample_count; ++i) { 119 channel = i % config->channels; 120 index = i / config->channels; 121 output[channel * index] = input[i]; 122 } 123 } 124 125 /* 126 * Convert channels into interleaved format and place it in output 127 * buffer 128 */ 129 static void 130 oss_merge(config_t *config, sample_t *input, sample_t *output) 131 { 132 int channel, index; 133 134 for (channel = 0; channel < config->channels; ++channel) { 135 for (index = 0; index < config->chsamples; ++index) { 136 output[index * config->channels + channel] = 137 input[channel * index]; 138 } 139 } 140 } 141 142 static void 143 oss_init(config_t *config) 144 { 145 int error, tmp, min_frag; 146 147 /* Open the device for read and write */ 148 config->fd = open(config->device, O_RDWR); 149 check_error(config->fd, "open"); 150 151 /* Get device information */ 152 config->audio_info.dev = -1; 153 error = ioctl(config->fd, SNDCTL_ENGINEINFO, &(config->audio_info)); 154 check_error(error, "SNDCTL_ENGINEINFO"); 155 printf("min_channels: %d\n", config->audio_info.min_channels); 156 printf("max_channels: %d\n", config->audio_info.max_channels); 157 printf("latency: %d\n", config->audio_info.latency); 158 printf("handle: %s\n", config->audio_info.handle); 159 if (config->audio_info.min_rate > config->sample_rate || 160 config->sample_rate > config->audio_info.max_rate) { 161 errx(1, "%s doesn't support chosen samplerate of %dHz!\n", 162 config->device, config->sample_rate); 163 } 164 if (config->channels < 1) 165 config->channels = config->audio_info.max_channels; 166 167 /* 168 * If device is going to be used in mmap mode, disable all format 169 * conversions. Official OSS documentation states error code should not 170 * be checked. 171 * http://manuals.opensound.com/developer/mmap_test.c.html#LOC10 172 */ 173 if (config->mmap) { 174 tmp = 0; 175 ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp); 176 } 177 178 /* 179 * Set number of channels. If number of channels is chosen to the value 180 * near the one wanted, save it in config 181 */ 182 tmp = config->channels; 183 error = ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp); 184 check_error(error, "SNDCTL_DSP_CHANNELS"); 185 /* Or check if tmp is close enough? */ 186 if (tmp != config->channels) { 187 errx(1, "%s doesn't support chosen channel count of %d set " 188 "to %d!\n", config->device, config->channels, tmp); 189 } 190 config->channels = tmp; 191 192 /* Set format, or bit size: 8, 16, 24 or 32 bit sample */ 193 tmp = config->format; 194 error = ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp); 195 check_error(error, "SNDCTL_DSP_SETFMT"); 196 if (tmp != config->format) { 197 errx(1, "%s doesn't support chosen sample format!\n", 198 config->device); 199 } 200 201 /* Most common values for samplerate (in kHz): 44.1, 48, 88.2, 96 */ 202 tmp = config->sample_rate; 203 error = ioctl(config->fd, SNDCTL_DSP_SPEED, &tmp); 204 check_error(error, "SNDCTL_DSP_SPEED"); 205 206 /* Get and check device capabilities */ 207 error = ioctl(config->fd, SNDCTL_DSP_GETCAPS, &(config->audio_info.caps)); 208 check_error(error, "SNDCTL_DSP_GETCAPS"); 209 if (!(config->audio_info.caps & PCM_CAP_DUPLEX)) 210 errx(1, "Device doesn't support full duplex!\n"); 211 212 if (config->mmap) { 213 if (!(config->audio_info.caps & PCM_CAP_TRIGGER)) 214 errx(1, "Device doesn't support triggering!\n"); 215 if (!(config->audio_info.caps & PCM_CAP_MMAP)) 216 errx(1, "Device doesn't support mmap mode!\n"); 217 } 218 219 /* 220 * If desired frag is smaller than minimum, based on number of channels 221 * and format (size in bits: 8, 16, 24, 32), set that as frag. Buffer 222 * size is 2^frag, but the real size of the buffer will be read when 223 * the configuration of the device is successful 224 */ 225 min_frag = size2frag(config->sample_size * config->channels); 226 227 if (config->frag < min_frag) 228 config->frag = min_frag; 229 230 /* 231 * Allocate buffer in fragments. Total buffer will be split in number 232 * of fragments (2 by default) 233 */ 234 if (config->buffer_info.fragments < 0) 235 config->buffer_info.fragments = 2; 236 tmp = ((config->buffer_info.fragments) << 16) | config->frag; 237 error = ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp); 238 check_error(error, "SNDCTL_DSP_SETFRAGMENT"); 239 240 /* When all is set and ready to go, get the size of buffer */ 241 error = ioctl(config->fd, SNDCTL_DSP_GETOSPACE, &(config->buffer_info)); 242 check_error(error, "SNDCTL_DSP_GETOSPACE"); 243 if (config->buffer_info.bytes < 1) { 244 errx(1, "OSS buffer error: buffer size can not be %d\n", 245 config->buffer_info.bytes); 246 } 247 config->sample_count = config->buffer_info.bytes / config->sample_size; 248 config->chsamples = config->sample_count / config->channels; 249 } 250 251 int 252 main(int argc, char *argv[]) 253 { 254 int ret, bytes; 255 int8_t *ibuf, *obuf; 256 config_t config = { 257 .device = "/dev/dsp", 258 .channels = -1, 259 .format = format, 260 .frag = -1, 261 .sample_rate = 48000, 262 .sample_size = sizeof(sample_t), 263 .buffer_info.fragments = -1, 264 .mmap = 0, 265 }; 266 267 /* Initialize device */ 268 oss_init(&config); 269 270 /* 271 * Allocate input and output buffers so that their size match frag_size 272 */ 273 bytes = config.buffer_info.bytes; 274 ibuf = malloc(bytes); 275 obuf = malloc(bytes); 276 sample_t *channels = malloc(bytes); 277 278 printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, " 279 "samples: %d\n", 280 bytes, config.buffer_info.fragments, 281 config.buffer_info.fragsize, config.buffer_info.fragstotal, 282 config.sample_count); 283 284 /* Minimal engine: read input and copy it to the output */ 285 for (;;) { 286 ret = read(config.fd, ibuf, bytes); 287 if (ret < bytes) { 288 fprintf(stderr, "Requested %d bytes, but read %d!\n", 289 bytes, ret); 290 break; 291 } 292 oss_split(&config, (sample_t *)ibuf, channels); 293 /* All processing will happen here */ 294 oss_merge(&config, channels, (sample_t *)obuf); 295 ret = write(config.fd, obuf, bytes); 296 if (ret < bytes) { 297 fprintf(stderr, "Requested %d bytes, but wrote %d!\n", 298 bytes, ret); 299 break; 300 } 301 } 302 303 /* Cleanup */ 304 free(channels); 305 free(obuf); 306 free(ibuf); 307 close(config.fd); 308 309 return (0); 310 } 311