/* * $Id$ * * This file is part of WorkMan, the civilized CD player library * (c) 1991-1997 by Steven Grimm (original author) * (c) by Dirk Försterling (current 'author' = maintainer) * The maintainer can be contacted by his e-mail address: * milliByte@DeathsDoor.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA * * * Linux digital audio functions. */ #include "include/wm_config.h" static char plat_linux_audio_id[] = "$Id$"; #if defined(__linux__) && defined(BUILD_CDDA) /* { */ #include "include/wm_cdda.h" /* types.h included by wm_cdda.h */ #include #include #include #include #include #include #include #include #define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM /* * Since there's a lag time between writing audio to the audio device and * hearing it, we need to make sure the status indicators correlate to what's * playing out the speaker. Luckily, Solaris gives us some audio * synchronization facilities that make this pretty easy. * * We maintain a circular queue of status information. When we write some * sound to the audio device, we put its status info into the queue. We write * a marker into the audio stream; when the audio device driver encounters the * marker, it increments a field in a status structure. When we see that * field go up, we grab the next status structure from the queue and send it * to the parent process. * * The minimum size of the queue depends on the latency of the audio stream. */ #define QSIZE 500 struct cdda_block queue[QSIZE]; int qtail; int qstart; /* * We only send WMCDDA_PLAYED status messages upstream when the CD is supposed * to be playing; this is used to keep track. */ extern int playing; static int aufd, aucfd; static int raw_audio = 1; /* Can /dev/audio take 44.1KHz stereo? */ /* * For fast linear-to-ulaw mapping, we use a lookup table that's generated * at startup. */ unsigned char *ulawmap, linear_to_ulaw(); char *getenv(); /* * Dummy signal handler so writes to /dev/audio will interrupt. */ static void dummy( void ) { signal(SIGALRM, dummy); } /* * Initialize the audio device. */ void wmaudio_init( void ) { audio_info_t info; char *audiodev, *acdev; int linval; audiodev = getenv("AUDIODEV"); if (audiodev == NULL) audiodev = "/dev/audio"; acdev = malloc(strlen(audiodev) + 4); if (acdev == NULL) { perror("Can't allocate audio control filename"); exit(1); } strcpy(acdev, audiodev); strcat(acdev, "ctl"); aucfd = open(acdev, O_WRONLY, 0); if (aucfd < 0) { perror(acdev); exit(1); } free(acdev); aufd = open(audiodev, O_WRONLY, 0); if (aufd < 0) { perror(audiodev); exit(1); } signal(SIGALRM, dummy); /* * Try to set the device to CD-style audio; we can process it * with the least CPU overhead. */ AUDIO_INITINFO(&info); info.play.sample_rate = 44100; info.play.channels = 2; info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; info.play.pause = 0; info.record.pause = 0; info.monitor_gain = 0; if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) if (errno == EINVAL) { /* * Oh well, so much for that idea. */ AUDIO_INITINFO(&info); info.play.sample_rate = 8000; info.play.channels = 1; info.play.precision = 8; info.play.encoding = AUDIO_ENCODING_ULAW; info.play.pause = 0; info.record.pause = 0; info.monitor_gain = 0; if (ioctl(aufd, AUDIO_SETINFO, &info) < 0) { perror("Can't set up audio device"); exit(1); } /* * Initialize the linear-to-ulaw mapping table. */ if (ulawmap == NULL) ulawmap = malloc(65536); if (ulawmap == NULL) { perror("malloc"); exit(1); } for (linval = 0; linval < 65536; linval++) ulawmap[linval] = linear_to_ulaw(linval-32768); ulawmap += 32768; raw_audio = 0; } else { perror(audiodev); exit(1); } } /* * Get ready to play some sound. */ void wmaudio_ready( void ) { audio_info_t info; /* * Start at the correct queue position. */ if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); qtail = info.play.eof % QSIZE; qstart = qtail; queue[qtail].status = WMCDDA_OK; } /* * Stop the audio immediately. */ void wmaudio_stop( void ) { if (ioctl(aufd, I_FLUSH, FLUSHRW) < 0) perror("flush"); } /* * Close the audio device. */ void wmaudio_close( void ) { wmaudio_stop(); close(aufd); close(aucfd); } /* * Set the volume level. */ void wmaudio_volume(int level) { audio_info_t info; AUDIO_INITINFO(&info); if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); info.play.gain = level; if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); } /* * Set the balance level. */ void wmaudio_balance(int level) { audio_info_t info; AUDIO_INITINFO(&info); if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); level *= AUDIO_RIGHT_BALANCE; info.play.balance = level / 255; if (ioctl(aucfd, AUDIO_SETINFO, &info) < 0) perror("AUDIO_SETINFO"); } /* * Mark the most recent audio block on the queue as the last one. */ void wmaudio_mark_last( void ) { queue[qtail].status = WMCDDA_DONE; } /* * Figure out the most recent status information and send it upstream. */ int wmaudio_send_status( void ) { audio_info_t info; int qhead; /* * Now send the most current status information to our parent. */ if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); qhead = info.play.eof % QSIZE; if (qhead != qstart && playing) { int balance; if (queue[qhead].status != WMCDDA_DONE) queue[qhead].status = WMCDDA_PLAYED; queue[qhead].volume = info.play.gain; queue[qhead].balance = (info.play.balance * 255) / AUDIO_RIGHT_BALANCE; send_status(queue + qhead); qstart = -1; } return (queue[qhead].status == WMCDDA_DONE); } /* * Play some audio and pass a status message upstream, if applicable. * Returns 0 on success. */ int wmaudio_play(unsigned char *rawbuf, long buflen, struct cdda_block *blk) { int i; short *buf16; int alarmcount = 0; struct itimerval it; alarm(1); while (write(aufd, rawbuf, buflen) <= 0) if (errno == EINTR) { if (! raw_audio && alarmcount++ < 5) { /* * 8KHz /dev/audio blocks for several seconds * waiting for its queue to drop below a low * water mark. */ wmaudio_send_status(); timerclear(&it.it_interval); timerclear(&it.it_value); it.it_value.tv_usec = 500000; setitimer(ITIMER_REAL, &it, NULL); continue; } /* close(aufd); close(aucfd); wmaudio_init(); */ wmaudio_stop( void ); alarm(2); continue; } else { blk->status = WMCDDA_ERROR; return (-1); } alarm(0); /* * Mark this spot in the audio stream. * * Marks don't always succeed (if the audio buffer is empty * this call will block forever) so do it asynchronously. */ fcntl(aufd, F_SETFL, O_NONBLOCK); if (write(aufd, rawbuf, 0) < 0) { if (errno != EAGAIN) perror("audio mark"); } else qtail = (qtail + 1) % QSIZE; fcntl(aufd, F_SETFL, 0); queue[qtail] = *blk; if (wmaudio_send_status() < 0) return (-1); else return (0); } /* * Get the current audio state. */ void wmaudio_state(struct cdda_block *blk) { audio_info_t info; int balance; if (ioctl(aucfd, AUDIO_GETINFO, &info) < 0) perror("AUDIO_GETINFO"); blk->volume = info.play.gain; blk->balance = (info.play.balance * 255) / AUDIO_RIGHT_BALANCE; } /* ** This routine converts from linear to ulaw. ** ** Craig Reese: IDA/Supercomputing Research Center ** Joe Campbell: Department of Defense ** 29 September 1989 ** ** References: ** 1) CCITT Recommendation G.711 (very difficult to follow) ** 2) "A New Digital Technique for Implementation of Any ** Continuous PCM Companding Law," Villeret, Michel, ** et al. 1973 IEEE Int. Conf. on Communications, Vol 1, ** 1973, pg. 11.12-11.17 ** 3) MIL-STD-188-113,"Interoperability and Performance Standards ** for Analog-to_Digital Conversion Techniques," ** 17 February 1987 ** ** Input: Signed 16 bit linear sample ** Output: 8 bit ulaw sample */ #define ZEROTRAP /* turn on the trap as per the MIL-STD */ #define BIAS 0x84 /* define the add-in bias for 16 bit samples */ #define CLIP 32635 unsigned char linear_to_ulaw( sample ) int sample; { static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7}; int sign, exponent, mantissa; unsigned char ulawbyte; /* Get the sample into sign-magnitude. */ sign = (sample >> 8) & 0x80; /* set aside the sign */ if ( sign != 0 ) sample = -sample; /* get magnitude */ if ( sample > CLIP ) sample = CLIP; /* clip the magnitude */ /* Convert from 16 bit linear to ulaw. */ sample = sample + BIAS; exponent = exp_lut[( sample >> 7 ) & 0xFF]; mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F; ulawbyte = ~ ( sign | ( exponent << 4 ) | mantissa ); #ifdef ZEROTRAP if ( ulawbyte == 0 ) ulawbyte = 0x02; /* optional CCITT trap */ #endif return ulawbyte; } /* * Downsample a block of CDDA data, if necessary, for playing out an old-style * audio device. */ long wmaudio_convert(unsigned char *rawbuf, long buflen, struct cdda_block *blk) { short *buf16 = (short *)rawbuf; int i, j, samples; int mono_value; unsigned char *rbend = rawbuf + buflen; /* Don't do anything if the audio device can take the raw values. */ if (raw_audio) return (buflen); for (i = 0; buf16 < (short *)(rbend); i++) { /* Downsampling to 8KHz is a little irregular. */ samples = (i & 1) ? ((i % 20) ? 10 : 12) : 12; /* And unfortunately, we don't always end on a nice boundary. */ if (buf16 + samples > (short *)(rbend)) samples = ((short *)rbend) - buf16; /* * No need to average all the values; taking the first one * is sufficient and less CPU-intensive. But we do need to * do both channels. */ mono_value = (buf16[0] + buf16[1]) / 2; buf16 += samples; rawbuf[i] = ulawmap[mono_value]; } return (i); } #endif /* ... && BUILD_CDDA */