$NetBSD: patch-ae,v 1.1.1.1 2002/09/24 12:49:13 blymn Exp $ --- src/platform/NetBSDMedia.cxx.orig Sun Sep 22 22:29:02 2002 +++ src/platform/NetBSDMedia.cxx @@ -0,0 +1,477 @@ +/* bzflag + * Copyright (c) 1993 - 2002 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named LICENSE that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "NetBSDMedia.h" +#include +#include +#ifdef BSD +#include +#else +#ifdef __NetBSD__ +#include +#else +#include +#endif +#endif +#include +#include +#include +#include +#include "bzsignal.h" +#ifdef __NetBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include + +#ifdef HALF_RATE_AUDIO +static const int defaultAudioRate=11025; +#else +static const int defaultAudioRate=22050; +#endif + +// +// NetBSDMedia +// + +NetBSDMedia::NetBSDMedia() : BzfMedia(), audioReady(False), + audioPortFd(-1), + queueIn(-1), queueOut(-1), + outputBuffer(NULL), + childProcID(0), + audio8Bit(False), + getospaceBroken(False) +{ + // do nothing +} + +NetBSDMedia::~NetBSDMedia() +{ + // do nothing +} + +double NetBSDMedia::getTime() +{ + struct timeval tv; + gettimeofday(&tv, 0); + return (double)tv.tv_sec + 1.0e-6 * (double)tv.tv_usec; +} + +double NetBSDMedia::stopwatch(boolean start) +{ + if (start) { + stopwatchTime = getTime(); + return 0.0; + } + return getTime() - stopwatchTime; +} + +void NetBSDMedia::sleep(float timeInSeconds) +{ + struct timeval tv; + tv.tv_sec = (long)timeInSeconds; + tv.tv_usec = (long)(1.0e6 * (timeInSeconds - tv.tv_sec)); + select(0, NULL, NULL, NULL, &tv); +} + +boolean NetBSDMedia::openAudio() +{ + // don't re-initialize + if (audioReady) return False; + + // check for and open audio hardware + if (!checkForAudioHardware() || !openAudioHardware()) return False; + + // open communication channel (FIFO pipe). close on exec. + int fd[2]; + if (pipe(fd)<0) { + closeAudio(); + return False; + } + queueIn = fd[1]; + queueOut = fd[0]; + fcntl(queueIn, F_SETFL, fcntl(queueIn, F_GETFL, 0) | O_NDELAY); + fcntl(queueOut, F_SETFL, fcntl(queueOut, F_GETFL, 0) | O_NDELAY); + fcntl(queueIn, F_SETFD, fcntl(queueIn, F_GETFD, 0) | FD_CLOEXEC); + fcntl(queueOut, F_SETFD, fcntl(queueOut, F_GETFD, 0) | FD_CLOEXEC); + + // compute maxFd for use in select() call + maxFd = queueOut; + if (maxFd (1 << fragmentSize)) { + audioBufferSize = 1 << fragmentSize; + noSetFragment = true; + } + if (!audio8Bit) + audioBufferSize >>= 1; + + // SNDCTL_DSP_GETOSPACE not supported on all platforms. check if + // it fails here and, if so, do a workaround by using the wall + // clock. *shudder* + if (audioPortFd != -1) { + audio_buf_info info; + if (!openIoctl(SNDCTL_DSP_GETOSPACE, &info, False)) { + getospaceBroken = True; + chunksPending = 0; + chunksPerSecond = (double)getAudioOutputRate() / + (double)getAudioBufferChunkSize(); + } + } + + return (audioPortFd != -1); +} + +void NetBSDMedia::closeAudio() +{ + delete [] outputBuffer; + if (audioPortFd>=0) close(audioPortFd); + if (queueIn!=-1) close(queueIn); + if (queueOut!=-1) close(queueOut); + audioReady=False; + audioPortFd=-1; + queueIn=-1; + queueOut=-1; + outputBuffer=0; +} + +boolean NetBSDMedia::startAudioThread( + void (*proc)(void*), void* data) +{ + // if no audio thread then just call proc and return + if (!hasAudioThread()) { + proc(data); + return True; + } + + // has an audio thread so fork and call proc + if (childProcID) return True; + if ((childProcID=fork()) > 0) { + close(queueOut); + close(audioPortFd); + return True; + } + else if (childProcID < 0) { + return False; + } + close(queueIn); + proc(data); + exit(0); +} + +void NetBSDMedia::stopAudioThread() +{ + if (childProcID != 0) kill(childProcID, SIGTERM); + childProcID=0; +} + +boolean NetBSDMedia::hasAudioThread() const +{ +#if defined(NO_AUDIO_THREAD) + return False; +#else + return True; +#endif +} + +void NetBSDMedia::audioThreadInit(void*) +{ +} + +void NetBSDMedia::writeSoundCommand(const void* cmd, int len) +{ + if (!audioReady) return; + write(queueIn, cmd, len); +} + +boolean NetBSDMedia::readSoundCommand(void* cmd, int len) +{ + return (read(queueOut, cmd, len)==len); +} + +int NetBSDMedia::getAudioOutputRate() const +{ + return audioOutputRate; +} + +int NetBSDMedia::getAudioBufferSize() const +{ + return NumChunks*(audioBufferSize>>1); +} + +int NetBSDMedia::getAudioBufferChunkSize() const +{ + return audioBufferSize>>1; +} + +boolean NetBSDMedia::isAudioTooEmpty() const +{ + if (getospaceBroken) { + if (chunksPending > 0) { + // get time elapsed since chunkTime + const double dt = getTime() - chunkTime; + + // how many chunks could've played in the elapsed time? + const int numChunks = (int)(dt * chunksPerSecond); + + // remove pending chunks + NetBSDMedia* self = (NetBSDMedia*)this; + self->chunksPending -= numChunks; + if (chunksPending < 0) + self->chunksPending = 0; + else + self->chunkTime += (double)numChunks / chunksPerSecond; + } + return chunksPending < audioLowWaterMark; + } + else { + audio_buf_info info; + if (ioctl(audioPortFd, SNDCTL_DSP_GETOSPACE, &info) < 0) + return False; + return info.fragments > info.fragstotal - audioLowWaterMark; + } +} + +void NetBSDMedia::writeAudioFrames8Bit( + const float* samples, int numFrames) +{ + int numSamples = 2 * numFrames; + int limit; + char *smOutputBuffer; + + smOutputBuffer=(char*)outputBuffer; + while (numSamples > 0) { + if (numSamples>audioBufferSize) limit=audioBufferSize; + else limit=numSamples; + for (int j = 0; j < limit; j++) { + if (samples[j] <= -32767.0) smOutputBuffer[j] = 0; + else if (samples[j] >= 32767.0) smOutputBuffer[j] = 255; + else smOutputBuffer[j] = char((samples[j]+32767)/257); + } + + // fill out the chunk (we never write a partial chunk) + if (limit < audioBufferSize) { + for (int j = limit; j < audioBufferSize; ++j) + smOutputBuffer[j] = 127; + limit = audioBufferSize; + } + + write(audioPortFd, smOutputBuffer, limit); + samples += limit; + numSamples -= limit; + } +} + +void NetBSDMedia::writeAudioFrames16Bit( + const float* samples, int numFrames) +{ + int numSamples = 2 * numFrames; + int limit; + + while (numSamples > 0) { + if (numSamples>audioBufferSize) limit=audioBufferSize; + else limit=numSamples; + for (int j = 0; j < limit; j++) { + if (samples[j] < -32767.0) outputBuffer[j] = -32767; + else if (samples[j] > 32767.0) outputBuffer[j] = 32767; + else outputBuffer[j] = short(samples[j]); + } + + // fill out the chunk (we never write a partial chunk) + if (limit < audioBufferSize) { + for (int j = limit; j < audioBufferSize; ++j) + outputBuffer[j] = 0; + limit = audioBufferSize; + } + + write(audioPortFd, outputBuffer, 2*limit); + samples += limit; + numSamples -= limit; + } +} + +void NetBSDMedia::writeAudioFrames( + const float* samples, int numFrames) +{ + if (audio8Bit) writeAudioFrames8Bit(samples, numFrames); + else writeAudioFrames16Bit(samples, numFrames); + + // if we couldn't set the fragment size then force the driver + // to play the short buffer. + if (noSetFragment) { + int dummy = 0; + ioctl(audioPortFd, SNDCTL_DSP_POST, &dummy); + } + + if (getospaceBroken) { + if (chunksPending == 0) + chunkTime = getTime(); + chunksPending += (numFrames + getAudioBufferChunkSize() - 1) / + getAudioBufferChunkSize(); + } +} + +void NetBSDMedia::audioSleep( + boolean checkLowWater, double endTime) +{ + fd_set commandSelectSet; + struct timeval tv; + + // To do both these operations at once, we need to poll. + if (checkLowWater) { + // start looping + TimeKeeper start = TimeKeeper::getCurrent(); + do { + // break if buffer has drained enough + if (isAudioTooEmpty()) break; + FD_ZERO(&commandSelectSet); + FD_SET(queueOut, &commandSelectSet); + tv.tv_sec=0; + tv.tv_usec=50000; + if (select(maxFd, &commandSelectSet, 0, 0, &tv)) break; + + } while (endTime<0.0 || (TimeKeeper::getCurrent()-start)=0.0)?&tv : 0); + } +} +// ex: shiftwidth=2 tabstop=8