xref: /freebsd/share/examples/sound/oss.h (revision 6a569666868b36f5f436eea9d66789b6df191b8a)
1*6a569666SGoran Mekić /*
2*6a569666SGoran Mekić  * SPDX-License-Identifier: BSD-2-Clause
3*6a569666SGoran Mekić  *
4*6a569666SGoran Mekić  * Copyright (c) 2025 Goran Mekić
5*6a569666SGoran Mekić  *
6*6a569666SGoran Mekić  * Redistribution and use in source and binary forms, with or without
7*6a569666SGoran Mekić  * modification, are permitted provided that the following conditions
8*6a569666SGoran Mekić  * are met:
9*6a569666SGoran Mekić  * 1. Redistributions of source code must retain the above copyright
10*6a569666SGoran Mekić  *    notice, this list of conditions and the following disclaimer.
11*6a569666SGoran Mekić  * 2. Redistributions in binary form must reproduce the above copyright
12*6a569666SGoran Mekić  *    notice, this list of conditions and the following disclaimer in the
13*6a569666SGoran Mekić  *    documentation and/or other materials provided with the distribution.
14*6a569666SGoran Mekić  *
15*6a569666SGoran Mekić  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*6a569666SGoran Mekić  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*6a569666SGoran Mekić  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*6a569666SGoran Mekić  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*6a569666SGoran Mekić  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*6a569666SGoran Mekić  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*6a569666SGoran Mekić  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*6a569666SGoran Mekić  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*6a569666SGoran Mekić  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*6a569666SGoran Mekić  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*6a569666SGoran Mekić  * SUCH DAMAGE.
26*6a569666SGoran Mekić  */
27*6a569666SGoran Mekić 
28*6a569666SGoran Mekić #include <sys/soundcard.h>
29*6a569666SGoran Mekić 
30*6a569666SGoran Mekić #include <err.h>
31*6a569666SGoran Mekić #include <fcntl.h>
32*6a569666SGoran Mekić #include <stdio.h>
33*6a569666SGoran Mekić #include <stdlib.h>
34*6a569666SGoran Mekić #include <string.h>
35*6a569666SGoran Mekić #include <unistd.h>
36*6a569666SGoran Mekić 
37*6a569666SGoran Mekić /*
38*6a569666SGoran Mekić  * Minimal configuration for OSS. For real world applications, this structure
39*6a569666SGoran Mekić  * will probably contain many more fields
40*6a569666SGoran Mekić  */
41*6a569666SGoran Mekić struct config {
42*6a569666SGoran Mekić 	char   *device;
43*6a569666SGoran Mekić 	int	mode;
44*6a569666SGoran Mekić 	int	fd;
45*6a569666SGoran Mekić 	int	format;
46*6a569666SGoran Mekić 	int	sample_count;
47*6a569666SGoran Mekić 	int	sample_rate;
48*6a569666SGoran Mekić 	int	sample_size;
49*6a569666SGoran Mekić 	int	chsamples;
50*6a569666SGoran Mekić 	int	mmap;
51*6a569666SGoran Mekić 	void   *buf;
52*6a569666SGoran Mekić 	oss_audioinfo audio_info;
53*6a569666SGoran Mekić 	audio_buf_info buffer_info;
54*6a569666SGoran Mekić };
55*6a569666SGoran Mekić 
56*6a569666SGoran Mekić /*
57*6a569666SGoran Mekić  * The buffer size used by OSS is (2 ^ exponent) * number_of_fragments.
58*6a569666SGoran Mekić  * Exponent values range between 4 and 16, so this function looks for the
59*6a569666SGoran Mekić  * smallest exponent which can fit a buffer of size "x". The fragments
60*6a569666SGoran Mekić  * determine in how many chunks the buffer will be sliced into, hence if the
61*6a569666SGoran Mekić  * exponent is 4, and number of fragments is 2, the requested size will be 2^4
62*6a569666SGoran Mekić  * * 2 = 32. Please note that the buffer size is in bytes, not samples. For
63*6a569666SGoran Mekić  * example, a 24-bit sample will be represented with 3 bytes. If you're porting
64*6a569666SGoran Mekić  * an audio application from Linux, you should be aware that 24-bit samples on
65*6a569666SGoran Mekić  * it are represented with 4 bytes (usually int). The idea of a total buffer
66*6a569666SGoran Mekić  * size that holds number of fragments is to allow application to be
67*6a569666SGoran Mekić  * number_of_fragments - 1 late. That's called jitter tolerance.
68*6a569666SGoran Mekić  *
69*6a569666SGoran Mekić  * Official OSS development howto:
70*6a569666SGoran Mekić  * http://manuals.opensound.com/developer/DSP.html
71*6a569666SGoran Mekić  */
72*6a569666SGoran Mekić static inline int
size2exp(int x)73*6a569666SGoran Mekić size2exp(int x)
74*6a569666SGoran Mekić {
75*6a569666SGoran Mekić 	int exp = 0;
76*6a569666SGoran Mekić 
77*6a569666SGoran Mekić 	while ((1 << exp) < x)
78*6a569666SGoran Mekić 		exp++;
79*6a569666SGoran Mekić 
80*6a569666SGoran Mekić 	return (exp);
81*6a569666SGoran Mekić }
82*6a569666SGoran Mekić 
83*6a569666SGoran Mekić static void
oss_init(struct config * config)84*6a569666SGoran Mekić oss_init(struct config *config)
85*6a569666SGoran Mekić {
86*6a569666SGoran Mekić 	unsigned long request = SNDCTL_DSP_GETOSPACE;
87*6a569666SGoran Mekić 	int tmp = 0;
88*6a569666SGoran Mekić 
89*6a569666SGoran Mekić 	if ((config->fd = open(config->device, config->mode)) < 0)
90*6a569666SGoran Mekić 		err(1, "Error opening the device %s", config->device);
91*6a569666SGoran Mekić 
92*6a569666SGoran Mekić 	/* Get device information */
93*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_ENGINEINFO, &config->audio_info) < 0)
94*6a569666SGoran Mekić 		err(1, "Unable to get device info");
95*6a569666SGoran Mekić 
96*6a569666SGoran Mekić 	/* Get device capabilities */
97*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_GETCAPS, &config->audio_info.caps) < 0)
98*6a569666SGoran Mekić 		err(1, "Unable to get capabilities");
99*6a569666SGoran Mekić 
100*6a569666SGoran Mekić 	/* Check if device supports triggering */
101*6a569666SGoran Mekić 	if (!(config->audio_info.caps & PCM_CAP_TRIGGER))
102*6a569666SGoran Mekić 		errx(1, "Device doesn't support triggering!\n");
103*6a569666SGoran Mekić 
104*6a569666SGoran Mekić 	/* Handle memory mapped mode */
105*6a569666SGoran Mekić 	if (config->mmap) {
106*6a569666SGoran Mekić 		if (!(config->audio_info.caps & PCM_CAP_MMAP))
107*6a569666SGoran Mekić 			errx(1, "Device doesn't support mmap mode!\n");
108*6a569666SGoran Mekić 		tmp = 0;
109*6a569666SGoran Mekić 		if (ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp) < 0)
110*6a569666SGoran Mekić 			err(1, "Unable to set cooked mode");
111*6a569666SGoran Mekić 	}
112*6a569666SGoran Mekić 
113*6a569666SGoran Mekić 	/* Set sample format */
114*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &config->format) < 0)
115*6a569666SGoran Mekić 		err(1, "Unable to set sample format");
116*6a569666SGoran Mekić 
117*6a569666SGoran Mekić 	/* Set sample channels */
118*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &config->audio_info.max_channels) < 0)
119*6a569666SGoran Mekić 		err(1, "Unable to set channels");
120*6a569666SGoran Mekić 
121*6a569666SGoran Mekić 	/* Set sample rate */
122*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0)
123*6a569666SGoran Mekić 		err(1, "Unable to set sample rate");
124*6a569666SGoran Mekić 
125*6a569666SGoran Mekić 	/* Calculate sample size */
126*6a569666SGoran Mekić 	switch (config->format) {
127*6a569666SGoran Mekić 	case AFMT_S8:
128*6a569666SGoran Mekić 	case AFMT_U8:
129*6a569666SGoran Mekić 		config->sample_size = 1;
130*6a569666SGoran Mekić 		break;
131*6a569666SGoran Mekić 	case AFMT_S16_BE:
132*6a569666SGoran Mekić 	case AFMT_S16_LE:
133*6a569666SGoran Mekić 	case AFMT_U16_BE:
134*6a569666SGoran Mekić 	case AFMT_U16_LE:
135*6a569666SGoran Mekić 		config->sample_size = 2;
136*6a569666SGoran Mekić 		break;
137*6a569666SGoran Mekić 	case AFMT_S24_BE:
138*6a569666SGoran Mekić 	case AFMT_S24_LE:
139*6a569666SGoran Mekić 	case AFMT_U24_BE:
140*6a569666SGoran Mekić 	case AFMT_U24_LE:
141*6a569666SGoran Mekić 		config->sample_size = 3;
142*6a569666SGoran Mekić 		break;
143*6a569666SGoran Mekić 	case AFMT_S32_BE:
144*6a569666SGoran Mekić 	case AFMT_S32_LE:
145*6a569666SGoran Mekić 	case AFMT_U32_BE:
146*6a569666SGoran Mekić 	case AFMT_U32_LE:
147*6a569666SGoran Mekić 	case AFMT_F32_BE:
148*6a569666SGoran Mekić 	case AFMT_F32_LE:
149*6a569666SGoran Mekić 		config->sample_size = 4;
150*6a569666SGoran Mekić 		break;
151*6a569666SGoran Mekić 	default:
152*6a569666SGoran Mekić 		errx(1, "Invalid audio format %d", config->format);
153*6a569666SGoran Mekić 		break;
154*6a569666SGoran Mekić 	}
155*6a569666SGoran Mekić 
156*6a569666SGoran Mekić 	/*
157*6a569666SGoran Mekić 	 * Set fragment and sample size. This part is optional as OSS has
158*6a569666SGoran Mekić 	 * default values. From the kernel's perspective, there are few things
159*6a569666SGoran Mekić 	 * OSS developers should be aware of:
160*6a569666SGoran Mekić 	 *
161*6a569666SGoran Mekić 	 * - For each sound(4)-created channel, there is a software-facing
162*6a569666SGoran Mekić 	 *   buffer, and a hardware-facing one.
163*6a569666SGoran Mekić 	 * - The sizes of the buffers can be listed in the console with "sndctl
164*6a569666SGoran Mekić 	 *   swbuf hwbuf".
165*6a569666SGoran Mekić 	 * - OSS ioctls only concern software-facing buffer fragments, not
166*6a569666SGoran Mekić 	 *   hardware.
167*6a569666SGoran Mekić 	 *
168*6a569666SGoran Mekić 	 * For USB sound cards, the block size is set according to the
169*6a569666SGoran Mekić 	 * hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 *
170*6a569666SGoran Mekić 	 * 48000 = 96 samples per block. Block size should be set as multiple
171*6a569666SGoran Mekić 	 * of 96, in this case. The OSS driver insists on reading/writing a
172*6a569666SGoran Mekić 	 * certain number of samples at a time, one fragment full of samples.
173*6a569666SGoran Mekić 	 * It is bound to do so at a fixed time frame, to avoid under- and
174*6a569666SGoran Mekić 	 * overruns during communication with the hardware.
175*6a569666SGoran Mekić 	 */
176*6a569666SGoran Mekić 	config->buffer_info.fragments = 2;
177*6a569666SGoran Mekić 	tmp = size2exp(config->sample_size * config->audio_info.max_channels);
178*6a569666SGoran Mekić 	tmp = ((config->buffer_info.fragments) << 16) | tmp;
179*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0)
180*6a569666SGoran Mekić 		err(1, "Unable to set fragment size");
181*6a569666SGoran Mekić 
182*6a569666SGoran Mekić 	/* Get buffer info */
183*6a569666SGoran Mekić 	if ((config->mode & O_ACCMODE) == O_RDONLY)
184*6a569666SGoran Mekić 		request = SNDCTL_DSP_GETISPACE;
185*6a569666SGoran Mekić 	if (ioctl(config->fd, request, &config->buffer_info) < 0)
186*6a569666SGoran Mekić 		err(1, "Unable to get buffer info");
187*6a569666SGoran Mekić 	if (config->buffer_info.fragments < 1)
188*6a569666SGoran Mekić 		config->buffer_info.fragments = config->buffer_info.fragstotal;
189*6a569666SGoran Mekić 	if (config->buffer_info.bytes < 1)
190*6a569666SGoran Mekić 		config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize;
191*6a569666SGoran Mekić 	if (config->buffer_info.bytes < 1) {
192*6a569666SGoran Mekić 		errx(1, "OSS buffer error: buffer size can not be %d\n",
193*6a569666SGoran Mekić 		    config->buffer_info.bytes);
194*6a569666SGoran Mekić 	}
195*6a569666SGoran Mekić 	config->sample_count = config->buffer_info.bytes / config->sample_size;
196*6a569666SGoran Mekić 	config->chsamples = config->sample_count / config->audio_info.max_channels;
197*6a569666SGoran Mekić 	config->buf = malloc(config->buffer_info.bytes);
198*6a569666SGoran Mekić 
199*6a569666SGoran Mekić 	printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
200*6a569666SGoran Mekić 	    "samples: %d\n",
201*6a569666SGoran Mekić 	    config->buffer_info.bytes, config->buffer_info.fragments,
202*6a569666SGoran Mekić 	    config->buffer_info.fragsize, config->buffer_info.fragstotal,
203*6a569666SGoran Mekić 	    config->sample_count);
204*6a569666SGoran Mekić 
205*6a569666SGoran Mekić 	/* Set the trigger */
206*6a569666SGoran Mekić 	switch (config->mode & O_ACCMODE) {
207*6a569666SGoran Mekić 	case O_RDONLY:
208*6a569666SGoran Mekić 		tmp = PCM_ENABLE_INPUT;
209*6a569666SGoran Mekić 		break;
210*6a569666SGoran Mekić 	case O_WRONLY:
211*6a569666SGoran Mekić 		tmp = PCM_ENABLE_OUTPUT;
212*6a569666SGoran Mekić 		break;
213*6a569666SGoran Mekić 	case O_RDWR:
214*6a569666SGoran Mekić 		tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
215*6a569666SGoran Mekić 		break;
216*6a569666SGoran Mekić 	default:
217*6a569666SGoran Mekić 		errx(1, "Invalid mode %d", config->mode);
218*6a569666SGoran Mekić 		break;
219*6a569666SGoran Mekić 	}
220*6a569666SGoran Mekić 	if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0)
221*6a569666SGoran Mekić 		err(1, "Failed to set trigger");
222*6a569666SGoran Mekić }
223