$NetBSD: patch-ak,v 1.2 2008/01/12 23:30:38 bad Exp $ --- platforms/unix/vm-sound-NetBSD/sqUnixSoundNetBSD.c.orig 2007-12-06 18:47:09.000000000 -0600 +++ platforms/unix/vm-sound-NetBSD/sqUnixSoundNetBSD.c @@ -0,0 +1,378 @@ +/* sqUnixSoundNetBSD.c -- sound support for NetBSD + * + * Copyright (C) 1996-2002 Ian Piumarta and other authors/contributors + * as listed elsewhere in this file. + * All rights reserved. + * + * This file is part of Unix Squeak. + * + * This file 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. + * + * You may use and/or distribute this file ONLY as part of Squeak, under + * the terms of the Squeak License as described in `LICENSE' in the base of + * this distribution, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment to the original author(s) (and any + * other contributors mentioned herein) in the product documentation + * would be appreciated but is not required. + * + * 2. This notice must not be removed or altered in any source distribution. + * + * Using (or modifying this file for use) in any context other than Squeak + * changes these copyright conditions. Read the file `COPYING' in the + * directory `platforms/unix/doc' before proceeding with any such use. + * + * You are not allowed to distribute a modified version of this file + * under its original name without explicit permission to do so. If + * you change it, rename it. + * + * Authors: Ian.Piumarta@inria.fr, Lex Spoon and + * Frederick Bruckman . + * + */ + +#include "sq.h" +#include "sqaio.h" + +#include +#include +#include +#include +#include +#include + + +/* Globals */ +static int auFd= -1; /* open file descriptor on AUDIODEVICE */ +static int fmtStereo; /* whether to use stereo or not */ +static int auBlockCount; /* VM's block size, in frames */ +static int auBlockSize; /* hardware blocksize, in bytes */ +static int auDuplexMode; /* 0: half duplex, !0: full duplex */ +static int auLatency; /* "fudge factor", to help keep the audio + hardware buffer filled */ +static int auPlaySemaIndex; /* identifier of audio system semaphore */ +static const char *auDevice; /* AUDIODEVICE, or "/dev/audio" by default */ + + +static int sound_AvailableSpace(void); + + +static void auHandle(int fd, void *data, int flags) +{ + if (auFd == -1) + return; + + if (sound_AvailableSpace() >= (auBlockSize >> 1)) + signalSemaphoreWithIndex(auPlaySemaIndex); + + aioHandle(fd, auHandle, flags); + return; +} + + +static int sound_Stop(void) +{ + if (auFd == -1) + return false; + + aioDisable(auFd); + close(auFd); + auFd= -1; + + return true; +} + + +static int sound_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex) +{ + struct audio_info info; + + /* Set some globals. */ + auBlockCount= frameCount; + fmtStereo= stereo; + auPlaySemaIndex= semaIndex; + + if (auFd != -1) + sound_Stop(); + + if ((auFd= open(auDevice, O_RDWR|O_NONBLOCK)) == -1) + { + perror(auDevice); + return false; + } + + AUDIO_INITINFO(&info); + info.play.precision= 16; + info.play.encoding= AUDIO_ENCODING_SLINEAR; + info.play.channels= stereo ? 2 : 1; + info.play.sample_rate= samplesPerSec; + /* + Request desired HW blocksize to be the same as the VM's blocksize. + The blocksize could end up being different than what we asked for. + */ + info.blocksize= auBlockSize= frameCount * (stereo ? 4 : 2); + info.mode= AUMODE_PLAY|AUMODE_PLAY_ALL; + + if (ioctl(auFd, AUDIO_SETINFO, &info) == -1) + { + perror("AUDIO_SETINFO"); + close(auFd); + auFd= -1; + return false; + } + + if (info.blocksize == auBlockSize && info.hiwat > 1) + { + /* + Hurrah! Now tune the high- and low- water marks. Because of the + auLatency term, sound_AvailableSpace() will return slightly more + than one block after the VM returns from select(). The VM will + utilize the headroom to "sync up" when necessary, and thereafter + mostly write one full block at a time. + */ + info.hiwat= (info.hiwat > 2) ? 2 : info.hiwat; + info.lowat= info.hiwat - 1; + if (ioctl(auFd, AUDIO_SETINFO, &info) == -1) + { + perror("AUDIO_SETINFO"); + close(auFd); + auFd= -1; + return false; + } + /* + Set the initial fudge factor to 1/8 of the blocksize. + */ + if (auLatency == 0) + auLatency= info.blocksize >> 3; + } + else + { + /* + The hardware buffer is probably too small, so just use the + defaults for high- and low- water marks. Don't bother with the + latency term, either, as we can't now sensibly calculate it. + */ + warn("%s: asked for blocksize of %d, got %d!", + auDevice, auBlockSize, info.blocksize); + auBlockSize= info.blocksize; + } + + aioEnable(auFd, 0, AIO_EXT); + aioHandle(auFd, auHandle, AIO_W); + return true; +} + + +static int sound_AvailableSpace(void) +{ + struct audio_info info; + int space; + + if (auFd == -1) + return 0; + + if (ioctl(auFd, AUDIO_GETINFO, &info) == -1) + { + perror("AUDIO_GETINFO"); + return 0; + } + + space= info.hiwat * info.blocksize - info.play.seek + auLatency; + return space; +} + + +static int sound_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex) +{ + int size, totalWritten, written; + char *start; + + if (auFd == -1 || frameCount == 0) + return 0; + + start= (char *)arrayIndex + 4 * startIndex; + size= frameCount * (fmtStereo ? 4 : 2); + totalWritten= 0; + + while (totalWritten < size) + if ((written= write(auFd, start, size)) == -1) + { + if (errno == EAGAIN) + /* + Astute listeners may now hear a "tick", but only the first + time the audio is started, as auLatency is preserved across + sound_Start() calls. + */ + auLatency= auLatency >> 1; + else + perror(auDevice); + /* + In any case, bail. Typically, we've gotten an EAGAIN because + the auLatency was initially too high. This means the audio + hardware buffer is already full, so there's no sense sitting + here in a tight loop, while if write() failed for some other + reason, then we *really* want to return control to the VM. + */ +#ifdef AUDIO_DEBUG + printf("Bytes written: %d; Latency: %d\n", totalWritten, auLatency); +#endif + frameCount= totalWritten/(fmtStereo ? 4 : 2); + return frameCount; + } + else + { + start += written; + size -= written; + totalWritten += written; + } + +#ifdef AUDIO_DEBUG + printf("Bytes written: %d; Latency: %d\n", totalWritten, auLatency); +#endif + frameCount= totalWritten/(fmtStereo ? 4 : 2); + return frameCount; +} + + +static int sound_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, + int samplesOfLeadTime) +{ + /* + We could actually implement this with an mmap()-able audio device, + via double-buffering, but it doesn't seem to be worth the trouble. + */ + success(false); + return 0; +} + + +static int sound_PlaySilence(void) +{ + int framesWritten; + char *buffer; + + if (auFd == -1 || auBlockCount == 0) + return 0; + + if ((buffer= calloc(auBlockCount, (fmtStereo ? 4 : 2))) == 0) + { + perror("calloc"); + return 0; + } + + framesWritten= sound_PlaySamplesFromAtLength(auBlockCount, (int)buffer, 0); + return framesWritten; +} + + +/* + Keep it clean, and try to use the simplified "audio" interface to set the + level, even though, due to a bug, it will do nothing on most systems older + than NetBSD 2.0. In that case, the user may still access the real mixer + controls via the command line tool "mixerctl", or various grapical mixers. + */ +static int sound_SetRecordLevel(int level) +{ + struct audio_info info; + + AUDIO_INITINFO (&info); + info.record.gain= (level > AUDIO_MAX_GAIN) ? AUDIO_MAX_GAIN : level; + + if (ioctl(auFd, AUDIO_SETINFO, &info) == -1) + perror("AUDIO_SETINFO"); + + return; +} + + +static int sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) +{ + success(false); + return 0; +} + + +static int sound_StopRecording(void) +{ + success(false); + return 0; +} + + +static double sound_GetRecordingSampleRate(void) +{ + success(false); + return 0; +} + + +static int sound_RecordSamplesIntoAtLength(int buf, int startSliceIndex, + int bufferSizeInBytes) +{ + success(false); + return 0; +} + + +/* + See the comment associated with sound_SetRecordLevel(). + */ +static void sound_Volume(double *left, double *right) +{ + struct audio_info info; + + if (ioctl(auFd, AUDIO_GETINFO, &info) == -1) + { + perror("AUDIO_GETINFO"); + success(false); + return; + } + + *left= *right= (double)info.play.gain/AUDIO_MAX_GAIN; + + return; +} + + +/* + This may not do anything at all. See the comment associated with + sound_SetRecordLevel(). + */ +static void sound_SetVolume(double left, double right) +{ + struct audio_info info; + + AUDIO_INITINFO (&info); + info.play.gain= AUDIO_MAX_GAIN * left; + + if (ioctl(auFd, AUDIO_SETINFO, &info) == -1) + perror("AUDIO_SETINFO"); + + return; +} + + +#include "SqSound.h" + +SqSoundDefine(NetBSD); + + +#include "SqModule.h" + +static void sound_parseEnvironment(void) +{ + char *ev= 0; + if ((ev= getenv("AUDIODEVICE"))) auDevice= strdup(ev); + else auDevice= strdup("/dev/audio"); +} + +static int sound_parseArgument(int argc, char **argv) { return 0; } +static void sound_printUsage(void) {} +static void sound_printUsageNotes(void) {} +static void *sound_makeInterface(void) { return &sound_NetBSD_itf; } + +SqModuleDefine(sound, NetBSD);