1 /*
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2024 The FreeBSD Foundation
5 * Copyright (c) 2025 Goran Mekić
6 *
7 * Portions of this software were developed by Christos Margiolis
8 * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include "oss.h"
33
34 /*
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 8
37 * samples would contain: L,R,L,R,L,R,L,R. The result of this function is a
38 * buffer containing: L,L,L,L,R,R,R,R.
39 */
40 static void
to_channels(struct config * config,void * output)41 to_channels(struct config *config, void *output)
42 {
43 uint8_t *in = config->buf;
44 uint8_t *out = output;
45 int i, channel, index, offset, byte;
46
47 /* Iterate over bytes in the input buffer */
48 for (byte = 0; byte < config->buffer_info.bytes;
49 byte += config->sample_size) {
50 /*
51 * Get index of a sample in the input buffer measured in
52 * samples
53 */
54 i = byte / config->sample_size;
55
56 /* Get which channel is being processed */
57 channel = i % config->audio_info.max_channels;
58
59 /* Get offset of the sample inside a single channel */
60 offset = i / config->audio_info.max_channels;
61
62 /* Get index of a sample in the output buffer */
63 index = (channel * config->chsamples + offset) *
64 config->sample_size;
65
66 /* Copy singe sample from input to output */
67 memcpy(out+index, in+byte, config->sample_size);
68 }
69 }
70
71 /*
72 * Convert channels into interleaved format and put into output buffer
73 */
74 static void
to_interleaved(struct config * config,void * input)75 to_interleaved(struct config *config, void *input)
76 {
77 uint8_t *out = config->buf;
78 uint8_t *in = input;
79 int i, index, offset, channel, byte;
80
81 /* Iterate over bytes in the input buffer */
82 for (byte = 0; byte < config->buffer_info.bytes;
83 byte += config->sample_size) {
84 /*
85 * Get index of a sample in the input buffer measured in
86 * samples
87 */
88 index = byte / config->sample_size;
89
90 /* Get which channel is being processed */
91 channel = index / config->chsamples;
92
93 /* Get offset of the sample inside a single channel */
94 offset = index % config->chsamples;
95
96 /* Get index of a sample in the output buffer */
97 i = (config->audio_info.max_channels * offset + channel) *
98 config->sample_size;
99
100 /* Copy singe sample from input to output */
101 memcpy(out+i, in+byte, config->sample_size);
102 }
103 }
104
105 int
main(int argc,char * argv[])106 main(int argc, char *argv[])
107 {
108 struct config config = {
109 .device = "/dev/dsp",
110 .mode = O_RDWR,
111 .format = AFMT_S32_NE,
112 .sample_rate = 48000,
113 };
114 int32_t *channels;
115 int rc, bytes;
116
117 oss_init(&config);
118 bytes = config.buffer_info.bytes;
119 channels = malloc(bytes);
120
121 for (;;) {
122 if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
123 warn("Requested %d bytes, but read %d!\n", bytes, rc);
124 break;
125 }
126 /*
127 * Strictly speaking, we could omit "channels" and operate only
128 * using config->buf, but this example tries to show the real
129 * world application usage. The problem is that the buffer is
130 * in interleaved format, and if you'd like to do any
131 * processing and/or mixing, it is easier to do that if samples
132 * are grouped per channel.
133 */
134 to_channels(&config, channels);
135 to_interleaved(&config, channels);
136 if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
137 warn("Requested %d bytes, but wrote %d!\n", bytes, rc);
138 break;
139 }
140 }
141
142 free(channels);
143 free(config.buf);
144 close(config.fd);
145
146 return (0);
147 }
148