2 * $Id: plat_sun_audio.c,v 1.3 1999/03/07 08:36:41 dirk Exp $
4 * This file is part of WorkMan, the civilized CD player library
5 * (c) 1991-1997 by Steven Grimm (original author)
6 * (c) by Dirk Försterling (current 'author' = maintainer)
7 * The maintainer can be contacted by his e-mail address:
8 * milliByte@DeathsDoor.com
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public
21 * License along with this library; if not, write to the Free
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * Sun (really Solaris) digital audio functions.
28 #include "include/wm_config.h"
29 #if defined(sun) && defined(SYSV) && defined(BUILD_CDDA)
31 static char plat_sun_audio_id
[] = "$Id: plat_sun_audio.c,v 1.3 1999/03/07 08:36:41 dirk Exp $";
33 #include "include/wm_cdda.h"
35 /* types.h included by wmcdda.h */
39 #include <sys/ioctl.h>
40 #include <sys/audioio.h>
41 #include <sys/stropts.h>
47 #define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM
50 * Since there's a lag time between writing audio to the audio device and
51 * hearing it, we need to make sure the status indicators correlate to what's
52 * playing out the speaker. Luckily, Solaris gives us some audio
53 * synchronization facilities that make this pretty easy.
55 * We maintain a circular queue of status information. When we write some
56 * sound to the audio device, we put its status info into the queue. We write
57 * a marker into the audio stream; when the audio device driver encounters the
58 * marker, it increments a field in a status structure. When we see that
59 * field go up, we grab the next status structure from the queue and send it
60 * to the parent process.
62 * The minimum size of the queue depends on the latency of the audio stream.
66 struct cdda_block queue
[QSIZE
];
71 * We only send WMCDDA_PLAYED status messages upstream when the CD is supposed
72 * to be playing; this is used to keep track.
76 static int aufd
, aucfd
;
77 static int raw_audio
= 1; /* Can /dev/audio take 44.1KHz stereo? */
80 * For fast linear-to-ulaw mapping, we use a lookup table that's generated
83 unsigned char *ulawmap
, linear_to_ulaw();
88 * Dummy signal handler so writes to /dev/audio will interrupt.
93 signal(SIGALRM
, dummy
);
97 * Initialize the audio device.
103 char *audiodev
, *acdev
;
106 audiodev
= getenv("AUDIODEV");
107 if (audiodev
== NULL
)
108 audiodev
= "/dev/audio";
110 acdev
= malloc(strlen(audiodev
) + 4);
113 perror("Can't allocate audio control filename");
116 strcpy(acdev
, audiodev
);
117 strcat(acdev
, "ctl");
119 aucfd
= open(acdev
, O_WRONLY
, 0);
127 aufd
= open(audiodev
, O_WRONLY
, 0);
134 signal(SIGALRM
, dummy
);
137 * Try to set the device to CD-style audio; we can process it
138 * with the least CPU overhead.
140 AUDIO_INITINFO(&info
);
141 info
.play
.sample_rate
= 44100;
142 info
.play
.channels
= 2;
143 info
.play
.precision
= 16;
144 info
.play
.encoding
= AUDIO_ENCODING_LINEAR
;
146 info
.record
.pause
= 0;
147 info
.monitor_gain
= 0;
149 if (ioctl(aufd
, AUDIO_SETINFO
, &info
) < 0)
153 * Oh well, so much for that idea.
155 AUDIO_INITINFO(&info
);
156 info
.play
.sample_rate
= 8000;
157 info
.play
.channels
= 1;
158 info
.play
.precision
= 8;
159 info
.play
.encoding
= AUDIO_ENCODING_ULAW
;
161 info
.record
.pause
= 0;
162 info
.monitor_gain
= 0;
163 if (ioctl(aufd
, AUDIO_SETINFO
, &info
) < 0)
165 perror("Can't set up audio device");
170 * Initialize the linear-to-ulaw mapping table.
173 ulawmap
= malloc(65536);
179 for (linval
= 0; linval
< 65536; linval
++)
180 ulawmap
[linval
] = linear_to_ulaw(linval
-32768);
192 * Get ready to play some sound.
195 wmaudio_ready( void )
200 * Start at the correct queue position.
202 if (ioctl(aucfd
, AUDIO_GETINFO
, &info
) < 0) perror("AUDIO_GETINFO");
203 qtail
= info
.play
.eof
% QSIZE
;
206 queue
[qtail
].status
= WMCDDA_OK
;
210 * Stop the audio immediately.
215 if (ioctl(aufd
, I_FLUSH
, FLUSHRW
) < 0)
220 * Close the audio device.
223 wmaudio_close( void )
231 * Set the volume level.
234 wmaudio_volume(int level
)
238 AUDIO_INITINFO(&info
);
239 if (ioctl(aucfd
, AUDIO_GETINFO
, &info
) < 0) perror("AUDIO_GETINFO");
240 info
.play
.gain
= level
;
241 if (ioctl(aucfd
, AUDIO_SETINFO
, &info
) < 0) perror("AUDIO_SETINFO");
245 * Set the balance level.
248 wmaudio_balance(int level
)
252 AUDIO_INITINFO(&info
);
253 if (ioctl(aucfd
, AUDIO_GETINFO
, &info
) < 0) perror("AUDIO_GETINFO");
254 level
*= AUDIO_RIGHT_BALANCE
;
255 info
.play
.balance
= level
/ 255;
256 if (ioctl(aucfd
, AUDIO_SETINFO
, &info
) < 0) perror("AUDIO_SETINFO");
260 * Mark the most recent audio block on the queue as the last one.
263 wmaudio_mark_last( void )
265 queue
[qtail
].status
= WMCDDA_DONE
;
269 * Figure out the most recent status information and send it upstream.
272 wmaudio_send_status( void )
278 * Now send the most current status information to our parent.
280 if (ioctl(aucfd
, AUDIO_GETINFO
, &info
) < 0)
281 perror("AUDIO_GETINFO");
282 qhead
= info
.play
.eof
% QSIZE
;
284 if (qhead
!= qstart
&& playing
)
288 if (queue
[qhead
].status
!= WMCDDA_DONE
)
289 queue
[qhead
].status
= WMCDDA_PLAYED
;
290 queue
[qhead
].volume
= info
.play
.gain
;
291 queue
[qhead
].balance
= (info
.play
.balance
* 255) /
294 send_status(queue
+ qhead
);
298 return (queue
[qhead
].status
== WMCDDA_DONE
);
302 * Play some audio and pass a status message upstream, if applicable.
303 * Returns 0 on success.
306 wmaudio_play(unsigned char *rawbuf
, long buflen
, struct cdda_block
*blk
)
315 while (write(aufd
, rawbuf
, buflen
) <= 0)
318 if (! raw_audio
&& alarmcount
++ < 5)
321 * 8KHz /dev/audio blocks for several seconds
322 * waiting for its queue to drop below a low
325 wmaudio_send_status();
326 timerclear(&it
.it_interval
);
327 timerclear(&it
.it_value
);
328 it
.it_value
.tv_usec
= 500000;
329 setitimer(ITIMER_REAL
, &it
, NULL
);
342 blk
->status
= WMCDDA_ERROR
;
348 * Mark this spot in the audio stream.
350 * Marks don't always succeed (if the audio buffer is empty
351 * this call will block forever) so do it asynchronously.
353 fcntl(aufd
, F_SETFL
, O_NONBLOCK
);
354 if (write(aufd
, rawbuf
, 0) < 0)
357 perror("audio mark");
360 qtail
= (qtail
+ 1) % QSIZE
;
362 fcntl(aufd
, F_SETFL
, 0);
366 if (wmaudio_send_status() < 0)
373 * Get the current audio state.
376 wmaudio_state(struct cdda_block
*blk
)
381 if (ioctl(aucfd
, AUDIO_GETINFO
, &info
) < 0)
382 perror("AUDIO_GETINFO");
383 blk
->volume
= info
.play
.gain
;
384 blk
->balance
= (info
.play
.balance
* 255) / AUDIO_RIGHT_BALANCE
;
388 ** This routine converts from linear to ulaw.
390 ** Craig Reese: IDA/Supercomputing Research Center
391 ** Joe Campbell: Department of Defense
395 ** 1) CCITT Recommendation G.711 (very difficult to follow)
396 ** 2) "A New Digital Technique for Implementation of Any
397 ** Continuous PCM Companding Law," Villeret, Michel,
398 ** et al. 1973 IEEE Int. Conf. on Communications, Vol 1,
399 ** 1973, pg. 11.12-11.17
400 ** 3) MIL-STD-188-113,"Interoperability and Performance Standards
401 ** for Analog-to_Digital Conversion Techniques,"
404 ** Input: Signed 16 bit linear sample
405 ** Output: 8 bit ulaw sample
407 #define ZEROTRAP /* turn on the trap as per the MIL-STD */
408 #define BIAS 0x84 /* define the add-in bias for 16 bit samples */
412 linear_to_ulaw( sample
)
415 static int exp_lut
[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
416 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
417 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
418 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
419 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
420 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
421 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
422 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
423 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
424 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
425 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
426 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
427 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
428 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
429 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
430 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
431 int sign
, exponent
, mantissa
;
432 unsigned char ulawbyte
;
434 /* Get the sample into sign-magnitude. */
435 sign
= (sample
>> 8) & 0x80; /* set aside the sign */
436 if ( sign
!= 0 ) sample
= -sample
; /* get magnitude */
437 if ( sample
> CLIP
) sample
= CLIP
; /* clip the magnitude */
439 /* Convert from 16 bit linear to ulaw. */
440 sample
= sample
+ BIAS
;
441 exponent
= exp_lut
[( sample
>> 7 ) & 0xFF];
442 mantissa
= ( sample
>> ( exponent
+ 3 ) ) & 0x0F;
443 ulawbyte
= ~ ( sign
| ( exponent
<< 4 ) | mantissa
);
445 if ( ulawbyte
== 0 ) ulawbyte
= 0x02; /* optional CCITT trap */
452 * Downsample a block of CDDA data, if necessary, for playing out an old-style
456 wmaudio_convert(unsigned char *rawbuf
, long buflen
, struct cdda_block
*blk
)
458 short *buf16
= (short *)rawbuf
;
461 unsigned char *rbend
= rawbuf
+ buflen
;
463 /* Don't do anything if the audio device can take the raw values. */
467 for (i
= 0; buf16
< (short *)(rbend
); i
++)
469 /* Downsampling to 8KHz is a little irregular. */
470 samples
= (i
& 1) ? ((i
% 20) ? 10 : 12) : 12;
472 /* And unfortunately, we don't always end on a nice boundary. */
473 if (buf16
+ samples
> (short *)(rbend
))
474 samples
= ((short *)rbend
) - buf16
;
477 * No need to average all the values; taking the first one
478 * is sufficient and less CPU-intensive. But we do need to
481 mono_value
= (buf16
[0] + buf16
[1]) / 2;
483 rawbuf
[i
] = ulawmap
[mono_value
];
489 #endif /* ... && BUILD_CDDA */