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