home *** CD-ROM | disk | FTP | other *** search
/ ftp.4front-tech.com / ftp.4front-tech.com.tar / ftp.4front-tech.com / ossfree / snd-util-3.8.tar.gz / snd-util-3.8.tar / sndkit / mmap / mmap_test.c
C/C++ Source or Header  |  1998-01-14  |  8KB  |  279 lines

  1. /*
  2.  * This is a simple program which demonstrates use of mmapped DMA buffer
  3.  * of the sound driver directly from application program.
  4.  *
  5.  * This sample program works (currently) only with Linux, FreeBSD and BSD/OS
  6.  * (FreeBSD and BSD/OS require OSS version 3.8 or later.
  7.  *
  8.  * Note! Don't use mmapped DMA buffers (direct audio) unless you have
  9.  * very good reasons to do it. Programs using this feature will not
  10.  * work with all soundcards. GUS (GF1) is one of them (GUS MAX works).
  11.  *
  12.  * This program requires version 3.5 or later of OSS in Linux or
  13.  * version 3.8 or later in FreeBSD and BSD/OS.
  14.  */
  15.  
  16. #include <stdio.h>
  17. #include <unistd.h>
  18. #include <fcntl.h>
  19. #include <sys/types.h>
  20. #include <sys/mman.h>
  21. #include <sys/soundcard.h>
  22. #include <sys/time.h>
  23.  
  24. main()
  25. {
  26.     int fd, sz, fsz, i, tmp, n, l, have_data=0, nfrag;
  27.         int caps;
  28.  
  29.     int sd, sl=0, sp;
  30.  
  31.     unsigned char data[500000], *dp = data;
  32.  
  33.     struct buffmem_desc imemd, omemd;
  34.         caddr_t buf;
  35.     struct timeval tim;
  36.  
  37.     unsigned char *op;
  38.     
  39.         struct audio_buf_info info;
  40.  
  41.     int frag = 0xffff000c;    /* Max # fragments of 2^13=8k bytes */
  42.  
  43.     fd_set writeset;
  44.  
  45.     close(0);
  46.     if ((fd=open("/dev/dsp", O_RDWR, 0))==-1)
  47.     {
  48.         perror("/dev/dsp");
  49.         exit(-1);
  50.     }
  51. /*
  52.  * Then setup sampling parameters. Just sampling rate in this case.
  53.  */
  54.  
  55.     tmp = 48000;
  56.     ioctl(fd, SNDCTL_DSP_SPEED, &tmp);
  57.     printf("Speed set to %d\n", tmp);
  58.  
  59. /*
  60.  * Load some test data.
  61.  */
  62.  
  63.   sl = sp = 0;
  64.   if ((sd=open("smpl", O_RDONLY, 0))!=-1)
  65.   {
  66.     sl = read(sd, data, sizeof(data));
  67.     printf("%d bytes read from file.\n", sl);
  68.     close(sd);
  69.   }
  70.   else perror("smpl");
  71.  
  72.     if (ioctl(fd, SNDCTL_DSP_GETCAPS, &caps)==-1)
  73.     {
  74.         perror("/dev/dsp");
  75.         fprintf(stderr, "Sorry but your sound driver is too old\n");
  76.         exit(-1);
  77.     }
  78.  
  79. /*
  80.  * Check that the device has capability to do this. Currently just
  81.  * CS4231 based cards will work.
  82.  *
  83.  * The application should also check for DSP_CAP_MMAP bit but this
  84.  * version of driver doesn't have it yet.
  85.  */
  86. /*    ioctl(fd, SNDCTL_DSP_SETSYNCRO, 0); */
  87.  
  88. /*
  89.  * You need version 3.5-beta7 or later of the sound driver before next
  90.  * two lines compile. There is no point to modify this program to
  91.  * compile with older driver versions since they don't have working
  92.  * mmap() support.
  93.  */
  94.     if (!(caps & DSP_CAP_TRIGGER) ||
  95.         !(caps & DSP_CAP_MMAP))
  96.     {
  97.         fprintf(stderr, "Sorry but your soundcard can't do this\n");
  98.         exit(-1);
  99.     }
  100.  
  101. /*
  102.  * Select the fragment size. This is propably important only when
  103.  * the program uses select(). Fragment size defines how often
  104.  * select call returns.
  105.  */
  106.  
  107.     ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
  108.  
  109. /*
  110.  * Compute total size of the buffer. It's important to use this value
  111.  * in mmap() call.
  112.  */
  113.  
  114.     if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)==-1)
  115.     {
  116.         perror("GETOSPACE");
  117.         exit(-1);
  118.     }
  119.  
  120.     sz = info.fragstotal * info.fragsize;
  121.     fsz = info.fragsize;
  122.  
  123. /*
  124.  * Call mmap().
  125.  * 
  126.  * IMPORTANT NOTE!!!!!!!!!!!
  127.  *
  128.  * Full duplex audio devices have separate input and output buffers. 
  129.  * It is not possible to map both of them at the same mmap() call. The buffer
  130.  * is selected based on the prot argument in the following way:
  131.  *
  132.  * - PROT_READ (alone) selects the input buffer.
  133.  * - PROT_WRITE (alone) selects the output buffer.
  134.  * - PROT_WRITE|PROT_READ together select the output buffer. This combination
  135.  *   is required in BSD to make the buffer accessible. With just PROT_WRITE
  136.  *   every attempt to access the returned buffer will result in segmentation/bus
  137.  *   error. PROT_READ|PROT_WRITE is also permitted in Linux with OSS version
  138.  *   3.8-beta16 and later (earlier versions don't accept it).
  139.  *
  140.  * Non duplex devices have just one buffer. When an application wants to do both
  141.  * input and output it's recommended that the device is closed and re-opened when
  142.  * switching between modes. PROT_READ|PROT_WRITE can be used to open the buffer
  143.  * for both input and output (with OSS 3.8-beta16 and later) but the result may be
  144.  * unpredictable.
  145.  */
  146.  
  147.     if ((buf=mmap(NULL, sz, PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t)-1)
  148.       {
  149.         perror("mmap (write)");
  150.         exit(-1);
  151.     }
  152.     printf("mmap (out) returned %08x\n", buf);
  153.     op=buf;
  154.  
  155. /*
  156.  * op contains now a pointer to the DMA buffer
  157.  */
  158.  
  159. /*
  160.  * Then it's time to start the engine. The driver doesn't allow read() and/or
  161.  * write() when the buffer is mapped. So the only way to start operation is
  162.  * to togle device's enable bits. First set them off. Setting them on enables
  163.  * recording and/or playback.
  164.  */
  165.  
  166.     tmp = 0;
  167.     ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
  168.     printf("Trigger set to %08x\n", tmp);
  169.  
  170. /*
  171.  * It might be usefull to write some data to the buffer before starting.
  172.  */
  173.  
  174.     tmp = PCM_ENABLE_OUTPUT;
  175.     ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
  176.     printf("Trigger set to %08x\n", tmp);
  177.  
  178. /*
  179.  * The machine is up and running now. Use SNDCTL_DSP_GETOPTR to get the
  180.  * buffer status.
  181.  *
  182.  * NOTE! The driver empties each buffer fragmen after they have been
  183.  * played. This prevents looping sound if there are some performance problems
  184.  * in the application side. For similar reasons it recommended that the
  185.  * application uses some amout of play ahead. It can rewrite the unplayed
  186.  * data later if necessary.
  187.  */
  188.  
  189.     nfrag = 0;
  190.     while (1)
  191.     {
  192.         struct count_info count;
  193.         int p, l, extra;
  194.  
  195.         FD_ZERO(&writeset);
  196.         FD_SET(fd, &writeset);
  197.  
  198.         tim.tv_sec = 10;
  199.         tim.tv_usec= 0;
  200.  
  201.         select(fd+1, &writeset, &writeset, NULL, NULL);
  202. /*
  203.  * SNDCTL_DSP_GETOPTR (and GETIPTR as well) return three items. The
  204.  * bytes field returns number of bytes played since start. It can be used
  205.  * as a real time clock.
  206.  *
  207.  * The blocks field returns number of fragment transitions (interrupts) since
  208.  * previous GETOPTR call. It can be used as a method to detect underrun 
  209.  * situations.
  210.  *
  211.  * The ptr field is the DMA pointer inside the buffer area (in bytes from
  212.  * the beginning of total buffer area).
  213.  */
  214.  
  215.         if (ioctl(fd, SNDCTL_DSP_GETOPTR, &count)==-1)
  216.         {
  217.             perror("GETOPTR");
  218.             exit(-1);
  219.         }
  220.  
  221.         nfrag += count.blocks;
  222.  
  223. #ifdef VERBOSE
  224.  
  225.         printf("\rTotal: %09d, Fragment: %03d, Ptr: %06d",
  226.             count.bytes, nfrag, count.ptr);
  227.         fflush(stdout);
  228. #endif
  229.  
  230. /*
  231.  * Caution! This version doesn't check for bounds of the DMA
  232.  * memory area. It's possible that the returned pointer value is not aligned
  233.  * to fragment boundaries. It may be several samples behind the boundary
  234.  * in case there was extra delay between the actual hardware interrupt and
  235.  * the time when DSP_GETOPTR was called.
  236.  *
  237.  * Don't just call memcpy() with length set to 'fragment_size' without
  238.  * first checking that the transfer really fits to the buffer area.
  239.  * A mistake of just one byte causes seg fault. It may be easiest just
  240.  * to align the returned pointer value to fragment boundary before using it.
  241.  *
  242.  * It would be very good idea to write few extra samples to next fragment
  243.  * too. Otherwise several (uninitialized) samples from next fragment
  244.  * will get played before your program gets chance to initialize them.
  245.  * Take in count the fact thaat there are other processes batling about
  246.  * the same CPU. This effect is likely to be very annoying if fragment
  247.  * size is decreased too much.
  248.  */
  249.  
  250. /*
  251.  * Just a minor clarification to the above. The following line alings
  252.  * the pointer to fragment boundaries. Note! Don't trust that fragment
  253.  * size is always a power of 2. It may not be so in future.
  254.  */
  255.         count.ptr = (count.ptr/fsz)*fsz;
  256.  
  257. #ifdef VERBOSE
  258.         printf(" memcpy(%6d, %4d)", (dp-data), fsz);
  259.         fflush(stdout);
  260. #endif
  261.  
  262. /*
  263.  * Set few bytes in the beginning of next fragment too.
  264.  */
  265.         if ((count.ptr+fsz+16) < sz)    /* Last fragment? */
  266.            extra = 16;
  267.         else
  268.            extra = 0;
  269.  
  270.         memcpy(op+count.ptr, dp, fsz+extra);
  271.         
  272.         dp += fsz;
  273.         if (dp > (data+sl-fsz))
  274.            dp = data;
  275.     }
  276.  
  277.     exit(0);
  278. }
  279.