/* * 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). * * $FreeBSD: src/sys/i386/isa/sound/mmap_test.c,v 1.6 1999/09/04 15:21:28 peter Exp $ * $DragonFly: src/sys/i386/isa/sound/Attic/mmap_test.c,v 1.2 2003/06/17 04:28:38 dillon Exp $ */ #include #include #include #include #include #include #include #include #include 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); }