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