The branch main has been updated by christos: URL: https://cgit.FreeBSD.org/src/commit/?id=a48bbef5eb32508a8d7b3b986c9b1d28176d1694
commit a48bbef5eb32508a8d7b3b986c9b1d28176d1694 Author: Goran Mekić <[email protected]> AuthorDate: 2026-06-17 10:36:12 +0000 Commit: Christos Margiolis <[email protected]> CommitDate: 2026-06-17 10:50:44 +0000 sound: Adjust mmap example to use kqueue Reviewed by: christos Differential Revision: https://reviews.freebsd.org/D57410 --- share/examples/sound/mmap.c | 178 +++++++++++++------------------------------- 1 file changed, 50 insertions(+), 128 deletions(-) diff --git a/share/examples/sound/mmap.c b/share/examples/sound/mmap.c index 7f165d417020..3710483361ac 100644 --- a/share/examples/sound/mmap.c +++ b/share/examples/sound/mmap.c @@ -26,114 +26,18 @@ */ /* - * This program demonstrates low-latency audio pass-through using mmap. - * Opens input and output audio devices using memory-mapped I/O, - * synchronizes them in a sync group for simultaneous start, - * then continuously copies audio data from input to output. + * This program demonstrates low-latency audio pass-through using mmap + * and kqueue. It opens input and output audio devices using memory- + * mapped I/O, synchronizes them in a sync group for simultaneous start, + * then continuously copies audio data from input to output. Buffer + * positions are obtained from kqueue's ext[0] (replacing GETIPTR/ + * GETOPTR ioctls) and error counters from ext[1] (replacing GETERROR). */ -#include <time.h> +#include <sys/event.h> #include "oss.h" -/* - * Get current time in nanoseconds using monotonic clock. - * Monotonic clock is not affected by system time changes. - */ -static int64_t -gettime_ns(void) -{ - struct timespec ts; - - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) - err(1, "clock_gettime failed"); - return ((int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec); -} - -/* - * Sleep until the specified absolute time (in nanoseconds). - * Uses TIMER_ABSTIME for precise timing synchronization. - */ -static void -sleep_until_ns(int64_t target_ns) -{ - struct timespec ts; - - ts.tv_sec = target_ns / 1000000000LL; - ts.tv_nsec = target_ns % 1000000000LL; - if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0) - err(1, "clock_nanosleep failed"); -} - -/* - * Calculate the number of frames to process per iteration. - * Higher sample rates require larger steps to maintain efficiency. - */ -static unsigned -frame_stepping(unsigned sample_rate) -{ - return (16U * (1U + (sample_rate / 50000U))); -} - -/* - * Update the mmap pointer and calculate progress. - * Returns the absolute progress in bytes. - * - * fd: file descriptor for the audio device - * request: ioctl request (SNDCTL_DSP_GETIPTR or SNDCTL_DSP_GETOPTR) - * map_pointer: current pointer position in the ring buffer - * map_progress: absolute progress in bytes - * buffer_bytes: total size of the ring buffer - * frag_size: size of each fragment - * frame_size: size of one audio frame in bytes - */ -static int64_t -update_map_progress(int fd, unsigned long request, int *map_pointer, - int64_t *map_progress, int buffer_bytes, int frag_size, int frame_size) -{ - count_info info = {}; - unsigned delta, max_bytes, cycles; - int fragments; - - if (ioctl(fd, request, &info) < 0) - err(1, "Failed to get mmap pointer"); - if (info.ptr < 0 || info.ptr >= buffer_bytes) - errx(1, "Pointer out of bounds: %d", info.ptr); - if ((info.ptr % frame_size) != 0) - errx(1, "Pointer %d not aligned to frame size %d", info.ptr, - frame_size); - if (info.blocks < 0) - errx(1, "Invalid block count %d", info.blocks); - - /* - * Calculate delta: how many bytes have been processed since last check. - * Handle ring buffer wraparound using modulo arithmetic. - */ - delta = (info.ptr + buffer_bytes - *map_pointer) % buffer_bytes; - - /* - * Adjust delta based on reported blocks available. - * This accounts for cases where the pointer has wrapped multiple times. - */ - max_bytes = (info.blocks + 1) * frag_size - 1; - if (max_bytes >= delta) { - cycles = max_bytes - delta; - cycles -= cycles % buffer_bytes; - delta += cycles; - } - - /* Verify fragment count matches expected value */ - fragments = delta / frag_size; - if (info.blocks < fragments || info.blocks > fragments + 1) - warnx("Pointer block mismatch: ptr=%d blocks=%d delta=%u", - info.ptr, info.blocks, delta); - - /* Update pointer and progress tracking */ - *map_pointer = info.ptr; - *map_progress += delta; - return (*map_progress); -} - /* * Copy data between ring buffers, handling wraparound. * The copy starts at 'offset' and copies 'length' bytes. @@ -169,8 +73,6 @@ main(int argc, char *argv[]) int ch, bytes; int frag_size, frame_size, verbose = 0; int map_pointer = 0; - unsigned step_frames; - int64_t frame_ns, start_ns, next_wakeup_ns; int64_t read_progress = 0, write_progress = 0; oss_syncgroup sync_group = { 0, 0, { 0 } }; struct config config_in = { @@ -187,6 +89,8 @@ main(int argc, char *argv[]) .sample_rate = 48000, .mmap = 1, }; + struct kevent ev; + int kq; while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { @@ -228,10 +132,6 @@ main(int argc, char *argv[]) errx(1, "Input and output configurations have different fragment sizes"); - /* Calculate timing parameters */ - step_frames = frame_stepping(config_in.sample_rate); - frame_ns = 1000000000LL / config_in.sample_rate; - /* Clear output buffer to prevent noise on startup */ memset(config_out.buf, 0, bytes); @@ -245,25 +145,51 @@ main(int argc, char *argv[]) if (ioctl(config_in.fd, SNDCTL_DSP_SYNCSTART, &sync_group.id) < 0) err(1, "Starting sync group failed"); - /* Initialize timing and progress tracking */ - start_ns = gettime_ns(); - read_progress = update_map_progress(config_in.fd, SNDCTL_DSP_GETIPTR, - &map_pointer, &read_progress, bytes, frag_size, frame_size); - write_progress = read_progress; - next_wakeup_ns = start_ns; + /* Create kqueue and register input device for read events */ + kq = kqueue(); + if (kq < 0) + err(1, "kqueue failed"); + EV_SET(&ev, config_in.fd, EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) + err(1, "kevent register failed"); /* * Main processing loop: - * 1. Sleep until next scheduled wakeup - * 2. Check how much new audio data is available - * 3. Copy available data from input to output buffer - * 4. Schedule next wakeup + * Block on kevent() until input data is available. + * ext[0] holds the current DMA pointer (GETIPTR/GETOPTR equivalent). + * ext[1] holds the xrun count for the channel (GETERROR equivalent). */ for (;;) { - sleep_until_ns(next_wakeup_ns); - read_progress = update_map_progress(config_in.fd, - SNDCTL_DSP_GETIPTR, &map_pointer, &read_progress, bytes, - frag_size, frame_size); + int n; + int ptr; + unsigned delta; + + n = kevent(kq, NULL, 0, &ev, 1, NULL); + if (n < 0) + err(1, "kevent failed"); + if (n == 0) + continue; + + ptr = (int)ev.ext[0]; + if (ptr < 0 || ptr >= bytes) + errx(1, "Pointer out of bounds: %d", ptr); + if ((ptr % frame_size) != 0) + errx(1, "Pointer %d not aligned to frame size %d", ptr, + frame_size); + + /* + * Calculate delta: how many bytes have been processed since + * last check. Handle ring buffer wraparound. + */ + delta = (ptr + bytes - map_pointer) % bytes; + + /* Update pointer and progress tracking */ + map_pointer = ptr; + read_progress += delta; + + /* Report xruns if any */ + if (ev.ext[1] != 0 && verbose) + warnx("xruns: %llu", (unsigned long long)ev.ext[1]); /* Copy new audio data if available */ if (read_progress > write_progress) { @@ -277,13 +203,9 @@ main(int argc, char *argv[]) printf("copied %d bytes at %d (abs %lld)\n", length, offset, (long long)write_progress); } - - /* Schedule next wakeup based on frame timing */ - next_wakeup_ns += (int64_t)step_frames * frame_ns; - if (next_wakeup_ns < gettime_ns()) - next_wakeup_ns = gettime_ns(); } + close(kq); if (munmap(config_in.buf, bytes) != 0) err(1, "Memory unmap failed"); config_in.buf = NULL;
