home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 2: Applications / Linux Cubed Series 2 - Applications.iso / editors / emacs / xemacs / xemacs-1.004 / xemacs-1 / xemacs-19.13 / src / linuxplay.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-08-02  |  14.8 KB  |  531 lines

  1. /* linuxplay.c - play a sound file on the speaker
  2.  **
  3.  ** Copyright (C) 1995 by Markus Gutschke (gutschk@math.uni-muenster.de)
  4.  **
  5.  ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by
  6.  ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further
  7.  ** information.
  8.  **
  9.  ** Permission to use, copy, modify, and distribute this software and its
  10.  ** documentation for any purpose and without fee is hereby granted, provided
  11.  ** that the above copyright notice appear in all copies and that both that
  12.  ** copyright notice and this permission notice appear in supporting
  13.  ** documentation.  This software is provided "as is" without express or
  14.  ** implied warranty.
  15.  **
  16.  ** Currently, only SunAudio, Wave, and RAW file formats are actively
  17.  ** supported; VOC file format is detected (and rejected :-( )
  18.  */
  19.  
  20. #define HEADERSZ  48    /* has to be at least as big as the biggest header   */
  21. #define SNDBUFSZ  2048  /* has to be at least as big as HEADERSZ             */
  22.  
  23. /* XEmacs beta testers say:  undef this by default. */
  24. #undef NOVOLUMECTRLFORMULAW /* Changing the volume for uLaw-encoded
  25.                    samples sounds very poor; possibly,
  26.                    this is true only for the PC-Snd
  27.                    driver, so undefine this symbol at your
  28.                    discretion */
  29.  
  30. #ifdef HAVE_CONFIG_H
  31. #include <config.h>
  32. #endif
  33.  
  34. #include <errno.h>
  35. #include <fcntl.h>
  36. #include <linux/soundcard.h>
  37. #include <stdio.h>
  38. #include <stdlib.h>
  39. #include <string.h>
  40. #include <sys/fcntl.h>
  41. #include <sys/file.h>
  42. #include <sys/ioctl.h>
  43. #include <sys/signal.h>
  44. #include <unistd.h>
  45.  
  46. #ifdef LINUXPLAYSTANDALONE
  47. #define perror(str) fprintf(stderr,"audio: %s %s\n",str,strerror(errno));
  48. #define warn(str)   fprintf(stderr,"audio: %s\n",str);
  49. #else
  50. #include "lisp.h"
  51. #define perror(str) message("audio: %s, %s ",str,strerror(errno))
  52. #define warn(str)   message("audio: %s ",GETTEXT(str))
  53. #endif
  54.  
  55. #ifdef __GNUC__
  56. #define UNUSED(x) ((void)(x))
  57. #else
  58. #define UNUSED(x)
  59. #define __inline__
  60. #endif
  61.  
  62. static __sighandler_t sighup_handler;
  63. static __sighandler_t sigint_handler;
  64.  
  65. static union {
  66.   struct {
  67.     int           align;
  68.     enum {wvMain,wvSubchunk,wvOutOfBlock,wvSkipChunk,
  69.       wvSoundChunk,wvFatal,wvFatalNotify} state;
  70.     size_t        left;
  71.     unsigned char leftover[HEADERSZ];
  72.     unsigned long chunklength;
  73.   } wave;
  74. } parsestate;
  75.  
  76. static unsigned char sndbuf[SNDBUFSZ];
  77. static int           mix_fd    = -1;
  78. static int           audio_vol = -1;
  79. static int           audio_fd  = -1;
  80. static char         *audio_dev = "";
  81.  
  82. typedef enum {fmtIllegal,fmtRaw,fmtVoc,fmtWave,fmtSunAudio} fmtType;
  83.  
  84. static void sighandler(int sig)
  85. {
  86.   if (audio_fd > 0) {
  87.     ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
  88.     close(audio_fd);
  89.     audio_fd = -1; }
  90.   if (mix_fd > 0) {
  91.     if (audio_vol >= 0) {
  92.       ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol);
  93.       audio_vol = -1; }
  94.     close(mix_fd);
  95.     mix_fd = -1; }
  96.   if (sig == SIGHUP && sighup_handler)      sighup_handler(sig);
  97.   else if (sig == SIGINT && sigint_handler) sigint_handler(sig);
  98.   else exit(1);
  99. }
  100.  
  101. static size_t parseraw(void **data,size_t *sz,void **outbuf)
  102. {
  103.   int rc = *sz;
  104.  
  105.   *outbuf = *data;
  106.   *sz = 0;
  107.   return(rc);
  108. }
  109.  
  110. static size_t parsevoc(void **data,size_t *sz,void **outbuf)
  111. {
  112.   UNUSED(data);
  113.   UNUSED(sz);
  114.   UNUSED(outbuf);
  115.   return(0);
  116. }
  117.  
  118. static __inline__ int waverequire(void **data,size_t *sz,size_t rq)
  119. {
  120.   int rc = 1;
  121.  
  122.   if (rq > HEADERSZ) {
  123.     warn("Header size exceeded while parsing WAVE file");
  124.     parsestate.wave.state = wvFatal;
  125.     *sz = 0;
  126.     return(0); }
  127.   if ((rq -= parsestate.wave.left) <= 0)
  128.     return(rc);
  129.   if (rq > *sz) {rq = *sz; rc = 0;}
  130.   memcpy(parsestate.wave.leftover+parsestate.wave.left,
  131.      *data,rq);
  132.   parsestate.wave.left     += rq;
  133.   ((unsigned char *)*data) += rq;
  134.   *sz                      -= rq;
  135.   return(rc);
  136. }
  137.  
  138. static __inline__ void waveremove(size_t rq)
  139. {
  140.   if (parsestate.wave.left <= rq)
  141.     parsestate.wave.left = 0;
  142.   else {
  143.     parsestate.wave.left -= rq;
  144.     memmove(parsestate.wave.leftover,
  145.         parsestate.wave.leftover+rq,
  146.         parsestate.wave.left); }
  147.   return;
  148. }
  149.  
  150. static size_t parsewave(void **data,size_t *sz,void **outbuf)
  151. {
  152.   for (;;)
  153.     switch (parsestate.wave.state) {
  154.     case wvMain:
  155.       if (!waverequire(data,sz,20))
  156.     return(0);
  157.       /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec  */
  158.       parsestate.wave.chunklength = parsestate.wave.leftover[16] +
  159.     256*(parsestate.wave.leftover[17] +
  160.          256*(parsestate.wave.leftover[18] +
  161.           256*parsestate.wave.leftover[19]));
  162.       waveremove(20);
  163.       parsestate.wave.state = wvSubchunk;
  164.       break;
  165.     case wvSubchunk:
  166.       if (!waverequire(data,sz,parsestate.wave.chunklength))
  167.     return(0);
  168.       parsestate.wave.align = parsestate.wave.chunklength < 14 ? 1
  169.     : parsestate.wave.leftover[2] * parsestate.wave.leftover[12];
  170.       if (parsestate.wave.align != 1 &&
  171.       parsestate.wave.align != 2 &&
  172.       parsestate.wave.align != 4) {
  173.     warn("Illegal datawidth detected while parsing WAVE file");
  174.     parsestate.wave.state = wvFatal; }
  175.       else
  176.     parsestate.wave.state = wvOutOfBlock;
  177.       waveremove(parsestate.wave.chunklength);
  178.       break;
  179.     case wvOutOfBlock:
  180.       if (!waverequire(data,sz,8))
  181.     return(0);
  182.       /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec  */
  183.       parsestate.wave.chunklength = parsestate.wave.leftover[4] +
  184.     256*(parsestate.wave.leftover[5] +
  185.          256*(parsestate.wave.leftover[6] +
  186.           256*parsestate.wave.leftover[7]));
  187.       if (memcmp(parsestate.wave.leftover,"data",4))
  188.     parsestate.wave.state = wvSkipChunk;
  189.       else
  190.     if (parsestate.wave.chunklength % parsestate.wave.align) {
  191.       warn("Datawidth does not match chunksize in WAVE file");
  192.       parsestate.wave.state = wvFatal; }
  193.     else
  194.       parsestate.wave.state = wvSoundChunk;
  195.       waveremove(8);
  196.       break;
  197.     case wvSkipChunk:
  198.       if (*sz < parsestate.wave.chunklength) {
  199.     parsestate.wave.chunklength -= *sz;
  200.     *sz = 0; }
  201.       else {
  202.     *sz -= parsestate.wave.chunklength;
  203.     ((unsigned char *)*data) += parsestate.wave.chunklength;
  204.     parsestate.wave.state = wvOutOfBlock; }
  205.       break;
  206.     case wvSoundChunk: {
  207.       size_t count,rq;
  208.       if (parsestate.wave.left) { /* handle leftover bytes from last
  209.                      alignment operation */
  210.     count = parsestate.wave.left;
  211.     rq    = HEADERSZ-count;
  212.     if (rq > parsestate.wave.chunklength)
  213.       rq = parsestate.wave.chunklength;
  214.     if (!waverequire(data,sz,rq)) {
  215.       parsestate.wave.chunklength -= parsestate.wave.left - count;
  216.       return(0); }
  217.     parsestate.wave.chunklength -= rq;
  218.     *outbuf                      = parsestate.wave.leftover;
  219.     parsestate.wave.left         = 0;
  220.     return(rq); }
  221.       if (*sz >= parsestate.wave.chunklength) {
  222.     count  = parsestate.wave.chunklength;
  223.     rq     = 0; }
  224.       else {
  225.     count  = *sz;
  226.     count -= rq = count % parsestate.wave.align; }
  227.       *outbuf                   = *data;
  228.       ((unsigned char *)*data) += count;
  229.       *sz                      -= count;
  230.       if ((parsestate.wave.chunklength -= count) <= 0)
  231.     parsestate.wave.state = wvOutOfBlock;
  232.       else if (rq)
  233.     /* align data length to a multiple of datasize; keep additional data
  234.        in "leftover" buffer --- this is neccessary to ensure proper
  235.        functioning of the sndcnv... routines */
  236.     waverequire(data,sz,rq);
  237.       return(count); }
  238.     case wvFatalNotify:
  239.       warn("Irrecoverable error while parsing WAVE file");
  240.       parsestate.wave.state = wvFatal;
  241.       break;
  242.     case wvFatal:
  243.     default:
  244.       *sz = 0;
  245.       return(0); }
  246. }
  247.  
  248. static size_t sndcnvnop(void **data,size_t *sz,void **outbuf)
  249. {
  250.   int rc = *sz;
  251.  
  252.   *outbuf = *data;
  253.   *sz = 0;
  254.   return(rc);
  255. }
  256.  
  257. static size_t sndcnv2mono(void **data,size_t *sz,void **outbuf)
  258. {
  259.   register unsigned char *src;
  260.   register unsigned char *dest;
  261.   int rc,count;
  262.  
  263.   count = *sz / 2;
  264.   if (count > SNDBUFSZ) {
  265.     *sz  -= 2*SNDBUFSZ;
  266.     count = SNDBUFSZ; }
  267.   else
  268.     *sz   = 0;
  269.   rc      = count;
  270.   src     = *data;
  271.   *outbuf =
  272.     dest  = sndbuf;
  273.   while (count--)
  274.     *dest++ = (unsigned char)(((int)*((unsigned char *)src)++ +
  275.                    (int)*((unsigned char *)src)++) / 2);
  276.   *data   = src;
  277.   return(rc);
  278. }
  279.  
  280. static size_t sndcnv2byte(void **data,size_t *sz,void **outbuf)
  281. {
  282.   register unsigned char *src;
  283.   register unsigned char *dest;
  284.   int rc,count;
  285.  
  286.   count = *sz / 2;
  287.   if (count > SNDBUFSZ) {
  288.     *sz  -= 2*SNDBUFSZ;
  289.     count = SNDBUFSZ; }
  290.   else
  291.     *sz   = 0;
  292.   rc      = count;
  293.   src     = *data;
  294.   *outbuf =
  295.     dest  = sndbuf;
  296.   while (count--) {
  297.     *dest++ = (unsigned char)(((signed char *)src)[1] + 0x80);
  298.     ((char *)src) += 2; }
  299.   *data   = src;
  300.   return(rc);
  301. }
  302.  
  303. static size_t sndcnv2monobyte(void **data,size_t *sz,void **outbuf)
  304. {
  305.   register unsigned char *src;
  306.   register unsigned char *dest;
  307.   int rc,count;
  308.  
  309.   count = *sz / 4;
  310.   if (count > SNDBUFSZ) {
  311.     *sz  -= 4*SNDBUFSZ;
  312.     count = SNDBUFSZ; }
  313.   else
  314.     *sz   = 0;
  315.   rc      = count;
  316.   src     = *data;
  317.   *outbuf =
  318.     dest  = sndbuf;
  319.   while (count--) {
  320.     *dest++ = (unsigned char)(((int)((signed char *)src)[1] +
  321.                    (int)((signed char *)src)[3]) / 2 + 0x80);
  322.     ((char *)src) += 4; }
  323.   *data   = src;
  324.   return(rc);
  325. }
  326.  
  327. static fmtType analyze_format(unsigned char *format,int *fmt,int *speed,
  328.                   int *tracks,
  329.                   size_t (**parsesndfile)(void **,size_t *sz,
  330.                               void **))
  331. {
  332.   /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec  */
  333.   if (!memcmp(format,"Creative Voice File\0x1A\0x1A\x00",22) &&
  334.       (format[22]+256*format[23]+0x1233)&0xFFFF ==
  335.       (format[24]+256*format[25])) { /* VOC */
  336.     *fmt          = AFMT_U8;
  337.     *speed        = 8000;
  338.     *tracks       = 2;
  339.     *parsesndfile = parsevoc;
  340.     return(fmtVoc); }
  341.   else if (!memcmp(format,"RIFF",4) &&
  342.        !memcmp(format+8,"WAVEfmt ",8)) { /* WAVE */
  343.     if (memcmp(format+20,"\001\000\001"/* PCM mono */,4) &&
  344.     memcmp(format+20,"\001\000\002"/* PCM stereo */,4))
  345.       return(fmtIllegal);
  346.     *fmt          = format[32] == 1 ? AFMT_U8 : AFMT_S16_LE;
  347.     *tracks       = format[22];
  348.     /* Keep compatibility with Linux 68k, etc. by not relying on byte-sec  */
  349.     *speed        = format[24]+256*(format[25]+256*
  350.                     (format[26]+256*format[27]));
  351.     *parsesndfile = parsewave;
  352.     return(fmtWave); }
  353.   else if (!memcmp(format,".snd",4)) { /* Sun Audio */
  354.     *fmt          = AFMT_MU_LAW;
  355.     *speed        = 8000;
  356.     *tracks       = 1;
  357.     *parsesndfile = parseraw;
  358.     return(fmtSunAudio); }
  359.   else {
  360.     *fmt          = AFMT_U8;
  361.     *speed        = 8000;
  362.     *tracks       = 1;
  363.     *parsesndfile = parseraw;
  364.     return(fmtRaw); }
  365. }
  366.  
  367. static int audio_init(int mix_fd,int audio_fd,int fmt,int speed,
  368.               int tracks,int *volume,
  369.               size_t (**sndcnv)(void **,size_t *sz,void **))
  370. {
  371.   int i;
  372.  
  373.   *sndcnv = sndcnvnop;
  374.  
  375.   if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
  376.     perror("SNDCTL_DSP_SYNC");
  377.     return(0); }
  378.  
  379.   if (((i=fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
  380.       fmt != i) {
  381.     if (fmt == AFMT_S16_LE) {
  382.       *sndcnv = sndcnv2mono;
  383.       if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
  384.       fmt != i) {
  385.     perror("SNDCTL_DSP_SETFMT");
  386.     return(0); } }
  387.     else {
  388.       perror("SNDCTL_DSP_SETFMT");
  389.       return(0); } }
  390.  
  391.   if (((i=tracks-1),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 ||
  392.       tracks-1 != i) {
  393.     if (tracks == 2) {
  394.       *sndcnv = *sndcnv == sndcnv2mono ? sndcnv2monobyte : sndcnv2byte;
  395.       if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) {
  396.     perror("SNDCTL_DSP_STEREO");
  397.     return(0); } }
  398.     else {
  399.       perror("SNDCTL_DSP_STEREO");
  400.       return(0); } }
  401.  
  402.   if (((i=speed),ioctl(audio_fd,SNDCTL_DSP_SPEED,&i)) < 0 ||
  403.       speed*11 < i*10 || speed*9 > i*10) {
  404.     char buffer[256];
  405.     sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,i);
  406.     perror(buffer);
  407.     return(0); }
  408.  
  409.   if (mix_fd > 0) {
  410.     int vol = *volume & 0xFF;
  411.     if (ioctl(mix_fd,SOUND_MIXER_READ_VOLUME,volume) < 0)
  412.       *volume = -1;
  413.     if (vol < 0) vol = 0; else if (vol > 100) vol = 100;
  414. #ifdef NOVOLUMECTRLFORMULAW
  415.     if (fmt == AFMT_MU_LAW)
  416.       vol = 100;
  417. #endif
  418.     /* vol  = (127*vol)/100; */ /* not using full volume sounds a lot better */
  419.     vol |= 256*vol;
  420.     /* Do not signal an error, if volume control is unavailable! */
  421.     ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&vol); }
  422.   return(1);
  423. }
  424.  
  425. static void linux_play_data_or_file(int fd,unsigned char *data,
  426.                     int length,int volume)
  427. {
  428.   size_t         (*parsesndfile)(void **data,size_t *sz,void **outbuf);
  429.   size_t         (*sndcnv)(void **data,size_t *sz,void **);
  430.   fmtType        ffmt;
  431.   int            fmt,speed,tracks;
  432.   unsigned char *pptr,*optr,*cptr,*sptr;
  433.   int            wrtn,rrtn,crtn,prtn;
  434.  
  435.   if (!data || length < HEADERSZ)
  436.     if (fd < 0) return;
  437.     else {
  438.       length = read(fd,sndbuf,SNDBUFSZ);
  439.       if (length < HEADERSZ)
  440.     return;
  441.       data   = sndbuf;
  442.       length = SNDBUFSZ; }
  443.  
  444.   ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile);
  445.  
  446.   if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) {
  447.     warn("Unsupported file format (neither RAW, nor SunAudio, nor WAVE)");
  448.       return; }
  449.  
  450.   mix_fd = open("/dev/mixer",(O_WRONLY|O_NDELAY),0);
  451.  
  452.   if ((audio_fd = open((audio_dev = ffmt==fmtSunAudio?"/dev/audio":"/dev/dsp"),
  453.                (O_WRONLY|O_NDELAY),0)) < 0) {
  454.     perror(audio_dev);
  455.     if (mix_fd > 0) { close(mix_fd); mix_fd = -1; }
  456.     return; }
  457.  
  458.   sighup_handler = signal(SIGHUP,(__sighandler_t)sighandler);
  459.   sigint_handler = signal(SIGINT,(__sighandler_t)sighandler);
  460.  
  461.   if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv))
  462.     goto END_OF_PLAY;
  463.   audio_vol = volume;
  464.  
  465.   memset(&parsestate,0,sizeof(parsestate));
  466.  
  467.   rrtn = length;
  468.   do {
  469.     for (pptr = data; (prtn = parsesndfile((void **)&pptr,&rrtn,
  470.                        (void **)&optr)) > 0; )
  471.       for (cptr = optr; (crtn = sndcnv((void **)&cptr,&prtn,
  472.                        (void **)&sptr)) > 0; ) {
  473.     for (;;) {
  474.       if ((wrtn = write(audio_fd,sptr,crtn)) < 0) {
  475.         perror("write"); goto END_OF_PLAY; }
  476.       else if (wrtn) break;
  477.       else if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
  478.         perror("SNDCTL_DSP_SYNC"); goto END_OF_PLAY; } }
  479.     if (wrtn != crtn) {
  480.       char buf[255];
  481.       sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn);
  482.       warn(buf);
  483.       goto END_OF_PLAY; } }
  484.     if (fd >= 0) {
  485.       if ((rrtn = read(fd,sndbuf,SNDBUFSZ)) < 0) {
  486.     perror("read"); goto END_OF_PLAY; } }
  487.     else
  488.       break;
  489.   } while (rrtn > 0);
  490.  
  491.   if (ffmt == fmtWave && parsestate.wave.state != wvOutOfBlock &&
  492.       parsestate.wave.state != wvFatal)
  493.     warn("Unexpected end of WAVE file");
  494.  
  495.   ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
  496. END_OF_PLAY:
  497.   ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
  498.  
  499.   signal(SIGHUP,sighup_handler);
  500.   signal(SIGINT,sigint_handler);
  501.  
  502.   close(audio_fd);
  503.   audio_fd = -1;
  504.  
  505.   if (mix_fd > 0) {
  506.     if (audio_vol >= 0) {
  507.       ioctl(mix_fd,SOUND_MIXER_WRITE_VOLUME,&audio_vol);
  508.       audio_vol = -1; }
  509.     close(mix_fd); mix_fd = -1; }
  510.  
  511.   return;
  512. }
  513.  
  514. void play_sound_file (char *sound_file, int volume)
  515. {
  516.   int fd;
  517.  
  518.   if ((fd=open(sound_file,O_RDONLY,0)) < 0) {
  519.     perror(sound_file);
  520.     return; }
  521.   linux_play_data_or_file(fd,NULL,0,volume);
  522.   close(fd);
  523.   return;
  524. }
  525.  
  526. void play_sound_data (unsigned char *data, int length, int volume)
  527. {
  528.   linux_play_data_or_file(-1,data,length,volume);
  529.   return;
  530. }
  531.