xref: /freebsd/share/examples/sound/oss/audio.c (revision 3decd659a7887e83c2c8af35053301dc7d6f7be2)
1*3decd659SChristos Margiolis /*
2*3decd659SChristos Margiolis  * SPDX-License-Identifier: BSD-2-Clause
3*3decd659SChristos Margiolis  *
4*3decd659SChristos Margiolis  * Copyright (c) 2021 Goran Mekić
5*3decd659SChristos Margiolis  * Copyright (c) 2024 The FreeBSD Foundation
6*3decd659SChristos Margiolis  *
7*3decd659SChristos Margiolis  * Portions of this software were developed by Christos Margiolis
8*3decd659SChristos Margiolis  * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
9*3decd659SChristos Margiolis  *
10*3decd659SChristos Margiolis  * Redistribution and use in source and binary forms, with or without
11*3decd659SChristos Margiolis  * modification, are permitted provided that the following conditions
12*3decd659SChristos Margiolis  * are met:
13*3decd659SChristos Margiolis  * 1. Redistributions of source code must retain the above copyright
14*3decd659SChristos Margiolis  *    notice, this list of conditions and the following disclaimer.
15*3decd659SChristos Margiolis  * 2. Redistributions in binary form must reproduce the above copyright
16*3decd659SChristos Margiolis  *    notice, this list of conditions and the following disclaimer in the
17*3decd659SChristos Margiolis  *    documentation and/or other materials provided with the distribution.
18*3decd659SChristos Margiolis  *
19*3decd659SChristos Margiolis  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20*3decd659SChristos Margiolis  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21*3decd659SChristos Margiolis  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22*3decd659SChristos Margiolis  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23*3decd659SChristos Margiolis  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24*3decd659SChristos Margiolis  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25*3decd659SChristos Margiolis  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26*3decd659SChristos Margiolis  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27*3decd659SChristos Margiolis  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28*3decd659SChristos Margiolis  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29*3decd659SChristos Margiolis  * SUCH DAMAGE.
30*3decd659SChristos Margiolis  */
31*3decd659SChristos Margiolis 
32*3decd659SChristos Margiolis #include <sys/soundcard.h>
33*3decd659SChristos Margiolis 
34*3decd659SChristos Margiolis #include <err.h>
35*3decd659SChristos Margiolis #include <errno.h>
36*3decd659SChristos Margiolis #include <fcntl.h>
37*3decd659SChristos Margiolis #include <stdio.h>
38*3decd659SChristos Margiolis #include <stdlib.h>
39*3decd659SChristos Margiolis #include <string.h>
40*3decd659SChristos Margiolis #include <unistd.h>
41*3decd659SChristos Margiolis 
42*3decd659SChristos Margiolis #ifndef SAMPLE_SIZE
43*3decd659SChristos Margiolis #define SAMPLE_SIZE 16
44*3decd659SChristos Margiolis #endif
45*3decd659SChristos Margiolis 
46*3decd659SChristos Margiolis /* Format can be unsigned, in which case replace S with U */
47*3decd659SChristos Margiolis #if SAMPLE_SIZE == 32
48*3decd659SChristos Margiolis typedef int32_t sample_t;
49*3decd659SChristos Margiolis int	format = AFMT_S32_NE;		/* Signed 32bit native endian format */
50*3decd659SChristos Margiolis #elif SAMPLE_SIZE == 16
51*3decd659SChristos Margiolis typedef int16_t sample_t;
52*3decd659SChristos Margiolis int	format = AFMT_S16_NE;		/* Signed 16bit native endian format */
53*3decd659SChristos Margiolis #elif SAMPLE_SIZE == 8
54*3decd659SChristos Margiolis typedef int8_t sample_t;
55*3decd659SChristos Margiolis int	format = AFMT_S8_NE;		/* Signed 8bit native endian format */
56*3decd659SChristos Margiolis #else
57*3decd659SChristos Margiolis #error Unsupported sample format!
58*3decd659SChristos Margiolis typedef int32_t sample_t;
59*3decd659SChristos Margiolis int	format = AFMT_S32_NE;		/* Not a real value, just silencing
60*3decd659SChristos Margiolis 					 * compiler errors */
61*3decd659SChristos Margiolis #endif
62*3decd659SChristos Margiolis 
63*3decd659SChristos Margiolis /*
64*3decd659SChristos Margiolis  * Minimal configuration for OSS
65*3decd659SChristos Margiolis  * For real world applications, this structure will probably contain many
66*3decd659SChristos Margiolis  * more fields
67*3decd659SChristos Margiolis  */
68*3decd659SChristos Margiolis typedef struct config {
69*3decd659SChristos Margiolis 	char   *device;
70*3decd659SChristos Margiolis 	int	channels;
71*3decd659SChristos Margiolis 	int	fd;
72*3decd659SChristos Margiolis 	int	format;
73*3decd659SChristos Margiolis 	int	frag;
74*3decd659SChristos Margiolis 	int	sample_count;
75*3decd659SChristos Margiolis 	int	sample_rate;
76*3decd659SChristos Margiolis 	int	sample_size;
77*3decd659SChristos Margiolis 	int	chsamples;
78*3decd659SChristos Margiolis 	int	mmap;
79*3decd659SChristos Margiolis 	oss_audioinfo audio_info;
80*3decd659SChristos Margiolis 	audio_buf_info buffer_info;
81*3decd659SChristos Margiolis } config_t;
82*3decd659SChristos Margiolis 
83*3decd659SChristos Margiolis /*
84*3decd659SChristos Margiolis  * Error state is indicated by value=-1 in which case application exits with
85*3decd659SChristos Margiolis  * error
86*3decd659SChristos Margiolis  */
87*3decd659SChristos Margiolis static inline void
check_error(const int value,const char * message)88*3decd659SChristos Margiolis check_error(const int value, const char *message)
89*3decd659SChristos Margiolis {
90*3decd659SChristos Margiolis 	if (value == -1)
91*3decd659SChristos Margiolis 		err(1, "OSS error: %s\n", message);
92*3decd659SChristos Margiolis }
93*3decd659SChristos Margiolis 
94*3decd659SChristos Margiolis 
95*3decd659SChristos Margiolis /* Calculate frag by giving it minimal size of buffer */
96*3decd659SChristos Margiolis static inline int
size2frag(int x)97*3decd659SChristos Margiolis size2frag(int x)
98*3decd659SChristos Margiolis {
99*3decd659SChristos Margiolis 	int frag = 0;
100*3decd659SChristos Margiolis 
101*3decd659SChristos Margiolis 	while ((1 << frag) < x)
102*3decd659SChristos Margiolis 		++frag;
103*3decd659SChristos Margiolis 
104*3decd659SChristos Margiolis 	return (frag);
105*3decd659SChristos Margiolis }
106*3decd659SChristos Margiolis 
107*3decd659SChristos Margiolis /*
108*3decd659SChristos Margiolis  * Split input buffer into channels. Input buffer is in interleaved format
109*3decd659SChristos Margiolis  * which means if we have 2 channels (L and R), this is what the buffer of 8
110*3decd659SChristos Margiolis  * samples would contain: L,R,L,R,L,R,L,R. The result are two channels
111*3decd659SChristos Margiolis  * containing: L,L,L,L and R,R,R,R.
112*3decd659SChristos Margiolis  */
113*3decd659SChristos Margiolis static void
oss_split(config_t * config,sample_t * input,sample_t * output)114*3decd659SChristos Margiolis oss_split(config_t *config, sample_t *input, sample_t *output)
115*3decd659SChristos Margiolis {
116*3decd659SChristos Margiolis 	int channel, index, i;
117*3decd659SChristos Margiolis 
118*3decd659SChristos Margiolis 	for (i = 0; i < config->sample_count; ++i) {
119*3decd659SChristos Margiolis 		channel = i % config->channels;
120*3decd659SChristos Margiolis 		index = i / config->channels;
121*3decd659SChristos Margiolis 		output[channel * index] = input[i];
122*3decd659SChristos Margiolis 	}
123*3decd659SChristos Margiolis }
124*3decd659SChristos Margiolis 
125*3decd659SChristos Margiolis /*
126*3decd659SChristos Margiolis  * Convert channels into interleaved format and place it in output
127*3decd659SChristos Margiolis  * buffer
128*3decd659SChristos Margiolis  */
129*3decd659SChristos Margiolis static void
oss_merge(config_t * config,sample_t * input,sample_t * output)130*3decd659SChristos Margiolis oss_merge(config_t *config, sample_t *input, sample_t *output)
131*3decd659SChristos Margiolis {
132*3decd659SChristos Margiolis 	int channel, index;
133*3decd659SChristos Margiolis 
134*3decd659SChristos Margiolis 	for (channel = 0; channel < config->channels; ++channel) {
135*3decd659SChristos Margiolis 		for (index = 0; index < config->chsamples; ++index) {
136*3decd659SChristos Margiolis 			output[index * config->channels + channel] =
137*3decd659SChristos Margiolis 			    input[channel * index];
138*3decd659SChristos Margiolis 		}
139*3decd659SChristos Margiolis 	}
140*3decd659SChristos Margiolis }
141*3decd659SChristos Margiolis 
142*3decd659SChristos Margiolis static void
oss_init(config_t * config)143*3decd659SChristos Margiolis oss_init(config_t *config)
144*3decd659SChristos Margiolis {
145*3decd659SChristos Margiolis 	int error, tmp, min_frag;
146*3decd659SChristos Margiolis 
147*3decd659SChristos Margiolis 	/* Open the device for read and write */
148*3decd659SChristos Margiolis 	config->fd = open(config->device, O_RDWR);
149*3decd659SChristos Margiolis 	check_error(config->fd, "open");
150*3decd659SChristos Margiolis 
151*3decd659SChristos Margiolis 	/* Get device information */
152*3decd659SChristos Margiolis 	config->audio_info.dev = -1;
153*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_ENGINEINFO, &(config->audio_info));
154*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_ENGINEINFO");
155*3decd659SChristos Margiolis 	printf("min_channels: %d\n", config->audio_info.min_channels);
156*3decd659SChristos Margiolis 	printf("max_channels: %d\n", config->audio_info.max_channels);
157*3decd659SChristos Margiolis 	printf("latency: %d\n", config->audio_info.latency);
158*3decd659SChristos Margiolis 	printf("handle: %s\n", config->audio_info.handle);
159*3decd659SChristos Margiolis 	if (config->audio_info.min_rate > config->sample_rate ||
160*3decd659SChristos Margiolis 	    config->sample_rate > config->audio_info.max_rate) {
161*3decd659SChristos Margiolis 		errx(1, "%s doesn't support chosen samplerate of %dHz!\n",
162*3decd659SChristos Margiolis 		    config->device, config->sample_rate);
163*3decd659SChristos Margiolis 	}
164*3decd659SChristos Margiolis 	if (config->channels < 1)
165*3decd659SChristos Margiolis 		config->channels = config->audio_info.max_channels;
166*3decd659SChristos Margiolis 
167*3decd659SChristos Margiolis 	/*
168*3decd659SChristos Margiolis 	 * If device is going to be used in mmap mode, disable all format
169*3decd659SChristos Margiolis 	 * conversions. Official OSS documentation states error code should not
170*3decd659SChristos Margiolis 	 * be checked.
171*3decd659SChristos Margiolis 	 * http://manuals.opensound.com/developer/mmap_test.c.html#LOC10
172*3decd659SChristos Margiolis          */
173*3decd659SChristos Margiolis 	if (config->mmap) {
174*3decd659SChristos Margiolis 		tmp = 0;
175*3decd659SChristos Margiolis 		ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp);
176*3decd659SChristos Margiolis 	}
177*3decd659SChristos Margiolis 
178*3decd659SChristos Margiolis 	/*
179*3decd659SChristos Margiolis 	 * Set number of channels. If number of channels is chosen to the value
180*3decd659SChristos Margiolis 	 * near the one wanted, save it in config
181*3decd659SChristos Margiolis          */
182*3decd659SChristos Margiolis 	tmp = config->channels;
183*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp);
184*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_CHANNELS");
185*3decd659SChristos Margiolis 	/* Or check if tmp is close enough? */
186*3decd659SChristos Margiolis 	if (tmp != config->channels) {
187*3decd659SChristos Margiolis 		errx(1, "%s doesn't support chosen channel count of %d set "
188*3decd659SChristos Margiolis 		    "to %d!\n", config->device, config->channels, tmp);
189*3decd659SChristos Margiolis 	}
190*3decd659SChristos Margiolis 	config->channels = tmp;
191*3decd659SChristos Margiolis 
192*3decd659SChristos Margiolis 	/* Set format, or bit size: 8, 16, 24 or 32 bit sample */
193*3decd659SChristos Margiolis 	tmp = config->format;
194*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp);
195*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_SETFMT");
196*3decd659SChristos Margiolis 	if (tmp != config->format) {
197*3decd659SChristos Margiolis 		errx(1, "%s doesn't support chosen sample format!\n",
198*3decd659SChristos Margiolis 		    config->device);
199*3decd659SChristos Margiolis 	}
200*3decd659SChristos Margiolis 
201*3decd659SChristos Margiolis 	/* Most common values for samplerate (in kHz): 44.1, 48, 88.2, 96 */
202*3decd659SChristos Margiolis 	tmp = config->sample_rate;
203*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_SPEED, &tmp);
204*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_SPEED");
205*3decd659SChristos Margiolis 
206*3decd659SChristos Margiolis 	/* Get and check device capabilities */
207*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_GETCAPS, &(config->audio_info.caps));
208*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_GETCAPS");
209*3decd659SChristos Margiolis 	if (!(config->audio_info.caps & PCM_CAP_DUPLEX))
210*3decd659SChristos Margiolis 		errx(1, "Device doesn't support full duplex!\n");
211*3decd659SChristos Margiolis 
212*3decd659SChristos Margiolis 	if (config->mmap) {
213*3decd659SChristos Margiolis 		if (!(config->audio_info.caps & PCM_CAP_TRIGGER))
214*3decd659SChristos Margiolis 			errx(1, "Device doesn't support triggering!\n");
215*3decd659SChristos Margiolis 		if (!(config->audio_info.caps & PCM_CAP_MMAP))
216*3decd659SChristos Margiolis 			errx(1, "Device doesn't support mmap mode!\n");
217*3decd659SChristos Margiolis 	}
218*3decd659SChristos Margiolis 
219*3decd659SChristos Margiolis 	/*
220*3decd659SChristos Margiolis 	 * If desired frag is smaller than minimum, based on number of channels
221*3decd659SChristos Margiolis 	 * and format (size in bits: 8, 16, 24, 32), set that as frag. Buffer
222*3decd659SChristos Margiolis 	 * size is 2^frag, but the real size of the buffer will be read when
223*3decd659SChristos Margiolis 	 * the configuration of the device is successful
224*3decd659SChristos Margiolis          */
225*3decd659SChristos Margiolis 	min_frag = size2frag(config->sample_size * config->channels);
226*3decd659SChristos Margiolis 
227*3decd659SChristos Margiolis 	if (config->frag < min_frag)
228*3decd659SChristos Margiolis 		config->frag = min_frag;
229*3decd659SChristos Margiolis 
230*3decd659SChristos Margiolis 	/*
231*3decd659SChristos Margiolis 	 * Allocate buffer in fragments. Total buffer will be split in number
232*3decd659SChristos Margiolis 	 * of fragments (2 by default)
233*3decd659SChristos Margiolis          */
234*3decd659SChristos Margiolis 	if (config->buffer_info.fragments < 0)
235*3decd659SChristos Margiolis 		config->buffer_info.fragments = 2;
236*3decd659SChristos Margiolis 	tmp = ((config->buffer_info.fragments) << 16) | config->frag;
237*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp);
238*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_SETFRAGMENT");
239*3decd659SChristos Margiolis 
240*3decd659SChristos Margiolis 	/* When all is set and ready to go, get the size of buffer */
241*3decd659SChristos Margiolis 	error = ioctl(config->fd, SNDCTL_DSP_GETOSPACE, &(config->buffer_info));
242*3decd659SChristos Margiolis 	check_error(error, "SNDCTL_DSP_GETOSPACE");
243*3decd659SChristos Margiolis 	if (config->buffer_info.bytes < 1) {
244*3decd659SChristos Margiolis 		errx(1, "OSS buffer error: buffer size can not be %d\n",
245*3decd659SChristos Margiolis 		    config->buffer_info.bytes);
246*3decd659SChristos Margiolis 	}
247*3decd659SChristos Margiolis 	config->sample_count = config->buffer_info.bytes / config->sample_size;
248*3decd659SChristos Margiolis 	config->chsamples = config->sample_count / config->channels;
249*3decd659SChristos Margiolis }
250*3decd659SChristos Margiolis 
251*3decd659SChristos Margiolis int
main(int argc,char * argv[])252*3decd659SChristos Margiolis main(int argc, char *argv[])
253*3decd659SChristos Margiolis {
254*3decd659SChristos Margiolis 	int ret, bytes;
255*3decd659SChristos Margiolis 	int8_t *ibuf, *obuf;
256*3decd659SChristos Margiolis 	config_t config = {
257*3decd659SChristos Margiolis 		.device = "/dev/dsp",
258*3decd659SChristos Margiolis 		.channels = -1,
259*3decd659SChristos Margiolis 		.format = format,
260*3decd659SChristos Margiolis 		.frag = -1,
261*3decd659SChristos Margiolis 		.sample_rate = 48000,
262*3decd659SChristos Margiolis 		.sample_size = sizeof(sample_t),
263*3decd659SChristos Margiolis 		.buffer_info.fragments = -1,
264*3decd659SChristos Margiolis 		.mmap = 0,
265*3decd659SChristos Margiolis 	};
266*3decd659SChristos Margiolis 
267*3decd659SChristos Margiolis 	/* Initialize device */
268*3decd659SChristos Margiolis 	oss_init(&config);
269*3decd659SChristos Margiolis 
270*3decd659SChristos Margiolis 	/*
271*3decd659SChristos Margiolis 	 * Allocate input and output buffers so that their size match frag_size
272*3decd659SChristos Margiolis 	 */
273*3decd659SChristos Margiolis 	bytes = config.buffer_info.bytes;
274*3decd659SChristos Margiolis 	ibuf = malloc(bytes);
275*3decd659SChristos Margiolis 	obuf = malloc(bytes);
276*3decd659SChristos Margiolis 	sample_t *channels = malloc(bytes);
277*3decd659SChristos Margiolis 
278*3decd659SChristos Margiolis 	printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
279*3decd659SChristos Margiolis 	    "samples: %d\n",
280*3decd659SChristos Margiolis 	    bytes, config.buffer_info.fragments,
281*3decd659SChristos Margiolis 	    config.buffer_info.fragsize, config.buffer_info.fragstotal,
282*3decd659SChristos Margiolis 	    config.sample_count);
283*3decd659SChristos Margiolis 
284*3decd659SChristos Margiolis 	/* Minimal engine: read input and copy it to the output */
285*3decd659SChristos Margiolis 	for (;;) {
286*3decd659SChristos Margiolis 		ret = read(config.fd, ibuf, bytes);
287*3decd659SChristos Margiolis 		if (ret < bytes) {
288*3decd659SChristos Margiolis 			fprintf(stderr, "Requested %d bytes, but read %d!\n",
289*3decd659SChristos Margiolis 			    bytes, ret);
290*3decd659SChristos Margiolis 			break;
291*3decd659SChristos Margiolis 		}
292*3decd659SChristos Margiolis 		oss_split(&config, (sample_t *)ibuf, channels);
293*3decd659SChristos Margiolis 		/* All processing will happen here */
294*3decd659SChristos Margiolis 		oss_merge(&config, channels, (sample_t *)obuf);
295*3decd659SChristos Margiolis 		ret = write(config.fd, obuf, bytes);
296*3decd659SChristos Margiolis 		if (ret < bytes) {
297*3decd659SChristos Margiolis 			fprintf(stderr, "Requested %d bytes, but wrote %d!\n",
298*3decd659SChristos Margiolis 			    bytes, ret);
299*3decd659SChristos Margiolis 			break;
300*3decd659SChristos Margiolis 		}
301*3decd659SChristos Margiolis 	}
302*3decd659SChristos Margiolis 
303*3decd659SChristos Margiolis 	/* Cleanup */
304*3decd659SChristos Margiolis 	free(channels);
305*3decd659SChristos Margiolis 	free(obuf);
306*3decd659SChristos Margiolis 	free(ibuf);
307*3decd659SChristos Margiolis 	close(config.fd);
308*3decd659SChristos Margiolis 
309*3decd659SChristos Margiolis 	return (0);
310*3decd659SChristos Margiolis }
311