summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/sound/mmap_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386/isa/sound/mmap_test.c')
-rw-r--r--sys/i386/isa/sound/mmap_test.c265
1 files changed, 265 insertions, 0 deletions
diff --git a/sys/i386/isa/sound/mmap_test.c b/sys/i386/isa/sound/mmap_test.c
new file mode 100644
index 0000000..c6afae9
--- /dev/null
+++ b/sys/i386/isa/sound/mmap_test.c
@@ -0,0 +1,265 @@
+/*
+ * This is a simple program which demonstrates use of mmapped DMA buffer
+ * of the sound driver directly from application program.
+ *
+ * This sample program works (currently) only with Linux, FreeBSD and BSD/OS
+ * (FreeBSD and BSD/OS require OSS version 3.8-beta16 or later.
+ *
+ * Note! Don't use mmapped DMA buffers (direct audio) unless you have
+ * very good reasons to do it. Programs using this feature will not
+ * work with all soundcards. GUS (GF1) is one of them (GUS MAX works).
+ *
+ * This program requires version 3.5-beta7 or later of OSS
+ * (3.8-beta16 or later in FreeBSD and BSD/OS).
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$Id: mmap_test.c,v 1.2 1998/01/16 07:20:34 charnier Exp $";
+#endif /* not lint */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <machine/soundcard.h>
+#include <sys/time.h>
+
+int
+main()
+{
+ int fd, sz, fsz, tmp, nfrag;
+ int caps;
+
+ int sd, sl=0, sp;
+
+ unsigned char data[500000], *dp = data;
+
+ caddr_t buf;
+ struct timeval tim;
+
+ unsigned char *op;
+
+ struct audio_buf_info info;
+
+ int frag = 0xffff000c; /* Max # fragments of 2^13=8k bytes */
+
+ fd_set writeset;
+
+ close(0);
+ if ((fd=open("/dev/dsp", O_RDWR, 0))==-1)
+ err(1, "/dev/dsp");
+/*
+ * Then setup sampling parameters. Just sampling rate in this case.
+ */
+
+ tmp = 8000;
+ ioctl(fd, SNDCTL_DSP_SPEED, &tmp);
+
+/*
+ * Load some test data.
+ */
+
+ sl = sp = 0;
+ if ((sd=open("smpl", O_RDONLY, 0))!=-1)
+ {
+ sl = read(sd, data, sizeof(data));
+ printf("%d bytes read from file.\n", sl);
+ close(sd);
+ }
+ else warn("smpl");
+
+ if (ioctl(fd, SNDCTL_DSP_GETCAPS, &caps)==-1)
+ {
+ warn("sorry but your sound driver is too old");
+ err(1, "/dev/dsp");
+ }
+
+/*
+ * Check that the device has capability to do this. Currently just
+ * CS4231 based cards will work.
+ *
+ * The application should also check for DSP_CAP_MMAP bit but this
+ * version of driver doesn't have it yet.
+ */
+/* ioctl(fd, SNDCTL_DSP_SETSYNCRO, 0); */
+
+/*
+ * You need version 3.5-beta7 or later of the sound driver before next
+ * two lines compile. There is no point to modify this program to
+ * compile with older driver versions since they don't have working
+ * mmap() support.
+ */
+ if (!(caps & DSP_CAP_TRIGGER) ||
+ !(caps & DSP_CAP_MMAP))
+ errx(1, "sorry but your soundcard can't do this");
+
+/*
+ * Select the fragment size. This is propably important only when
+ * the program uses select(). Fragment size defines how often
+ * select call returns.
+ */
+
+ ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
+
+/*
+ * Compute total size of the buffer. It's important to use this value
+ * in mmap() call.
+ */
+
+ if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)==-1)
+ err(1, "GETOSPACE");
+
+ sz = info.fragstotal * info.fragsize;
+ fsz = info.fragsize;
+/*
+ * Call mmap().
+ *
+ * IMPORTANT NOTE!!!!!!!!!!!
+ *
+ * Full duplex audio devices have separate input and output buffers.
+ * It is not possible to map both of them at the same mmap() call. The buffer
+ * is selected based on the prot argument in the following way:
+ *
+ * - PROT_READ (alone) selects the input buffer.
+ * - PROT_WRITE (alone) selects the output buffer.
+ * - PROT_WRITE|PROT_READ together select the output buffer. This combination
+ * is required in BSD to make the buffer accessible. With just PROT_WRITE
+ * every attempt to access the returned buffer will result in segmentation/bus
+ * error. PROT_READ|PROT_WRITE is also permitted in Linux with OSS version
+ * 3.8-beta16 and later (earlier versions don't accept it).
+ *
+ * Non duplex devices have just one buffer. When an application wants to do both
+ * input and output it's recommended that the device is closed and re-opened when
+ * switching between modes. PROT_READ|PROT_WRITE can be used to open the buffer
+ * for both input and output (with OSS 3.8-beta16 and later) but the result may be
+ * unpredictable.
+ */
+
+ if ((buf=mmap(NULL, sz, PROT_WRITE | PROT_READ, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t)-1)
+ err(1, "mmap (write)");
+ printf("mmap (out) returned %08x\n", buf);
+ op=buf;
+
+/*
+ * op contains now a pointer to the DMA buffer
+ */
+
+/*
+ * Then it's time to start the engine. The driver doesn't allow read() and/or
+ * write() when the buffer is mapped. So the only way to start operation is
+ * to togle device's enable bits. First set them off. Setting them on enables
+ * recording and/or playback.
+ */
+
+ tmp = 0;
+ ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
+
+/*
+ * It might be usefull to write some data to the buffer before starting.
+ */
+
+ tmp = PCM_ENABLE_OUTPUT;
+ ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
+
+/*
+ * The machine is up and running now. Use SNDCTL_DSP_GETOPTR to get the
+ * buffer status.
+ *
+ * NOTE! The driver empties each buffer fragmen after they have been
+ * played. This prevents looping sound if there are some performance problems
+ * in the application side. For similar reasons it recommended that the
+ * application uses some amout of play ahead. It can rewrite the unplayed
+ * data later if necessary.
+ */
+
+ nfrag = 0;
+ while (1)
+ {
+ struct count_info count;
+ int extra;
+
+ FD_ZERO(&writeset);
+ FD_SET(fd, &writeset);
+
+ tim.tv_sec = 10;
+ tim.tv_usec= 0;
+
+ select(fd+1, &writeset, &writeset, NULL, NULL);
+/*
+ * SNDCTL_DSP_GETOPTR (and GETIPTR as well) return three items. The
+ * bytes field returns number of bytes played since start. It can be used
+ * as a real time clock.
+ *
+ * The blocks field returns number of fragment transitions (interrupts) since
+ * previous GETOPTR call. It can be used as a method to detect underrun
+ * situations.
+ *
+ * The ptr field is the DMA pointer inside the buffer area (in bytes from
+ * the beginning of total buffer area).
+ */
+
+ if (ioctl(fd, SNDCTL_DSP_GETOPTR, &count)==-1)
+ err(1, "GETOPTR");
+ if (count.ptr < 0 ) count.ptr = 0;
+ nfrag += count.blocks;
+
+
+#ifdef VERBOSE
+
+ printf("\rTotal: %09d, Fragment: %03d, Ptr: %06d",
+ count.bytes, nfrag, count.ptr);
+ fflush(stdout);
+#endif
+
+/*
+ * Caution! This version doesn't check for bounds of the DMA
+ * memory area. It's possible that the returned pointer value is not aligned
+ * to fragment boundaries. It may be several samples behind the boundary
+ * in case there was extra delay between the actual hardware interrupt and
+ * the time when DSP_GETOPTR was called.
+ *
+ * Don't just call memcpy() with length set to 'fragment_size' without
+ * first checking that the transfer really fits to the buffer area.
+ * A mistake of just one byte causes seg fault. It may be easiest just
+ * to align the returned pointer value to fragment boundary before using it.
+ *
+ * It would be very good idea to write few extra samples to next fragment
+ * too. Otherwise several (uninitialized) samples from next fragment
+ * will get played before your program gets chance to initialize them.
+ * Take in count the fact thaat there are other processes batling about
+ * the same CPU. This effect is likely to be very annoying if fragment
+ * size is decreased too much.
+ */
+
+/*
+ * Just a minor clarification to the above. The following line alings
+ * the pointer to fragment boundaries. Note! Don't trust that fragment
+ * size is always a power of 2. It may not be so in future.
+ */
+ count.ptr = ((count.ptr+16)/fsz )*fsz;
+#ifdef VERBOSE
+ printf(" memcpy(%6d, %4d)", (dp-data), fsz);
+ fflush(stdout);
+#endif
+
+/*
+ * Set few bytes in the beginning of next fragment too.
+ */
+
+ if ((count.ptr+fsz+16) < sz) /* Last fragment? */
+ extra = 16;
+ else
+ extra = 0;
+ memcpy(op+count.ptr, dp, (fsz+extra));
+ dp += fsz;
+ if (dp > (data+sl-fsz))
+ dp = data;
+
+ }
+
+ exit(0);
+}
OpenPOWER on IntegriCloud