xref: /freebsd/share/examples/sound/oss.h (revision 9978553d0199e7ec0bdd1c44fc7f6c7b0c11e43b)
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 	if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &config->format) < 0)
116 		err(1, "Unable to set sample format");
117 
118 	/* Set sample channels */
119 	if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &config->audio_info.max_channels) < 0)
120 		err(1, "Unable to set channels");
121 
122 	/* Set sample rate */
123 	if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0)
124 		err(1, "Unable to set sample rate");
125 
126 	/* Calculate sample size */
127 	switch (config->format) {
128 	case AFMT_S8:
129 	case AFMT_U8:
130 		config->sample_size = 1;
131 		break;
132 	case AFMT_S16_BE:
133 	case AFMT_S16_LE:
134 	case AFMT_U16_BE:
135 	case AFMT_U16_LE:
136 		config->sample_size = 2;
137 		break;
138 	case AFMT_S24_BE:
139 	case AFMT_S24_LE:
140 	case AFMT_U24_BE:
141 	case AFMT_U24_LE:
142 		config->sample_size = 3;
143 		break;
144 	case AFMT_S32_BE:
145 	case AFMT_S32_LE:
146 	case AFMT_U32_BE:
147 	case AFMT_U32_LE:
148 	case AFMT_F32_BE:
149 	case AFMT_F32_LE:
150 		config->sample_size = 4;
151 		break;
152 	default:
153 		errx(1, "Invalid audio format %d", config->format);
154 		break;
155 	}
156 
157 	/*
158 	 * Set fragment and sample size. This part is optional as OSS has
159 	 * default values. From the kernel's perspective, there are few things
160 	 * OSS developers should be aware of:
161 	 *
162 	 * - For each sound(4)-created channel, there is a software-facing
163 	 *   buffer, and a hardware-facing one.
164 	 * - The sizes of the buffers can be listed in the console with "sndctl
165 	 *   swbuf hwbuf".
166 	 * - OSS ioctls only concern software-facing buffer fragments, not
167 	 *   hardware.
168 	 *
169 	 * For USB sound cards, the block size is set according to the
170 	 * hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 *
171 	 * 48000 = 96 samples per block. Block size should be set as multiple
172 	 * of 96, in this case. The OSS driver insists on reading/writing a
173 	 * certain number of samples at a time, one fragment full of samples.
174 	 * It is bound to do so at a fixed time frame, to avoid under- and
175 	 * overruns during communication with the hardware.
176 	 */
177 	config->buffer_info.fragments = 2;
178 	tmp = size2exp(config->sample_size * config->audio_info.max_channels);
179 	tmp = ((config->buffer_info.fragments) << 16) | tmp;
180 	if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0)
181 		err(1, "Unable to set fragment size");
182 
183 	/* Get buffer info */
184 	if ((config->mode & O_ACCMODE) == O_RDONLY)
185 		request = SNDCTL_DSP_GETISPACE;
186 	if (ioctl(config->fd, request, &config->buffer_info) < 0)
187 		err(1, "Unable to get buffer info");
188 	if (config->buffer_info.fragments < 1)
189 		config->buffer_info.fragments = config->buffer_info.fragstotal;
190 	if (config->buffer_info.bytes < 1)
191 		config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize;
192 	if (config->buffer_info.bytes < 1) {
193 		errx(1, "OSS buffer error: buffer size can not be %d\n",
194 		    config->buffer_info.bytes);
195 	}
196 	config->sample_count = config->buffer_info.bytes / config->sample_size;
197 	config->chsamples = config->sample_count / config->audio_info.max_channels;
198 
199 	printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
200 	    "samples: %d\n",
201 	    config->buffer_info.bytes, config->buffer_info.fragments,
202 	    config->buffer_info.fragsize, config->buffer_info.fragstotal,
203 	    config->sample_count);
204 
205 	/* Set trigger direction and mmap protection */
206 	switch (config->mode & O_ACCMODE) {
207 	case O_RDONLY:
208 		tmp = PCM_ENABLE_INPUT;
209 		prot = PROT_READ;
210 		break;
211 	case O_WRONLY:
212 		tmp = PCM_ENABLE_OUTPUT;
213 		prot = PROT_WRITE;
214 		break;
215 	case O_RDWR:
216 		tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
217 		prot = PROT_READ | PROT_WRITE;
218 		break;
219 	default:
220 		errx(1, "Invalid mode %d", config->mode);
221 		break;
222 	}
223 
224 	/* Map or allocate the buffer */
225 	if (config->mmap) {
226 		config->buf = mmap(NULL, config->buffer_info.bytes, prot, MAP_SHARED, config->fd, 0);
227 		if (config->buf == MAP_FAILED)
228 			err(1, "Memory map failed");
229 	} else {
230 		if ((config->buf = malloc(config->buffer_info.bytes)) == NULL)
231 			err(1, "Allocating buffer failed");
232 	}
233 
234 	/* Set the trigger */
235 	if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0)
236 		err(1, "Failed to set trigger");
237 }
238