Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/share/examples/sound/oss/audio.c
39556 views
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
* <[email protected]> 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
312