tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Audio device mmap and kevents
Hello,
I would like to propose a small improvement to the audio system.
I think that it is very interesting to be able to mmap the audio device
for better
performance and smaller latency but It seems neither clean nor optimal
to use
AUDIO_GETOOFFS ioctl and sleep to synchronize.
Why don't we use kernel events?
Something like audio.patch for the kernel.
I also implemented that use in audioplay and tried to clean a bit the
code in audioplay.patch.
There is certainly more work to be done but I have to ask now. What do
you think?
Thanks.
diff --git a/sys/dev/audio.c b/sys/dev/audio.c
index 49fc0cce9c2e..f0faba543824 100644
--- a/sys/dev/audio.c
+++ b/sys/dev/audio.c
@@ -3453,8 +3453,11 @@ filt_audiowrite(struct knote *kn, long hint)
struct audio_chan *chan;
audio_stream_t *stream;
dev_t dev;
+ struct virtual_channel *vc;
+ int rv = true;
chan = kn->kn_hook;
+ vc = chan->vc;
dev = chan->dev;
sc = device_lookup_private(&audio_cd, AUDIOUNIT(dev));
if (sc == NULL)
@@ -3463,11 +3466,21 @@ filt_audiowrite(struct knote *kn, long hint)
mutex_enter(sc->sc_intr_lock);
stream = chan->vc->sc_pustream;
- kn->kn_data = (stream->end - stream->start)
- - audio_stream_get_used(stream);
+ if (vc->sc_mpr.mmapped) {
+ off_t offset;
+ offset = stream->outp - stream->start
+ + vc->sc_mpr.blksize;
+ if (stream->start + offset >= stream->end)
+ offset = 0;
+ kn->kn_data = offset;
+ } else {
+ kn->kn_data = (stream->end - stream->start)
+ - audio_stream_get_used(stream);
+ rv = kn->kn_data > 0;
+ }
mutex_exit(sc->sc_intr_lock);
- return kn->kn_data > 0;
+ return rv;
}
static const struct filterops audiowrite_filtops = {
@@ -3816,6 +3829,9 @@ audio_mix(void *v)
cb->s.outp = audio_stream_add_outp(&cb->s, cb->s.outp,
blksize);
mutex_exit(sc->sc_intr_lock);
+ if ((vc->sc_mode & AUMODE_PLAY) && !cb->pause)
+ sc->schedule_wih = true;
+
continue;
}
@@ -3934,7 +3950,7 @@ audio_mix(void *v)
vc = sc->sc_hwvc;
cb = &sc->sc_mixring.sc_mpr;
inp = cb->s.inp;
- cc = blksize - (inp - cb->s.start) % blksize;
+ cc = blksize - (inp - cb->s.start) % blksize; // that line in 'then' branch
if (sc->sc_writeme == false) {
DPRINTFN(3, ("MIX RING EMPTY - INSERT SILENCE\n"));
audio_fill_silence(&vc->sc_pustream->param, inp, cc);
diff --git a/usr.bin/audio/play/audioplay.1 b/usr.bin/audio/play/audioplay.1
index 59ea9c4ded7f..7ff60217ba37 100644
--- a/usr.bin/audio/play/audioplay.1
+++ b/usr.bin/audio/play/audioplay.1
@@ -118,6 +118,12 @@ sample rate.
Print a help message.
.It Fl i
If the audio device cannot be opened, exit now rather than wait for it.
+.It Fl l
+List the audio encodings supported by the device and if it emulated
+or not. Useful to know what format to use when using
+.Fl m .
+.It Fl m
+Use mmap on the audio device.
.It Fl P
When combined with the
.Fl f
diff --git a/usr.bin/audio/play/play.c b/usr.bin/audio/play/play.c
index b273a6ff94db..790608f57595 100644
--- a/usr.bin/audio/play/play.c
+++ b/usr.bin/audio/play/play.c
@@ -37,6 +37,8 @@ __RCSID("$NetBSD: play.c,v 1.56 2018/11/16 13:55:17 mlelstv Exp $");
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/event.h>
#include <err.h>
#include <fcntl.h>
@@ -46,16 +48,20 @@ __RCSID("$NetBSD: play.c,v 1.56 2018/11/16 13:55:17 mlelstv Exp $");
#include <string.h>
#include <unistd.h>
#include <util.h>
+#include <stdarg.h>
#include <paths.h>
#include "libaudio.h"
static void usage(void) __dead;
-static void play(char *);
-static void play_fd(const char *, int);
+static void play(const char *);
static ssize_t audioctl_write_fromhdr(void *, size_t, int, off_t *, const char *);
static void cleanup(int) __dead;
+static int audio_write(void *, size_t);
+//static void s_err_aux(void (*wf)(char const*, va_list), int, const char*, va_list);
+static void s_errx(int, const char*, ...);
+static void s_err(int, const char*, ...);
static audio_info_t info;
static int volume;
@@ -63,18 +69,65 @@ static int balance;
static int port;
static int fflag;
static int qflag;
-int verbose;
+int verbose = 1;
static int sample_rate;
static int encoding;
static char *encoding_str;
static int precision;
static int channels;
+static int audio_memmap = 0;
+static int file_memmap = 1;
+static void *oaddr = NULL;
+static size_t sizet_filesize;
static char const *play_errstring = NULL;
static size_t bufsize;
-static int audiofd;
+static int audiofd = -1;
static int exitstatus = EXIT_SUCCESS;
+typedef struct audiomm {
+ int kq;
+ void* addr;
+ size_t size;
+ char* inp; //addr to be written next
+ char* outp;
+} audiomm_t;
+static audiomm_t g_aumm;
+
+static int
+init_aumm(audiomm_t *aumm)
+{
+ struct kevent ev;
+
+ aumm->addr = mmap(0, bufsize, PROT_WRITE, MAP_SHARED, audiofd, 0);
+ if (aumm->addr == MAP_FAILED)
+ return -1;
+ (void)madvise(aumm->addr, bufsize, MADV_SEQUENTIAL);
+ aumm->size = bufsize;
+
+ if ((aumm->kq = kqueue()) == -1) {
+ warn("kqueue");
+ goto cant_mmap;
+ }
+ EV_SET(&ev, audiofd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
+ if (kevent(aumm->kq, &ev, 1, NULL, 0, NULL) == -1) {
+ warn("kevent");
+ goto cant_mmap;
+ }
+ if (kevent(aumm->kq, NULL, 0, &ev, 1, NULL) == -1) {
+ warn("kevent");
+ goto cant_mmap;
+ }
+ aumm->inp = aumm->outp = (char*)aumm->addr + ev.data;
+
+ return 0;
+cant_mmap:
+ if (munmap(aumm->addr, bufsize) < 0)
+ warn("munmap");
+ aumm->addr = MAP_FAILED;
+ return -1;
+}
+
int
main(int argc, char *argv[])
{
@@ -83,13 +136,14 @@ main(int argc, char *argv[])
int iflag = 0;
const char *defdevice = _PATH_SOUND;
const char *device = NULL;
+ int list_audio_encodings = 0;
- while ((ch = getopt(argc, argv, "b:B:C:c:d:e:fhip:P:qs:Vv:")) != -1) {
+ while ((ch = getopt(argc, argv, "b:B:C:c:d:e:fhilmp:P:qs:Vv:")) != -1) {
switch (ch) {
case 'b':
decode_int(optarg, &balance);
if (balance < 0 || balance > 64)
- errx(1, "balance must be between 0 and 63");
+ s_errx(EXIT_FAILURE, "balance must be between 0 and 63");
break;
case 'B':
bufsize = strsuftoll("write buffer size", optarg,
@@ -98,7 +152,7 @@ main(int argc, char *argv[])
case 'c':
decode_int(optarg, &channels);
if (channels < 0)
- errx(1, "channels must be positive");
+ s_errx(EXIT_FAILURE, "channels must be positive");
break;
case 'C':
/* Ignore, compatibility */
@@ -115,6 +169,12 @@ main(int argc, char *argv[])
case 'i':
iflag++;
break;
+ case 'l':
+ list_audio_encodings = 1;
+ break;
+ case 'm':
+ audio_memmap = 1;
+ break;
case 'q':
qflag++;
break;
@@ -123,7 +183,7 @@ main(int argc, char *argv[])
if (precision != 4 && precision != 8 &&
precision != 16 && precision != 24 &&
precision != 32)
- errx(1, "precision must be between 4, 8, 16, 24 or 32");
+ s_errx(EXIT_FAILURE, "precision must be between 4, 8, 16, 24 or 32");
break;
case 'p':
len = strlen(optarg);
@@ -135,13 +195,13 @@ main(int argc, char *argv[])
else if (strncmp(optarg, "line", len) == 0)
port |= AUDIO_LINE_OUT;
else
- errx(1,
+ s_errx(EXIT_FAILURE,
"port must be `speaker', `headphone', or `line'");
break;
case 's':
decode_int(optarg, &sample_rate);
if (sample_rate < 0 || sample_rate > 48000 * 2) /* XXX */
- errx(1, "sample rate must be between 0 and 96000");
+ s_errx(EXIT_FAILURE, "sample rate must be between 0 and 96000");
break;
case 'V':
verbose++;
@@ -149,7 +209,7 @@ main(int argc, char *argv[])
case 'v':
volume = atoi(optarg);
if (volume < 0 || volume > 255)
- errx(1, "volume must be between 0 and 255");
+ s_errx(EXIT_FAILURE, "volume must be between 0 and 255");
break;
/* case 'h': */
default:
@@ -163,7 +223,7 @@ main(int argc, char *argv[])
if (encoding_str) {
encoding = audio_enc_to_val(encoding_str);
if (encoding == -1)
- errx(1, "unknown encoding, bailing...");
+ s_errx(EXIT_FAILURE, "unknown encoding, bailing...");
}
if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
@@ -177,14 +237,29 @@ main(int argc, char *argv[])
}
if (audiofd < 0)
- err(1, "failed to open %s", device);
+ s_err(EXIT_FAILURE, "failed to open %s", device);
if (ioctl(audiofd, AUDIO_GETINFO, &info) < 0)
- err(1, "failed to get audio info");
+ s_err(EXIT_FAILURE, "failed to get audio info");
if (bufsize == 0) {
bufsize = info.play.buffer_size;
if (bufsize < 32 * 1024)
- bufsize = 32 * 1024;
+ bufsize = 32 * 1024; //XXX: when audio_memmap, use this as size, do something about that!
+ }
+
+ if (list_audio_encodings) {
+ audio_encoding_t ae = { .index = 0 };
+
+ while (-1 != ioctl(audiofd, AUDIO_GETENC, &ae)) {
+ printf("%d: %s (%d bits)", ae.index, ae.name, ae.precision);
+ if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED)
+ printf(" emulated\n");
+ else
+ printf("\n");
+ ++ae.index;
+ }
+
+ cleanup(0);
}
signal(SIGINT, cleanup);
@@ -196,72 +271,119 @@ main(int argc, char *argv[])
play(*argv++);
while (*argv);
else
- play_fd("standard input", STDIN_FILENO);
+ play("-");
cleanup(0);
}
static void
-cleanup(int signo)
+s_err_aux(void (*warn_func)(const char*, va_list), int e, const char *fmt, va_list ap)
+{
+ exitstatus = e;
+ (*warn_func)(fmt, ap);
+ cleanup(0);
+}
+
+static void
+s_err(int e, const char* fmt, ...)
{
+ va_list ap;
- (void)ioctl(audiofd, AUDIO_FLUSH, NULL);
- (void)ioctl(audiofd, AUDIO_SETINFO, &info);
- close(audiofd);
- if (signo != 0) {
- (void)raise_default_signal(signo);
+ va_start(ap, fmt);
+ s_err_aux(vwarn, e, fmt, ap);
+ va_end(ap);
+}
+
+static void
+s_errx(int e, const char* fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ s_err_aux(vwarnx, e, fmt, ap);
+ va_end(ap);
+}
+
+static void
+cleanup(int signo)
+{
+ if (audiofd != -1) {
+ (void)ioctl(audiofd, AUDIO_FLUSH, NULL);
+ (void)ioctl(audiofd, AUDIO_SETINFO, &info);
+ if (file_memmap) {
+ if (oaddr != MAP_FAILED)
+ (void)munmap(oaddr, sizet_filesize);
+ else if (oaddr)
+ free(oaddr);
+ }
+ close(audiofd);
}
+ if (audio_memmap && g_aumm.addr != MAP_FAILED)
+ (void)munmap(g_aumm.addr, g_aumm.size);
+ if (signo != 0)
+ (void)raise_default_signal(signo);
exit(exitstatus);
}
static void
-play(char *file)
+play(const char *file)
{
struct stat sb;
- void *addr, *oaddr;
+ char* addr;
off_t filesize;
- size_t sizet_filesize;
off_t datasize = 0;
ssize_t hdrlen;
- int fd;
+ int fd, nr = 0, nw;
+ off_t dataout = 0;
if (file[0] == '-' && file[1] == 0) {
- play_fd("standard input", STDIN_FILENO);
- return;
- }
-
- fd = open(file, O_RDONLY);
- if (fd < 0) {
- if (!qflag)
- warn("could not open %s", file);
- exitstatus = EXIT_FAILURE;
- return;
- }
-
- if (fstat(fd, &sb) < 0)
- err(1, "could not fstat %s", file);
- filesize = sb.st_size;
- sizet_filesize = (size_t)filesize;
+ fd = STDIN_FILENO;
+ file = "standard input";
+ file_memmap = 0;
+ } else {
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ if (!qflag)
+ warn("could not open %s", file);
+ return;
+ }
- /*
- * if the file is not a regular file, doesn't fit in a size_t,
- * or if we failed to mmap the file, try to read it instead, so
- * that filesystems, etc, that do not support mmap() work
- */
- if (!S_ISREG(sb.st_mode) ||
- ((off_t)sizet_filesize != filesize) ||
- (oaddr = addr = mmap(0, sizet_filesize, PROT_READ,
- MAP_SHARED, fd, 0)) == MAP_FAILED) {
- play_fd(file, fd);
- close(fd);
- return;
+ if (fstat(fd, &sb) < 0)
+ s_err(EXIT_FAILURE, "could not fstat %s", file);
+ filesize = sb.st_size;
+ sizet_filesize = (size_t)filesize;
+ /*
+ * if the file is not a regular file, doesn't fit in a size_t,
+ * or if we failed to mmap the file, try to read it instead, so
+ * that filesystems, etc, that do not support mmap() work
+ */
+ if (!S_ISREG(sb.st_mode) ||
+ ((off_t)sizet_filesize != filesize) ||
+ (oaddr = addr = mmap(0, sizet_filesize, PROT_READ,
+ MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ if (!qflag)
+ warn("Can't mmap %s\n", file);
+ file_memmap = 0;
+ }
}
-
+ if (!file_memmap) {
+ oaddr = addr = malloc(bufsize);
+ if (addr == NULL)
+ s_err(EXIT_FAILURE, "malloc of read buffer failed");
+ nr = read(fd, addr, bufsize);
+ if (nr < 0)
+ s_err(EXIT_FAILURE, "standard input read error");
+ if (nr == 0)
+ s_err(EXIT_FAILURE, "unexpected EOF");
+ filesize = nr;
+ sizet_filesize = (size_t)filesize;
+ } else {
/*
* give the VM system a bit of a hint about the type
* of accesses we will make. we don't care about errors.
*/
- madvise(addr, sizet_filesize, MADV_SEQUENTIAL);
+ madvise(addr, sizet_filesize, MADV_SEQUENTIAL);
+ }
/*
* get the header length and set up the audio device
@@ -269,97 +391,64 @@ play(char *file)
if ((hdrlen = audioctl_write_fromhdr(addr,
sizet_filesize, audiofd, &datasize, file)) < 0) {
if (play_errstring)
- errx(1, "%s: %s", play_errstring, file);
+ s_errx(EXIT_FAILURE, "%s: %s", play_errstring, file);
else
- errx(1, "unknown audio file: %s", file);
+ s_errx(EXIT_FAILURE, "unknown audio file: %s", file);
}
-
- filesize -= hdrlen;
- addr = (char *)addr + hdrlen;
- if (filesize < datasize || datasize == 0) {
- if (filesize < datasize)
- warnx("bogus datasize: %ld", (u_long)datasize);
- datasize = filesize;
+ if (!file_memmap) {
+ if (hdrlen > 0) {
+ if (hdrlen > nr) /* shouldn't happen */
+ s_errx(EXIT_FAILURE, "header seems really large: %lld", (long long)hdrlen);
+ memmove(addr, addr + hdrlen, nr - hdrlen);
+ nr -= hdrlen;
+ filesize = nr;
+ }
+ } else {
+ filesize -= hdrlen;
+ addr = addr + hdrlen;
+ if (filesize < datasize || datasize == 0) {
+ if (filesize < datasize)
+ warnx("bogus datasize: %ld", (u_long)datasize);
+ datasize = filesize;
+ }
}
- while ((uint64_t)datasize > bufsize) {
- if ((size_t)write(audiofd, addr, bufsize) != bufsize)
- err(1, "write failed");
- addr = (char *)addr + bufsize;
- datasize -= bufsize;
+ if (audio_memmap && init_aumm(&g_aumm) == -1)
+ audio_memmap = 0;
+
+ if (file_memmap) {
+ while (datasize) {
+ nr = MIN(bufsize, datasize);
+ nw = audio_write(addr, nr);
+ if (nw != nr)
+ s_err(EXIT_FAILURE, "write failed");
+ addr = addr + bufsize;
+ datasize -= nr;
+ }
+ } else {
+ while (datasize == 0 || dataout < datasize) {
+ if (datasize != 0 && dataout + nr > datasize)
+ nr = datasize - dataout;
+ nw = audio_write(addr, nr);
+ if (nw != nr)
+ s_err(EXIT_FAILURE, "write failed");
+ dataout += nw;
+ nr = read(fd, addr, bufsize);
+ if (nr == -1)
+ s_err(EXIT_FAILURE, "read failed");
+ if (nr == 0)
+ break;
+ }
}
- if ((off_t)write(audiofd, addr, datasize) != datasize)
- err(1, "final write failed");
if (ioctl(audiofd, AUDIO_DRAIN) < 0 && !qflag)
warn("audio drain ioctl failed");
- if (munmap(oaddr, sizet_filesize) < 0)
- err(1, "munmap failed");
+ if (file_memmap && munmap(oaddr, sizet_filesize) < 0)
+ s_err(EXIT_FAILURE, "munmap failed");
close(fd);
}
-/*
- * play the file on the file descriptor fd
- */
-static void
-play_fd(const char *file, int fd)
-{
- char *buffer = malloc(bufsize);
- ssize_t hdrlen;
- int nr, nw;
- off_t datasize = 0;
- off_t dataout = 0;
-
- if (buffer == NULL)
- err(1, "malloc of read buffer failed");
-
- nr = read(fd, buffer, bufsize);
- if (nr < 0)
- goto read_error;
- if (nr == 0) {
- if (fflag) {
- free(buffer);
- return;
- }
- err(1, "unexpected EOF");
- }
- hdrlen = audioctl_write_fromhdr(buffer, nr, audiofd, &datasize, file);
- if (hdrlen < 0) {
- if (play_errstring)
- errx(1, "%s: %s", play_errstring, file);
- else
- errx(1, "unknown audio file: %s", file);
- }
- if (hdrlen > 0) {
- if (hdrlen > nr) /* shouldn't happen */
- errx(1, "header seems really large: %lld", (long long)hdrlen);
- memmove(buffer, buffer + hdrlen, nr - hdrlen);
- nr -= hdrlen;
- }
- while (datasize == 0 || dataout < datasize) {
- if (datasize != 0 && dataout + nr > datasize)
- nr = datasize - dataout;
- nw = write(audiofd, buffer, nr);
- if (nw != nr)
- goto write_error;
- dataout += nw;
- nr = read(fd, buffer, bufsize);
- if (nr == -1)
- goto read_error;
- if (nr == 0)
- break;
- }
- /* something to think about: no message given for dataout < datasize */
- if (ioctl(audiofd, AUDIO_DRAIN) < 0 && !qflag)
- warn("audio drain ioctl failed");
- return;
-read_error:
- err(1, "read of standard input failed");
-write_error:
- err(1, "audio device write failed");
-}
-
/*
* only support sun and wav audio files so far ...
*
@@ -451,16 +540,95 @@ set_audio_mode:
}
if (ioctl(fd, AUDIO_SETINFO, &info) < 0)
- err(1, "failed to set audio info");
+ s_err(EXIT_FAILURE, "failed to set audio info");
return (hdr_len);
}
+static char*
+circular_write(audiomm_t* aumm, char* raddr, size_t size)
+{
+ char* end_w = aumm->inp + size;
+ size_t partsize;
+
+ if (end_w > (char*)aumm->addr + aumm->size) {
+ end_w = (char*)aumm->addr + aumm->size;
+ partsize = end_w - aumm->inp;
+ memcpy(aumm->inp, raddr, partsize);
+ raddr += partsize;
+ size -= partsize;
+ aumm->inp = (char*)aumm->addr;
+ end_w = aumm->inp + size;
+ }
+
+ memcpy(aumm->inp, raddr, size);
+ aumm->inp += size;
+
+ return (raddr + size);
+}
+
+static u_int
+aumm_space(audiomm_t *aumm)
+{
+ if (aumm->outp >= aumm->inp)
+ return aumm->outp - aumm->inp;
+ return aumm->outp + aumm->size - aumm->inp;
+}
+
+static int
+update_aumm(audiomm_t *aumm)
+{
+ struct kevent ev;
+ int nev;
+
+ nev = kevent(aumm->kq, NULL, 0, &ev, 1, NULL);
+ if (nev == -1) {
+ warn("kevent");
+ return nev;
+ }
+ if (ev.data == (int64_t)bufsize)
+ aumm->outp = (char*)aumm->addr;
+ else
+ aumm->outp = (char*)aumm->addr + ev.data;
+
+ return 0;
+}
+
+static int
+audio_write_mmap(audiomm_t *aumm, void* buffer, size_t nbytes)
+{
+ int written = 0;
+ size_t size;
+
+ while (nbytes) {
+ if ((size = aumm_space(aumm)) == 0) {
+ if (update_aumm(aumm)) {
+ warn("update_aumm");
+ return -1;
+ }
+ }
+ size = MIN(size, nbytes);
+ buffer = circular_write(aumm, buffer, size);
+ nbytes -= size;
+ written += size;
+ }
+
+ return written;
+}
+
+static int
+audio_write(void* buffer, size_t nbytes)
+{
+ if (audio_memmap)
+ return audio_write_mmap(&g_aumm, buffer, nbytes);
+ return write(audiofd, buffer, nbytes);
+}
+
static void
usage(void)
{
- fprintf(stderr, "Usage: %s [-hiqV] [options] files\n", getprogname());
+ fprintf(stderr, "Usage: %s [-hilmqV] [options] files\n", getprogname());
fprintf(stderr, "Options:\n\t"
"-B buffer size\n\t"
"-b balance (0-63)\n\t"
Home |
Main Index |
Thread Index |
Old Index