/*1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2024 The FreeBSD Foundation4* Copyright (c) 2025 Goran Mekić5*6* Portions of this software were developed by Christos Margiolis7* <[email protected]> under sponsorship from the FreeBSD Foundation.8*9* Redistribution and use in source and binary forms, with or without10* modification, are permitted provided that the following conditions11* are met:12* 1. Redistributions of source code must retain the above copyright13* notice, this list of conditions and the following disclaimer.14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND19* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE20* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE21* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE22* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL23* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS24* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)25* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT26* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY27* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF28* SUCH DAMAGE.29*/3031#include <sys/soundcard.h>32#include "oss.h"3334/*35* Split input buffer into channels. The input buffer is in interleaved format,36* which means if we have 2 channels (L and R), this is what the buffer of 837* samples would contain: L,R,L,R,L,R,L,R. The result of this function is a38* buffer containing: L,L,L,L,R,R,R,R.39*/40static void41to_channels(struct config *config, void *output)42{43uint8_t *in = config->buf;44uint8_t *out = output;45int i, channel, index, offset, byte;4647/* Iterate over bytes in the input buffer */48for (byte = 0; byte < config->buffer_info.bytes;49byte += config->sample_size) {50/*51* Get index of a sample in the input buffer measured in52* samples53*/54i = byte / config->sample_size;5556/* Get which channel is being processed */57channel = i % config->audio_info.max_channels;5859/* Get offset of the sample inside a single channel */60offset = i / config->audio_info.max_channels;6162/* Get index of a sample in the output buffer */63index = (channel * config->chsamples + offset) *64config->sample_size;6566/* Copy singe sample from input to output */67memcpy(out+index, in+byte, config->sample_size);68}69}7071/*72* Convert channels into interleaved format and put into output buffer73*/74static void75to_interleaved(struct config *config, void *input)76{77uint8_t *out = config->buf;78uint8_t *in = input;79int i, index, offset, channel, byte;8081/* Iterate over bytes in the input buffer */82for (byte = 0; byte < config->buffer_info.bytes;83byte += config->sample_size) {84/*85* Get index of a sample in the input buffer measured in86* samples87*/88index = byte / config->sample_size;8990/* Get which channel is being processed */91channel = index / config->chsamples;9293/* Get offset of the sample inside a single channel */94offset = index % config->chsamples;9596/* Get index of a sample in the output buffer */97i = (config->audio_info.max_channels * offset + channel) *98config->sample_size;99100/* Copy singe sample from input to output */101memcpy(out+i, in+byte, config->sample_size);102}103}104105int106main(int argc, char *argv[])107{108struct config config = {109.device = "/dev/dsp",110.mode = O_RDWR,111.format = AFMT_S32_NE,112.sample_rate = 48000,113};114int32_t *channels;115int rc, bytes;116117oss_init(&config);118if (config.format != AFMT_S32_NE)119errx(1, "Device doesn't support signed 32bit samples. "120"Check with 'sndctl' if it can be configured for 's32le' format.");121bytes = config.buffer_info.bytes;122channels = malloc(bytes);123124for (;;) {125if ((rc = read(config.fd, config.buf, bytes)) < bytes) {126warn("Requested %d bytes, but read %d!\n", bytes, rc);127break;128}129/*130* Strictly speaking, we could omit "channels" and operate only131* using config->buf, but this example tries to show the real132* world application usage. The problem is that the buffer is133* in interleaved format, and if you'd like to do any134* processing and/or mixing, it is easier to do that if samples135* are grouped per channel.136*/137to_channels(&config, channels);138to_interleaved(&config, channels);139if ((rc = write(config.fd, config.buf, bytes)) < bytes) {140warn("Requested %d bytes, but wrote %d!\n", bytes, rc);141break;142}143}144145free(channels);146free(config.buf);147close(config.fd);148149return (0);150}151152153