Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/share/examples/sound/oss.h
96290 views
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
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
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
253