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
size2exp(int x)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
oss_init(struct config * config)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 tmp = config->format;
116 if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp) < 0)
117 err(1, "Unable to set sample format");
118 if (tmp != config->format)
119 warnx("Format: requested=%08x, got=%08x", config->format, tmp);
120 config->format = tmp;
121
122 /* Set sample channels */
123 tmp = config->audio_info.max_channels;
124 if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0)
125 err(1, "Unable to set channels");
126 if (tmp != config->audio_info.max_channels)
127 warnx("Channels: requested=%d, got=%d", config->audio_info.max_channels, tmp);
128 config->audio_info.max_channels = tmp;
129
130 /* Set sample rate */
131 tmp = config->sample_rate;
132 if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0)
133 err(1, "Unable to set sample rate");
134 if (tmp != config->sample_rate)
135 warnx("Sample rate: requested=%d, got=%d", config->sample_rate, tmp);
136 config->sample_rate = tmp;
137
138 /* Calculate sample size */
139 switch (config->format) {
140 case AFMT_S8:
141 case AFMT_U8:
142 config->sample_size = 1;
143 break;
144 case AFMT_S16_BE:
145 case AFMT_S16_LE:
146 case AFMT_U16_BE:
147 case AFMT_U16_LE:
148 config->sample_size = 2;
149 break;
150 case AFMT_S24_BE:
151 case AFMT_S24_LE:
152 case AFMT_U24_BE:
153 case AFMT_U24_LE:
154 config->sample_size = 3;
155 break;
156 case AFMT_S32_BE:
157 case AFMT_S32_LE:
158 case AFMT_U32_BE:
159 case AFMT_U32_LE:
160 case AFMT_F32_BE:
161 case AFMT_F32_LE:
162 config->sample_size = 4;
163 break;
164 default:
165 errx(1, "Invalid audio format %d", config->format);
166 break;
167 }
168
169 /*
170 * Set fragment and sample size. This part is optional as OSS has
171 * default values. From the kernel's perspective, there are few things
172 * OSS developers should be aware of:
173 *
174 * - For each sound(4)-created channel, there is a software-facing
175 * buffer, and a hardware-facing one.
176 * - The sizes of the buffers can be listed in the console with "sndctl
177 * swbuf hwbuf".
178 * - OSS ioctls only concern software-facing buffer fragments, not
179 * hardware.
180 *
181 * For USB sound cards, the block size is set according to the
182 * hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 *
183 * 48000 = 96 samples per block. Block size should be set as multiple
184 * of 96, in this case. The OSS driver insists on reading/writing a
185 * certain number of samples at a time, one fragment full of samples.
186 * It is bound to do so at a fixed time frame, to avoid under- and
187 * overruns during communication with the hardware.
188 */
189 config->buffer_info.fragments = 2;
190 tmp = size2exp(config->sample_size * config->audio_info.max_channels);
191 tmp = ((config->buffer_info.fragments) << 16) | tmp;
192 if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0)
193 err(1, "Unable to set fragment size");
194
195 /* Get buffer info */
196 if ((config->mode & O_ACCMODE) == O_RDONLY)
197 request = SNDCTL_DSP_GETISPACE;
198 if (ioctl(config->fd, request, &config->buffer_info) < 0)
199 err(1, "Unable to get buffer info");
200 if (config->buffer_info.fragments < 1)
201 config->buffer_info.fragments = config->buffer_info.fragstotal;
202 if (config->buffer_info.bytes < 1)
203 config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize;
204 if (config->buffer_info.bytes < 1) {
205 errx(1, "OSS buffer error: buffer size can not be %d\n",
206 config->buffer_info.bytes);
207 }
208 config->sample_count = config->buffer_info.bytes / config->sample_size;
209 config->chsamples = config->sample_count / config->audio_info.max_channels;
210
211 printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
212 "samples: %d, channels: %d, sample size: %d, sample rate: %d, "
213 "format: %08x\n",
214 config->buffer_info.bytes, config->buffer_info.fragments,
215 config->buffer_info.fragsize, config->buffer_info.fragstotal,
216 config->sample_count, config->audio_info.max_channels,
217 config->sample_size, config->sample_rate, config->format);
218
219 /* Set trigger direction and mmap protection */
220 switch (config->mode & O_ACCMODE) {
221 case O_RDONLY:
222 tmp = PCM_ENABLE_INPUT;
223 prot = PROT_READ;
224 break;
225 case O_WRONLY:
226 tmp = PCM_ENABLE_OUTPUT;
227 prot = PROT_WRITE;
228 break;
229 case O_RDWR:
230 tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
231 prot = PROT_READ | PROT_WRITE;
232 break;
233 default:
234 errx(1, "Invalid mode %d", config->mode);
235 break;
236 }
237
238 /* Map or allocate the buffer */
239 if (config->mmap) {
240 config->buf = mmap(NULL, config->buffer_info.bytes, prot, MAP_SHARED, config->fd, 0);
241 if (config->buf == MAP_FAILED)
242 err(1, "Memory map failed");
243 } else {
244 if ((config->buf = malloc(config->buffer_info.bytes)) == NULL)
245 err(1, "Allocating buffer failed");
246 }
247
248 /* Set the trigger */
249 if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0)
250 err(1, "Failed to set trigger");
251 }
252