sound examples: Extend and clean up

- Simplify directory and file structure.
- Clean up and improve code. Add more comments.
- Add polling examples.

MFC after:	1 week
Reviewed by:	christos
Differential Revision:	https://reviews.freebsd.org/D53353
This commit is contained in:
Goran Mekić
2025-11-12 21:15:59 +01:00
committed by Christos Margiolis
parent e353cb88d4
commit 6a56966686
8 changed files with 595 additions and 383 deletions
+7 -7
View File
@@ -319,13 +319,13 @@ SE_SCSI_TARGET= \
SE_DIRS+= sound
SE_SOUND= \
sndstat_nv.c \
midi.c
SE_DIRS+= sound/oss
SE_SOUND_OSS= \
README \
audio.c
kqueue.c \
midi.c \
oss.h \
poll.c \
select.c \
simple.c \
sndstat_nv.c
SE_DIRS+= sunrpc
SE_SUNRPC= Makefile
+79
View File
@@ -0,0 +1,79 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Goran Mekić
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/event.h>
#include "oss.h"
int
main(int argc, char *argv[])
{
struct config config = {
.device = "/dev/dsp",
.mode = O_RDWR,
.format = AFMT_S32_NE,
.sample_rate = 48000,
};
struct kevent event = {};
int rc, bytes, kq;
oss_init(&config);
bytes = config.buffer_info.bytes;
if ((kq = kqueue()) < 0)
err(1, "Failed to allocate kqueue");
EV_SET(&event, config.fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0);
if (kevent(kq, &event, 1, NULL, 0, NULL) < 0)
err(1, "Failed to register kevent");
for (;;) {
if (kevent(kq, NULL, 0, &event, 1, NULL) < 0) {
warn("Event error");
break;
}
if (event.flags & EV_ERROR) {
warn("Event error: %s", strerror(event.data));
break;
}
if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but read %d!\n", bytes, rc);
break;
}
if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but wrote %d!\n", bytes, rc);
break;
}
}
EV_SET(&event, config.fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
if (kevent(kq, &event, 1, NULL, 0, NULL) < 0)
err(1, "Failed to unregister kevent");
close(kq);
free(config.buf);
close(config.fd);
return (0);
}
+222
View File
@@ -0,0 +1,222 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Goran Mekić
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/soundcard.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*
* Minimal configuration for OSS. For real world applications, this structure
* will probably contain many more fields
*/
struct config {
char *device;
int mode;
int fd;
int format;
int sample_count;
int sample_rate;
int sample_size;
int chsamples;
int mmap;
void *buf;
oss_audioinfo audio_info;
audio_buf_info buffer_info;
};
/*
* The buffer size used by OSS is (2 ^ exponent) * number_of_fragments.
* Exponent values range between 4 and 16, so this function looks for the
* smallest exponent which can fit a buffer of size "x". The fragments
* determine in how many chunks the buffer will be sliced into, hence if the
* exponent is 4, and number of fragments is 2, the requested size will be 2^4
* * 2 = 32. Please note that the buffer size is in bytes, not samples. For
* example, a 24-bit sample will be represented with 3 bytes. If you're porting
* an audio application from Linux, you should be aware that 24-bit samples on
* it are represented with 4 bytes (usually int). The idea of a total buffer
* size that holds number of fragments is to allow application to be
* number_of_fragments - 1 late. That's called jitter tolerance.
*
* Official OSS development howto:
* http://manuals.opensound.com/developer/DSP.html
*/
static inline int
size2exp(int x)
{
int exp = 0;
while ((1 << exp) < x)
exp++;
return (exp);
}
static void
oss_init(struct config *config)
{
unsigned long request = SNDCTL_DSP_GETOSPACE;
int tmp = 0;
if ((config->fd = open(config->device, config->mode)) < 0)
err(1, "Error opening the device %s", config->device);
/* Get device information */
if (ioctl(config->fd, SNDCTL_ENGINEINFO, &config->audio_info) < 0)
err(1, "Unable to get device info");
/* Get device capabilities */
if (ioctl(config->fd, SNDCTL_DSP_GETCAPS, &config->audio_info.caps) < 0)
err(1, "Unable to get capabilities");
/* Check if device supports triggering */
if (!(config->audio_info.caps & PCM_CAP_TRIGGER))
errx(1, "Device doesn't support triggering!\n");
/* Handle memory mapped mode */
if (config->mmap) {
if (!(config->audio_info.caps & PCM_CAP_MMAP))
errx(1, "Device doesn't support mmap mode!\n");
tmp = 0;
if (ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp) < 0)
err(1, "Unable to set cooked mode");
}
/* Set sample format */
if (ioctl(config->fd, SNDCTL_DSP_SETFMT, &config->format) < 0)
err(1, "Unable to set sample format");
/* Set sample channels */
if (ioctl(config->fd, SNDCTL_DSP_CHANNELS, &config->audio_info.max_channels) < 0)
err(1, "Unable to set channels");
/* Set sample rate */
if (ioctl(config->fd, SNDCTL_DSP_SPEED, &config->sample_rate) < 0)
err(1, "Unable to set sample rate");
/* Calculate sample size */
switch (config->format) {
case AFMT_S8:
case AFMT_U8:
config->sample_size = 1;
break;
case AFMT_S16_BE:
case AFMT_S16_LE:
case AFMT_U16_BE:
case AFMT_U16_LE:
config->sample_size = 2;
break;
case AFMT_S24_BE:
case AFMT_S24_LE:
case AFMT_U24_BE:
case AFMT_U24_LE:
config->sample_size = 3;
break;
case AFMT_S32_BE:
case AFMT_S32_LE:
case AFMT_U32_BE:
case AFMT_U32_LE:
case AFMT_F32_BE:
case AFMT_F32_LE:
config->sample_size = 4;
break;
default:
errx(1, "Invalid audio format %d", config->format);
break;
}
/*
* Set fragment and sample size. This part is optional as OSS has
* default values. From the kernel's perspective, there are few things
* OSS developers should be aware of:
*
* - For each sound(4)-created channel, there is a software-facing
* buffer, and a hardware-facing one.
* - The sizes of the buffers can be listed in the console with "sndctl
* swbuf hwbuf".
* - OSS ioctls only concern software-facing buffer fragments, not
* hardware.
*
* For USB sound cards, the block size is set according to the
* hw.usb.uaudio.buffer_ms sysctl, meaning 2ms at 48kHz gives 0.002 *
* 48000 = 96 samples per block. Block size should be set as multiple
* of 96, in this case. The OSS driver insists on reading/writing a
* certain number of samples at a time, one fragment full of samples.
* It is bound to do so at a fixed time frame, to avoid under- and
* overruns during communication with the hardware.
*/
config->buffer_info.fragments = 2;
tmp = size2exp(config->sample_size * config->audio_info.max_channels);
tmp = ((config->buffer_info.fragments) << 16) | tmp;
if (ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0)
err(1, "Unable to set fragment size");
/* Get buffer info */
if ((config->mode & O_ACCMODE) == O_RDONLY)
request = SNDCTL_DSP_GETISPACE;
if (ioctl(config->fd, request, &config->buffer_info) < 0)
err(1, "Unable to get buffer info");
if (config->buffer_info.fragments < 1)
config->buffer_info.fragments = config->buffer_info.fragstotal;
if (config->buffer_info.bytes < 1)
config->buffer_info.bytes = config->buffer_info.fragstotal * config->buffer_info.fragsize;
if (config->buffer_info.bytes < 1) {
errx(1, "OSS buffer error: buffer size can not be %d\n",
config->buffer_info.bytes);
}
config->sample_count = config->buffer_info.bytes / config->sample_size;
config->chsamples = config->sample_count / config->audio_info.max_channels;
config->buf = malloc(config->buffer_info.bytes);
printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
"samples: %d\n",
config->buffer_info.bytes, config->buffer_info.fragments,
config->buffer_info.fragsize, config->buffer_info.fragstotal,
config->sample_count);
/* Set the trigger */
switch (config->mode & O_ACCMODE) {
case O_RDONLY:
tmp = PCM_ENABLE_INPUT;
break;
case O_WRONLY:
tmp = PCM_ENABLE_OUTPUT;
break;
case O_RDWR:
tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
break;
default:
errx(1, "Invalid mode %d", config->mode);
break;
}
if (ioctl(config->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0)
err(1, "Failed to set trigger");
}
-66
View File
@@ -1,66 +0,0 @@
Briefly summarised, a general audio application will:
- open(2)
- ioctl(2)
- read(2)
- write(2)
- close(2)
In this example, read/write will be called in a loop for a duration of
record/playback. Usually, /dev/dsp is the device you want to open, but it can
be any OSS compatible device, even user space one created with virtual_oss. For
configuring sample rate, bit depth and all other configuring of the device
ioctl is used. As devices can support multiple sample rates and formats, what
specific application should do in case there's an error issuing ioctl, as not
all errors are fatal, is upon the developer to decide. As a general guideline
Official OSS development howto should be used. FreeBSD OSS and virtual_oss are
different to a small degree.
For more advanced OSS and real-time applications, developers need to handle
buffers more carefully. The size of the buffer in OSS is selected using fragment
size size_selector and the buffer size is 2^size_selector for values between 4
and 16. The formula on the official site is:
int frag = (max_fragments << 16) | (size_selector);
ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
The max_fragments determines in how many fragments the buffer will be, hence if
the size_selector is 4, the requested size is 2^4 = 16 and for the
max_fragments of 2, the total buffer size will be
(2 ^ size_selector) * max_fragments
or in this case 32 bytes. Please note that size of buffer is in bytes not
samples. For example, 24bit sample will be represented with 3 bytes. If you're
porting audio app from Linux, you should be aware that 24 bit samples are
represented with 4 bytes (usually int).
FreeBSD kernel will round up max_fragments and size of fragment/buffer, so the
last thing any OSS code should do is get info about buffer with audio_buf_info
and SNDCTL_DSP_GETOSPACE. That also means that not all values of max_fragments
are permitted.
From kernel perspective, there are few points OSS developers should be aware of:
- There is a software facing buffer (bs) and a hardware driver buffer (b)
- The sizes can be seen with cat /dev/sndstat as [b:_/_/_] [bs:_/_/_] (needed:
sysctl hw.snd.verbose=2)
- OSS ioctl only concern software buffer fragments, not hardware
For USB the block size is according to hw.usb.uaudio.buffer_ms sysctl, meaning
2ms at 48kHz gives 0.002 * 48000 = 96 samples per block, all multiples of this
work well. Block size for virtual_oss, if used, should be set accordingly.
OSS driver insists on reading / writing a certain number of samples at a time,
one fragment full of samples. It is bound to do so in a fixed time frame, to
avoid under- and overruns in communication with the hardware.
The idea of a total buffer size that holds max_fragments fragments is to give
some slack and allow application to be about max_fragments - 1 fragments late.
Let's call this the jitter tolerance. The jitter tolerance may be much less if
there is a slight mismatch between the period and the samples per fragment.
Jitter tolerance gets better if we can make either the period or the samples
per fragment considerably smaller than the other. In our case that means we
divide the total buffer size into smaller fragments, keeping overall latency at
the same level.
Official OSS development howto: http://manuals.opensound.com/developer/DSP.html
-310
View File
@@ -1,310 +0,0 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2021 Goran Mekić
* Copyright (c) 2024 The FreeBSD Foundation
*
* Portions of this software were developed by Christos Margiolis
* <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/soundcard.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef SAMPLE_SIZE
#define SAMPLE_SIZE 16
#endif
/* Format can be unsigned, in which case replace S with U */
#if SAMPLE_SIZE == 32
typedef int32_t sample_t;
int format = AFMT_S32_NE; /* Signed 32bit native endian format */
#elif SAMPLE_SIZE == 16
typedef int16_t sample_t;
int format = AFMT_S16_NE; /* Signed 16bit native endian format */
#elif SAMPLE_SIZE == 8
typedef int8_t sample_t;
int format = AFMT_S8_NE; /* Signed 8bit native endian format */
#else
#error Unsupported sample format!
typedef int32_t sample_t;
int format = AFMT_S32_NE; /* Not a real value, just silencing
* compiler errors */
#endif
/*
* Minimal configuration for OSS
* For real world applications, this structure will probably contain many
* more fields
*/
typedef struct config {
char *device;
int channels;
int fd;
int format;
int frag;
int sample_count;
int sample_rate;
int sample_size;
int chsamples;
int mmap;
oss_audioinfo audio_info;
audio_buf_info buffer_info;
} config_t;
/*
* Error state is indicated by value=-1 in which case application exits with
* error
*/
static inline void
check_error(const int value, const char *message)
{
if (value == -1)
err(1, "OSS error: %s\n", message);
}
/* Calculate frag by giving it minimal size of buffer */
static inline int
size2frag(int x)
{
int frag = 0;
while ((1 << frag) < x)
++frag;
return (frag);
}
/*
* Split input buffer into channels. Input buffer is in interleaved format
* which means if we have 2 channels (L and R), this is what the buffer of 8
* samples would contain: L,R,L,R,L,R,L,R. The result are two channels
* containing: L,L,L,L and R,R,R,R.
*/
static void
oss_split(config_t *config, sample_t *input, sample_t *output)
{
int channel, index, i;
for (i = 0; i < config->sample_count; ++i) {
channel = i % config->channels;
index = i / config->channels;
output[channel * index] = input[i];
}
}
/*
* Convert channels into interleaved format and place it in output
* buffer
*/
static void
oss_merge(config_t *config, sample_t *input, sample_t *output)
{
int channel, index;
for (channel = 0; channel < config->channels; ++channel) {
for (index = 0; index < config->chsamples; ++index) {
output[index * config->channels + channel] =
input[channel * index];
}
}
}
static void
oss_init(config_t *config)
{
int error, tmp, min_frag;
/* Open the device for read and write */
config->fd = open(config->device, O_RDWR);
check_error(config->fd, "open");
/* Get device information */
config->audio_info.dev = -1;
error = ioctl(config->fd, SNDCTL_ENGINEINFO, &(config->audio_info));
check_error(error, "SNDCTL_ENGINEINFO");
printf("min_channels: %d\n", config->audio_info.min_channels);
printf("max_channels: %d\n", config->audio_info.max_channels);
printf("latency: %d\n", config->audio_info.latency);
printf("handle: %s\n", config->audio_info.handle);
if (config->audio_info.min_rate > config->sample_rate ||
config->sample_rate > config->audio_info.max_rate) {
errx(1, "%s doesn't support chosen samplerate of %dHz!\n",
config->device, config->sample_rate);
}
if (config->channels < 1)
config->channels = config->audio_info.max_channels;
/*
* If device is going to be used in mmap mode, disable all format
* conversions. Official OSS documentation states error code should not
* be checked.
* http://manuals.opensound.com/developer/mmap_test.c.html#LOC10
*/
if (config->mmap) {
tmp = 0;
ioctl(config->fd, SNDCTL_DSP_COOKEDMODE, &tmp);
}
/*
* Set number of channels. If number of channels is chosen to the value
* near the one wanted, save it in config
*/
tmp = config->channels;
error = ioctl(config->fd, SNDCTL_DSP_CHANNELS, &tmp);
check_error(error, "SNDCTL_DSP_CHANNELS");
/* Or check if tmp is close enough? */
if (tmp != config->channels) {
errx(1, "%s doesn't support chosen channel count of %d set "
"to %d!\n", config->device, config->channels, tmp);
}
config->channels = tmp;
/* Set format, or bit size: 8, 16, 24 or 32 bit sample */
tmp = config->format;
error = ioctl(config->fd, SNDCTL_DSP_SETFMT, &tmp);
check_error(error, "SNDCTL_DSP_SETFMT");
if (tmp != config->format) {
errx(1, "%s doesn't support chosen sample format!\n",
config->device);
}
/* Most common values for samplerate (in kHz): 44.1, 48, 88.2, 96 */
tmp = config->sample_rate;
error = ioctl(config->fd, SNDCTL_DSP_SPEED, &tmp);
check_error(error, "SNDCTL_DSP_SPEED");
/* Get and check device capabilities */
error = ioctl(config->fd, SNDCTL_DSP_GETCAPS, &(config->audio_info.caps));
check_error(error, "SNDCTL_DSP_GETCAPS");
if (!(config->audio_info.caps & PCM_CAP_DUPLEX))
errx(1, "Device doesn't support full duplex!\n");
if (config->mmap) {
if (!(config->audio_info.caps & PCM_CAP_TRIGGER))
errx(1, "Device doesn't support triggering!\n");
if (!(config->audio_info.caps & PCM_CAP_MMAP))
errx(1, "Device doesn't support mmap mode!\n");
}
/*
* If desired frag is smaller than minimum, based on number of channels
* and format (size in bits: 8, 16, 24, 32), set that as frag. Buffer
* size is 2^frag, but the real size of the buffer will be read when
* the configuration of the device is successful
*/
min_frag = size2frag(config->sample_size * config->channels);
if (config->frag < min_frag)
config->frag = min_frag;
/*
* Allocate buffer in fragments. Total buffer will be split in number
* of fragments (2 by default)
*/
if (config->buffer_info.fragments < 0)
config->buffer_info.fragments = 2;
tmp = ((config->buffer_info.fragments) << 16) | config->frag;
error = ioctl(config->fd, SNDCTL_DSP_SETFRAGMENT, &tmp);
check_error(error, "SNDCTL_DSP_SETFRAGMENT");
/* When all is set and ready to go, get the size of buffer */
error = ioctl(config->fd, SNDCTL_DSP_GETOSPACE, &(config->buffer_info));
check_error(error, "SNDCTL_DSP_GETOSPACE");
if (config->buffer_info.bytes < 1) {
errx(1, "OSS buffer error: buffer size can not be %d\n",
config->buffer_info.bytes);
}
config->sample_count = config->buffer_info.bytes / config->sample_size;
config->chsamples = config->sample_count / config->channels;
}
int
main(int argc, char *argv[])
{
int ret, bytes;
int8_t *ibuf, *obuf;
config_t config = {
.device = "/dev/dsp",
.channels = -1,
.format = format,
.frag = -1,
.sample_rate = 48000,
.sample_size = sizeof(sample_t),
.buffer_info.fragments = -1,
.mmap = 0,
};
/* Initialize device */
oss_init(&config);
/*
* Allocate input and output buffers so that their size match frag_size
*/
bytes = config.buffer_info.bytes;
ibuf = malloc(bytes);
obuf = malloc(bytes);
sample_t *channels = malloc(bytes);
printf("bytes: %d, fragments: %d, fragsize: %d, fragstotal: %d, "
"samples: %d\n",
bytes, config.buffer_info.fragments,
config.buffer_info.fragsize, config.buffer_info.fragstotal,
config.sample_count);
/* Minimal engine: read input and copy it to the output */
for (;;) {
ret = read(config.fd, ibuf, bytes);
if (ret < bytes) {
fprintf(stderr, "Requested %d bytes, but read %d!\n",
bytes, ret);
break;
}
oss_split(&config, (sample_t *)ibuf, channels);
/* All processing will happen here */
oss_merge(&config, channels, (sample_t *)obuf);
ret = write(config.fd, obuf, bytes);
if (ret < bytes) {
fprintf(stderr, "Requested %d bytes, but wrote %d!\n",
bytes, ret);
break;
}
}
/* Cleanup */
free(channels);
free(obuf);
free(ibuf);
close(config.fd);
return (0);
}
+70
View File
@@ -0,0 +1,70 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Goran Mekić
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/poll.h>
#include "oss.h"
int
main(int argc, char *argv[])
{
struct config config = {
.device = "/dev/dsp",
.mode = O_RDWR,
.format = AFMT_S32_NE,
.sample_rate = 48000,
};
struct pollfd pfds[1];
int rc, bytes;
oss_init(&config);
bytes = config.buffer_info.bytes;
for (;;) {
pfds[0].fd = config.fd;
pfds[0].events = POLLOUT;
if (poll(pfds, sizeof(pfds) / sizeof(struct pollfd), -1) < 0)
err(1, "poll");
if (pfds[0].revents != 0) {
if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but read %d!\n",
bytes, rc);
break;
}
if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
err(1, "Requested %d bytes, but wrote %d!\n",
bytes, rc);
break;
}
}
}
free(config.buf);
close(config.fd);
return (0);
}
+70
View File
@@ -0,0 +1,70 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Goran Mekić
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/poll.h>
#include "oss.h"
int
main(int argc, char *argv[])
{
struct config config = {
.device = "/dev/dsp",
.mode = O_RDWR,
.format = AFMT_S32_NE,
.sample_rate = 48000,
};
fd_set fds;
int rc, bytes;
oss_init(&config);
bytes = config.buffer_info.bytes;
for (;;) {
FD_ZERO(&fds);
FD_SET(config.fd, &fds);
if (select(config.fd + 1, &fds, NULL, NULL, NULL) < 0)
err(1, "select");
if (FD_ISSET(config.fd, &fds)) {
if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but read %d!\n",
bytes, rc);
break;
}
if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but wrote %d!\n",
bytes, rc);
break;
}
}
}
free(config.buf);
close(config.fd);
return (0);
}
+147
View File
@@ -0,0 +1,147 @@
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2024 The FreeBSD Foundation
* Copyright (c) 2025 Goran Mekić
*
* Portions of this software were developed by Christos Margiolis
* <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "oss.h"
/*
* Split input buffer into channels. The input buffer is in interleaved format,
* which means if we have 2 channels (L and R), this is what the buffer of 8
* samples would contain: L,R,L,R,L,R,L,R. The result of this function is a
* buffer containing: L,L,L,L,R,R,R,R.
*/
static void
to_channels(struct config *config, void *output)
{
uint8_t *in = config->buf;
uint8_t *out = output;
int i, channel, index, offset, byte;
/* Iterate over bytes in the input buffer */
for (byte = 0; byte < config->buffer_info.bytes;
byte += config->sample_size) {
/*
* Get index of a sample in the input buffer measured in
* samples
*/
i = byte / config->sample_size;
/* Get which channel is being processed */
channel = i % config->audio_info.max_channels;
/* Get offset of the sample inside a single channel */
offset = i / config->audio_info.max_channels;
/* Get index of a sample in the output buffer */
index = (channel * config->chsamples + offset) *
config->sample_size;
/* Copy singe sample from input to output */
memcpy(out+index, in+byte, config->sample_size);
}
}
/*
* Convert channels into interleaved format and put into output buffer
*/
static void
to_interleaved(struct config *config, void *input)
{
uint8_t *out = config->buf;
uint8_t *in = input;
int i, index, offset, channel, byte;
/* Iterate over bytes in the input buffer */
for (byte = 0; byte < config->buffer_info.bytes;
byte += config->sample_size) {
/*
* Get index of a sample in the input buffer measured in
* samples
*/
index = byte / config->sample_size;
/* Get which channel is being processed */
channel = index / config->chsamples;
/* Get offset of the sample inside a single channel */
offset = index % config->chsamples;
/* Get index of a sample in the output buffer */
i = (config->audio_info.max_channels * offset + channel) *
config->sample_size;
/* Copy singe sample from input to output */
memcpy(out+i, in+byte, config->sample_size);
}
}
int
main(int argc, char *argv[])
{
struct config config = {
.device = "/dev/dsp",
.mode = O_RDWR,
.format = AFMT_S32_NE,
.sample_rate = 48000,
};
int32_t *channels;
int rc, bytes;
oss_init(&config);
bytes = config.buffer_info.bytes;
channels = malloc(bytes);
for (;;) {
if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but read %d!\n", bytes, rc);
break;
}
/*
* Strictly speaking, we could omit "channels" and operate only
* using config->buf, but this example tries to show the real
* world application usage. The problem is that the buffer is
* in interleaved format, and if you'd like to do any
* processing and/or mixing, it is easier to do that if samples
* are grouped per channel.
*/
to_channels(&config, channels);
to_interleaved(&config, channels);
if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
warn("Requested %d bytes, but wrote %d!\n", bytes, rc);
break;
}
}
free(channels);
free(config.buf);
close(config.fd);
return (0);
}