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