Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/share/examples/sound/mmap.c
288956 views
1
/*
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2026 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
/*
29
* This program demonstrates low-latency audio pass-through using mmap.
30
* Opens input and output audio devices using memory-mapped I/O,
31
* synchronizes them in a sync group for simultaneous start,
32
* then continuously copies audio data from input to output.
33
*/
34
35
#include <time.h>
36
37
#include "oss.h"
38
39
/*
40
* Get current time in nanoseconds using monotonic clock.
41
* Monotonic clock is not affected by system time changes.
42
*/
43
static int64_t
44
gettime_ns(void)
45
{
46
struct timespec ts;
47
48
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
49
err(1, "clock_gettime failed");
50
return ((int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec);
51
}
52
53
/*
54
* Sleep until the specified absolute time (in nanoseconds).
55
* Uses TIMER_ABSTIME for precise timing synchronization.
56
*/
57
static void
58
sleep_until_ns(int64_t target_ns)
59
{
60
struct timespec ts;
61
62
ts.tv_sec = target_ns / 1000000000LL;
63
ts.tv_nsec = target_ns % 1000000000LL;
64
if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0)
65
err(1, "clock_nanosleep failed");
66
}
67
68
/*
69
* Calculate the number of frames to process per iteration.
70
* Higher sample rates require larger steps to maintain efficiency.
71
*/
72
static unsigned
73
frame_stepping(unsigned sample_rate)
74
{
75
return (16U * (1U + (sample_rate / 50000U)));
76
}
77
78
/*
79
* Update the mmap pointer and calculate progress.
80
* Returns the absolute progress in bytes.
81
*
82
* fd: file descriptor for the audio device
83
* request: ioctl request (SNDCTL_DSP_GETIPTR or SNDCTL_DSP_GETOPTR)
84
* map_pointer: current pointer position in the ring buffer
85
* map_progress: absolute progress in bytes
86
* buffer_bytes: total size of the ring buffer
87
* frag_size: size of each fragment
88
* frame_size: size of one audio frame in bytes
89
*/
90
static int64_t
91
update_map_progress(int fd, unsigned long request, int *map_pointer,
92
int64_t *map_progress, int buffer_bytes, int frag_size, int frame_size)
93
{
94
count_info info = {};
95
unsigned delta, max_bytes, cycles;
96
int fragments;
97
98
if (ioctl(fd, request, &info) < 0)
99
err(1, "Failed to get mmap pointer");
100
if (info.ptr < 0 || info.ptr >= buffer_bytes)
101
errx(1, "Pointer out of bounds: %d", info.ptr);
102
if ((info.ptr % frame_size) != 0)
103
errx(1, "Pointer %d not aligned to frame size %d", info.ptr,
104
frame_size);
105
if (info.blocks < 0)
106
errx(1, "Invalid block count %d", info.blocks);
107
108
/*
109
* Calculate delta: how many bytes have been processed since last check.
110
* Handle ring buffer wraparound using modulo arithmetic.
111
*/
112
delta = (info.ptr + buffer_bytes - *map_pointer) % buffer_bytes;
113
114
/*
115
* Adjust delta based on reported blocks available.
116
* This accounts for cases where the pointer has wrapped multiple times.
117
*/
118
max_bytes = (info.blocks + 1) * frag_size - 1;
119
if (max_bytes >= delta) {
120
cycles = max_bytes - delta;
121
cycles -= cycles % buffer_bytes;
122
delta += cycles;
123
}
124
125
/* Verify fragment count matches expected value */
126
fragments = delta / frag_size;
127
if (info.blocks < fragments || info.blocks > fragments + 1)
128
warnx("Pointer block mismatch: ptr=%d blocks=%d delta=%u",
129
info.ptr, info.blocks, delta);
130
131
/* Update pointer and progress tracking */
132
*map_pointer = info.ptr;
133
*map_progress += delta;
134
return (*map_progress);
135
}
136
137
/*
138
* Copy data between ring buffers, handling wraparound.
139
* The copy starts at 'offset' and copies 'length' bytes.
140
* If the copy crosses the buffer boundary, it wraps to the beginning.
141
*/
142
static void
143
copy_ring(void *dstv, const void *srcv, int buffer_bytes, int offset,
144
int length)
145
{
146
uint8_t *dst = dstv;
147
const uint8_t *src = srcv;
148
int first;
149
150
if (length <= 0)
151
return;
152
153
/* Calculate bytes to copy before wraparound */
154
first = buffer_bytes - offset;
155
if (first > length)
156
first = length;
157
158
/* Copy first part (up to buffer end or length) */
159
memcpy(dst + offset, src + offset, first);
160
161
/* Copy remaining part from beginning of buffer if needed */
162
if (first < length)
163
memcpy(dst, src, length - first);
164
}
165
166
int
167
main(int argc, char *argv[])
168
{
169
int ch, bytes;
170
int frag_size, frame_size, verbose = 0;
171
int map_pointer = 0;
172
unsigned step_frames;
173
int64_t frame_ns, start_ns, next_wakeup_ns;
174
int64_t read_progress = 0, write_progress = 0;
175
oss_syncgroup sync_group = { 0, 0, { 0 } };
176
struct config config_in = {
177
.device = "/dev/dsp",
178
.mode = O_RDONLY | O_EXCL | O_NONBLOCK,
179
.format = AFMT_S32_NE,
180
.sample_rate = 48000,
181
.mmap = 1,
182
};
183
struct config config_out = {
184
.device = "/dev/dsp",
185
.mode = O_WRONLY | O_EXCL | O_NONBLOCK,
186
.format = AFMT_S32_NE,
187
.sample_rate = 48000,
188
.mmap = 1,
189
};
190
191
while ((ch = getopt(argc, argv, "v")) != -1) {
192
switch (ch) {
193
case 'v':
194
verbose = 1;
195
break;
196
}
197
}
198
argc -= optind;
199
argv += optind;
200
201
if (!verbose)
202
printf("Use -v for verbose mode\n");
203
204
oss_init(&config_in);
205
oss_init(&config_out);
206
207
/*
208
* Verify input and output have matching ring-buffer geometry.
209
* The passthrough loop copies raw bytes at the same offset in both mmap
210
* buffers, so both devices must expose the same total byte count.
211
* They must also use the same max_channels because frame_size is
212
* derived from that value and all mmap pointers/lengths are expected to
213
* stay aligned to whole frames on both sides. If channels differed, the
214
* same byte offset could land in the middle of a frame on one device.
215
*/
216
if (config_in.buffer_info.bytes != config_out.buffer_info.bytes)
217
errx(1,
218
"Input and output configurations have different buffer sizes");
219
if (config_in.audio_info.max_channels !=
220
config_out.audio_info.max_channels)
221
errx(1,
222
"Input and output configurations have different number of channels");
223
224
bytes = config_in.buffer_info.bytes;
225
frag_size = config_in.buffer_info.fragsize;
226
frame_size = config_in.sample_size * config_in.audio_info.max_channels;
227
if (frag_size != config_out.buffer_info.fragsize)
228
errx(1,
229
"Input and output configurations have different fragment sizes");
230
231
/* Calculate timing parameters */
232
step_frames = frame_stepping(config_in.sample_rate);
233
frame_ns = 1000000000LL / config_in.sample_rate;
234
235
/* Clear output buffer to prevent noise on startup */
236
memset(config_out.buf, 0, bytes);
237
238
/* Configure and start sync group */
239
sync_group.mode = PCM_ENABLE_INPUT;
240
if (ioctl(config_in.fd, SNDCTL_DSP_SYNCGROUP, &sync_group) < 0)
241
err(1, "Failed to add input to syncgroup");
242
sync_group.mode = PCM_ENABLE_OUTPUT;
243
if (ioctl(config_out.fd, SNDCTL_DSP_SYNCGROUP, &sync_group) < 0)
244
err(1, "Failed to add output to syncgroup");
245
if (ioctl(config_in.fd, SNDCTL_DSP_SYNCSTART, &sync_group.id) < 0)
246
err(1, "Starting sync group failed");
247
248
/* Initialize timing and progress tracking */
249
start_ns = gettime_ns();
250
read_progress = update_map_progress(config_in.fd, SNDCTL_DSP_GETIPTR,
251
&map_pointer, &read_progress, bytes, frag_size, frame_size);
252
write_progress = read_progress;
253
next_wakeup_ns = start_ns;
254
255
/*
256
* Main processing loop:
257
* 1. Sleep until next scheduled wakeup
258
* 2. Check how much new audio data is available
259
* 3. Copy available data from input to output buffer
260
* 4. Schedule next wakeup
261
*/
262
for (;;) {
263
sleep_until_ns(next_wakeup_ns);
264
read_progress = update_map_progress(config_in.fd,
265
SNDCTL_DSP_GETIPTR, &map_pointer, &read_progress, bytes,
266
frag_size, frame_size);
267
268
/* Copy new audio data if available */
269
if (read_progress > write_progress) {
270
int offset = write_progress % bytes;
271
int length = read_progress - write_progress;
272
273
copy_ring(config_out.buf, config_in.buf, bytes, offset,
274
length);
275
write_progress = read_progress;
276
if (verbose)
277
printf("copied %d bytes at %d (abs %lld)\n",
278
length, offset, (long long)write_progress);
279
}
280
281
/* Schedule next wakeup based on frame timing */
282
next_wakeup_ns += (int64_t)step_frames * frame_ns;
283
if (next_wakeup_ns < gettime_ns())
284
next_wakeup_ns = gettime_ns();
285
}
286
287
if (munmap(config_in.buf, bytes) != 0)
288
err(1, "Memory unmap failed");
289
config_in.buf = NULL;
290
if (munmap(config_out.buf, bytes) != 0)
291
err(1, "Memory unmap failed");
292
config_out.buf = NULL;
293
close(config_in.fd);
294
close(config_out.fd);
295
296
return (0);
297
}
298
299