1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 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 #include <errno.h> 30 #include <fcntl.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 37 #ifndef SAMPLE_SIZE 38 #define SAMPLE_SIZE 16 39 #endif 40 41 /* Format can be unsigned, in which case replace S with U */ 42 #if SAMPLE_SIZE == 32 43 typedef int32_t sample_t; 44 int format = AFMT_S32_NE; /* Signed 32bit native endian format */ 45 #elif SAMPLE_SIZE == 16 46 typedef int16_t sample_t; 47 int format = AFMT_S16_NE; /* Signed 16bit native endian format */ 48 #elif SAMPLE_SIZE == 8 49 typedef int8_t sample_t; 50 int format = AFMT_S8_NE; /* Signed 8bit native endian format */ 51 #else 52 #error Unsupported sample format! 53 typedef int32_t sample_t; 54 int format = AFMT_S32_NE; /* Not a real value, just silencing 55 * compiler errors */ 56 #endif 57 58 59 60 /* 61 * Minimal configuration for OSS 62 * For real world applications, this structure will probably contain many 63 * more fields 64 */ 65 typedef struct config { 66 char *device; 67 int channels; 68 int fd; 69 int format; 70 int frag; 71 int sample_count; 72 int sample_rate; 73 int sample_size; 74 int chsamples; 75 int mmap; 76 oss_audioinfo audio_info; 77 audio_buf_info buffer_info; 78 } config_t; 79 80 81 /* 82 * Error state is indicated by value=-1 in which case application exits 83 * with error 84 */ 85 static inline void 86 check_error(const int value, const char *message) 87 { 88 if (value == -1) { 89 fprintf(stderr, "OSS error: %s %s\n", message, strerror(errno)); 90 exit(1); 91 } 92 } 93 94 95 96 /* Calculate frag by giving it minimal size of buffer */ 97 static inline int 98 size2frag(int x) 99 { 100 int frag = 0; 101 102 while ((1 << frag) < x) { 103 ++frag; 104 } 105 return frag; 106 } 107 108 109 /* 110 * Split input buffer into channels. Input buffer is in interleaved format 111 * which means if we have 2 channels (L and R), this is what the buffer of 112 * 8 samples would contain: L,R,L,R,L,R,L,R. The result are two channels 113 * containing: L,L,L,L and R,R,R,R. 114 */ 115 void 116 oss_split(config_t *config, sample_t *input, sample_t *output) 117 { 118 int channel; 119 int index; 120 121 for (int i = 0; i < config->sample_count; ++i) { 122 channel = i % config->channels; 123 index = i / config->channels; 124 output[channel * index] = input[i]; 125 } 126 } 127 128 129 /* 130 * Convert channels into interleaved format and place it in output 131 * buffer 132 */ 133 void 134 oss_merge(config_t *config, sample_t *input, sample_t *output) 135 { 136 for (int channel = 0; channel < config->channels; ++channel) { 137 for (int index = 0; index < config->chsamples; ++index) { 138 output[index * config->channels + channel] = input[channel * index]; 139 } 140 } 141 } 142 143 void 144 oss_init(config_t *config) 145 { 146 int error; 147 int tmp; 148 149 /* Open the device for read and write */ 150 config->fd = open(config->device, O_RDWR); 151 check_error(config->fd, "open"); 152 153 /* Get device information */ 154 config->audio_info.dev = -1; 155 error = ioctl(config->fd, SNDCTL_ENGINEINFO, &(config->audio_info)); 156 check_error(error, "SNDCTL_ENGINEINFO"); 157 printf("min_channels: %d\n", config->audio_info.min_channels); 158 printf("max_channels: %d\n", config->audio_info.max_channels); 159 printf("latency: %d\n", config->audio_info.latency); 160 printf("handle: %s\n", config->audio_info.handle); 161 if (config->audio_info.min_rate > config->sample_rate || config->sample_rate > config->audio_info.max_rate) { 162 fprintf(stderr, "%s doesn't support chosen ", config->device); 163 fprintf(stderr, "samplerate of %dHz!\n", config->sample_rate); 164 exit(1); 165 } 166 if (config->channels < 1) { 167 config->channels = config->audio_info.max_channels; 168 } 169 170 /* 171 * If device is going to be used in mmap mode, disable all format 172 * conversions. Official OSS documentation states error code should not be 173 * checked. http://manuals.opensound.com/developer/mmap_test.c.html#LOC10 174 */ 175 if (config->mmap) { 176 tmp = 0; 177 ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp); 178 } 179 180 /* 181 * Set number of channels. If number of channels is chosen to the value 182 * near the one wanted, save it in config 183 */ 184 tmp = config->channels; 185 error = ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp); 186 check_error(error, "SNDCTL_DSP_CHANNELS"); 187 if (tmp != config->channels) { /* or check if tmp is close enough? */ 188 fprintf(stderr, "%s doesn't support chosen ", config->device); 189 fprintf(stderr, "channel count of %d", config->channels); 190 fprintf(stderr, ", set to %d!\n", tmp); 191 } 192 config->channels = tmp; 193 194 /* Set format, or bit size: 8, 16, 24 or 32 bit sample */ 195 tmp = config->format; 196 error = ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp); 197 check_error(error, "SNDCTL_DSP_SETFMT"); 198 if (tmp != config->format) { 199 fprintf(stderr, "%s doesn't support chosen sample format!\n", config->device); 200 exit(1); 201 } 202 203 /* Most common values for samplerate (in kHz): 44.1, 48, 88.2, 96 */ 204 tmp = config->sample_rate; 205 error = ioctl(config->fd, SNDCTL_DSP_SPEED, &tmp); 206 check_error(error, "SNDCTL_DSP_SPEED"); 207 208 /* Get and check device capabilities */ 209 error = ioctl(config->fd, SNDCTL_DSP_GETCAPS, &(config->audio_info.caps)); 210 check_error(error, "SNDCTL_DSP_GETCAPS"); 211 if (!(config->audio_info.caps & PCM_CAP_DUPLEX)) { 212 fprintf(stderr, "Device doesn't support full duplex!\n"); 213 exit(1); 214 } 215 if (config->mmap) { 216 if (!(config->audio_info.caps & PCM_CAP_TRIGGER)) { 217 fprintf(stderr, "Device doesn't support triggering!\n"); 218 exit(1); 219 } 220 if (!(config->audio_info.caps & PCM_CAP_MMAP)) { 221 fprintf(stderr, "Device doesn't support mmap mode!\n"); 222 exit(1); 223 } 224 } 225 226 /* 227 * If desired frag is smaller than minimum, based on number of channels 228 * and format (size in bits: 8, 16, 24, 32), set that as frag. Buffer size 229 * is 2^frag, but the real size of the buffer will be read when the 230 * configuration of the device is successfull 231 */ 232 int min_frag = size2frag(config->sample_size * config->channels); 233 234 if (config->frag < min_frag) { 235 config->frag = min_frag; 236 } 237 238 /* 239 * Allocate buffer in fragments. Total buffer will be split in number 240 * of fragments (2 by default) 241 */ 242 if (config->buffer_info.fragments < 0) { 243 config->buffer_info.fragments = 2; 244 } 245 tmp = ((config->buffer_info.fragments) << 16) | config->frag; 246 error = ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp); 247 check_error(error, "SNDCTL_DSP_SETFRAGMENT"); 248 249 /* When all is set and ready to go, get the size of buffer */ 250 error = ioctl(config->fd, SNDCTL_DSP_GETOSPACE, &(config->buffer_info)); 251 check_error(error, "SNDCTL_DSP_GETOSPACE"); 252 if (config->buffer_info.bytes < 1) { 253 fprintf( 254 stderr, 255 "OSS buffer error: buffer size can not be %d\n", 256 config->buffer_info.bytes 257 ); 258 exit(1); 259 } 260 config->sample_count = config->buffer_info.bytes / config->sample_size; 261 config->chsamples = config->sample_count / config->channels; 262 } 263