xref: /freebsd/share/examples/sound/oss/audio.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
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  * <christos@FreeBSD.org> 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