blob: 57ab203e5b1a5711dfeb568a06d83f7aabfbc558 [file] [log] [blame]
/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
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 Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**********/
// Copyright (c) 2001-2004 Live Networks, Inc. All rights reserved.
// Windows implementation of a generic audio input device
// Base class for both library versions:
// One that uses Windows' built-in software mixer; another that doesn't.
// Implementation
#include "WindowsAudioInputDevice_common.hh"
#include <GroupsockHelper.hh>
////////// WindowsAudioInputDevice_common implementation //////////
unsigned WindowsAudioInputDevice_common::_bitsPerSample = 16;
WindowsAudioInputDevice_common
::WindowsAudioInputDevice_common(UsageEnvironment& env, int inputPortNumber,
unsigned char bitsPerSample,
unsigned char numChannels,
unsigned samplingFrequency,
unsigned granularityInMS)
: AudioInputDevice(env, bitsPerSample, numChannels, samplingFrequency, granularityInMS),
fCurPortIndex(-1), fHaveStarted(False) {
_bitsPerSample = bitsPerSample;
}
WindowsAudioInputDevice_common::~WindowsAudioInputDevice_common() {
}
Boolean WindowsAudioInputDevice_common::initialSetInputPort(int portIndex) {
if (!setInputPort(portIndex)) {
char errMsgPrefix[100];
sprintf(errMsgPrefix, "Failed to set audio input port number to %d: ", portIndex);
char* errMsgSuffix = strDup(envir().getResultMsg());
envir().setResultMsg(errMsgPrefix, errMsgSuffix);
delete[] errMsgSuffix;
return False;
} else {
return True;
}
}
void WindowsAudioInputDevice_common::doGetNextFrame() {
if (!fHaveStarted) {
// Before reading the first audio data, flush any existing data:
while (readHead != NULL) releaseHeadBuffer();
fHaveStarted = True;
}
fTotalPollingDelay = 0;
audioReadyPoller1();
}
void WindowsAudioInputDevice_common::doStopGettingFrames() {
// Turn off the audio poller:
envir().taskScheduler().unscheduleDelayedTask(nextTask());
}
double WindowsAudioInputDevice_common::getAverageLevel() const {
// If the input audio queue is empty, return the previous level,
// otherwise use the input queue to recompute "averageLevel":
if (readHead != NULL) {
double levelTotal = 0.0;
unsigned totNumSamples = 0;
WAVEHDR* curHdr = readHead;
while (1) {
short* samplePtr = (short*)(curHdr->lpData);
unsigned numSamples = blockSize/2;
totNumSamples += numSamples;
while (numSamples-- > 0) {
short sample = *samplePtr++;
if (sample < 0) sample = -sample;
levelTotal += (unsigned short)sample;
}
if (curHdr == readTail) break;
curHdr = curHdr->lpNext;
}
averageLevel = levelTotal/(totNumSamples*(double)0x8000);
}
return averageLevel;
}
void WindowsAudioInputDevice_common::audioReadyPoller(void* clientData) {
WindowsAudioInputDevice_common* inputDevice = (WindowsAudioInputDevice_common*)clientData;
inputDevice->audioReadyPoller1();
}
void WindowsAudioInputDevice_common::audioReadyPoller1() {
nextTask() = NULL;
if (readHead != NULL) {
onceAudioIsReady();
} else {
unsigned const maxPollingDelay = (100 + fGranularityInMS)*1000;
if (fTotalPollingDelay > maxPollingDelay) {
// We've waited too long for the audio device - assume it's down:
handleClosure(this);
return;
}
// Try again after a short delay:
unsigned const uSecondsToDelay = fGranularityInMS*1000;
fTotalPollingDelay += uSecondsToDelay;
nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToDelay,
(TaskFunc*)audioReadyPoller, this);
}
}
void WindowsAudioInputDevice_common::onceAudioIsReady() {
fFrameSize = readFromBuffers(fTo, fMaxSize, fPresentationTime);
if (fFrameSize == 0) {
// The source is no longer readable
handleClosure(this);
return;
}
fDurationInMicroseconds = 1000000/fSamplingFrequency;
// Call our own 'after getting' function. Because we sometimes get here
// after returning from a delay, we can call this directly, without risking
// infinite recursion
afterGetting(this);
}
static void CALLBACK waveInCallback(HWAVEIN /*hwi*/, UINT uMsg,
DWORD /*dwInstance*/, DWORD dwParam1, DWORD /*dwParam2*/) {
switch (uMsg) {
case WIM_DATA:
WAVEHDR* hdr = (WAVEHDR*)dwParam1;
WindowsAudioInputDevice_common::waveInProc(hdr);
break;
}
}
Boolean WindowsAudioInputDevice_common::openWavInPort(int index, unsigned numChannels, unsigned samplingFrequency, unsigned granularityInMS) {
uSecsPerByte = (8*1e6)/(_bitsPerSample*numChannels*samplingFrequency);
// Configure the port, based on the specified parameters:
WAVEFORMATEX wfx;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = numChannels;
wfx.nSamplesPerSec = samplingFrequency;
wfx.wBitsPerSample = _bitsPerSample;
wfx.nBlockAlign = (numChannels*_bitsPerSample)/8;
wfx.nAvgBytesPerSec = samplingFrequency*wfx.nBlockAlign;
wfx.cbSize = 0;
blockSize = (wfx.nAvgBytesPerSec*granularityInMS)/1000;
// Use a 10-second input buffer, to allow for CPU competition from video, etc.,
// and also for some audio cards that buffer as much as 5 seconds of audio.
unsigned const bufferSeconds = 10;
numBlocks = (bufferSeconds*1000)/granularityInMS;
if (!waveIn_open(index, wfx)) return False;
// Set this process's priority high. I'm not sure how much this is really needed,
// but the "rat" code does this:
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
return True;
}
Boolean WindowsAudioInputDevice_common::waveIn_open(unsigned uid, WAVEFORMATEX& wfx) {
if (shWaveIn != NULL) return True; // already open
do {
waveIn_reset();
if (waveInOpen(&shWaveIn, uid, &wfx,
(DWORD)waveInCallback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) break;
// Allocate read buffers, and headers:
readData = new unsigned char[numBlocks*blockSize];
if (readData == NULL) break;
readHdrs = new WAVEHDR[numBlocks];
if (readHdrs == NULL) break;
readHead = readTail = NULL;
readTimes = new struct timeval[numBlocks];
if (readTimes == NULL) break;
// Initialize headers:
for (unsigned i = 0; i < numBlocks; ++i) {
readHdrs[i].lpData = (char*)&readData[i*blockSize];
readHdrs[i].dwBufferLength = blockSize;
readHdrs[i].dwFlags = 0;
if (waveInPrepareHeader(shWaveIn, &readHdrs[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR) break;
if (waveInAddBuffer(shWaveIn, &readHdrs[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR) break;
}
if (waveInStart(shWaveIn) != MMSYSERR_NOERROR) break;
#ifdef UNICODE
hAudioReady = CreateEvent(NULL, TRUE, FALSE, L"waveIn Audio Ready");
#else
hAudioReady = CreateEvent(NULL, TRUE, FALSE, "waveIn Audio Ready");
#endif
return True;
} while (0);
waveIn_reset();
return False;
}
void WindowsAudioInputDevice_common::waveIn_close() {
if (shWaveIn == NULL) return; // already closed
waveInStop(shWaveIn);
waveInReset(shWaveIn);
for (unsigned i = 0; i < numBlocks; ++i) {
if (readHdrs[i].dwFlags & WHDR_PREPARED) {
waveInUnprepareHeader(shWaveIn, &readHdrs[i], sizeof (WAVEHDR));
}
}
waveInClose(shWaveIn);
waveIn_reset();
}
void WindowsAudioInputDevice_common::waveIn_reset() {
shWaveIn = NULL;
delete[] readData; readData = NULL;
bytesUsedAtReadHead = 0;
delete[] readHdrs; readHdrs = NULL;
readHead = readTail = NULL;
delete[] readTimes; readTimes = NULL;
hAudioReady = NULL;
}
unsigned WindowsAudioInputDevice_common::readFromBuffers(unsigned char* to, unsigned numBytesWanted, struct timeval& creationTime) {
// Begin by computing the creation time of (the first bytes of) this returned audio data:
if (readHead != NULL) {
int hdrIndex = readHead - readHdrs;
creationTime = readTimes[hdrIndex];
// Adjust this time to allow for any data that's already been read from this buffer:
if (bytesUsedAtReadHead > 0) {
creationTime.tv_usec += (unsigned)(uSecsPerByte*bytesUsedAtReadHead);
creationTime.tv_sec += creationTime.tv_usec/1000000;
creationTime.tv_usec %= 1000000;
}
}
// Then, read from each available buffer, until we have the data that we want:
unsigned numBytesRead = 0;
while (readHead != NULL && numBytesRead < numBytesWanted) {
unsigned thisRead = min(readHead->dwBytesRecorded - bytesUsedAtReadHead, numBytesWanted - numBytesRead);
memmove(&to[numBytesRead], &readHead->lpData[bytesUsedAtReadHead], thisRead);
numBytesRead += thisRead;
bytesUsedAtReadHead += thisRead;
if (bytesUsedAtReadHead == readHead->dwBytesRecorded) {
// We're finished with the block; give it back to the device:
releaseHeadBuffer();
}
}
return numBytesRead;
}
void WindowsAudioInputDevice_common::releaseHeadBuffer() {
WAVEHDR* toRelease = readHead;
if (readHead == NULL) return;
readHead = readHead->lpNext;
if (readHead == NULL) readTail = NULL;
toRelease->lpNext = NULL;
toRelease->dwBytesRecorded = 0;
toRelease->dwFlags &= ~WHDR_DONE;
waveInAddBuffer(shWaveIn, toRelease, sizeof (WAVEHDR));
bytesUsedAtReadHead = 0;
}
void WindowsAudioInputDevice_common::waveInProc(WAVEHDR* hdr) {
unsigned hdrIndex = hdr - readHdrs;
// Record the time that the data arrived:
int dontCare;
gettimeofday(&readTimes[hdrIndex], &dontCare);
// Add the block to the tail of the queue:
hdr->lpNext = NULL;
if (readTail != NULL) {
readTail->lpNext = hdr;
readTail = hdr;
} else {
readHead = readTail = hdr;
}
SetEvent(hAudioReady);
}
HWAVEIN WindowsAudioInputDevice_common::shWaveIn = NULL;
unsigned WindowsAudioInputDevice_common::blockSize = 0;
unsigned WindowsAudioInputDevice_common::numBlocks = 0;
unsigned char* WindowsAudioInputDevice_common::readData = NULL;
DWORD WindowsAudioInputDevice_common::bytesUsedAtReadHead = 0;
double WindowsAudioInputDevice_common::uSecsPerByte = 0.0;
double WindowsAudioInputDevice_common::averageLevel = 0.0;
WAVEHDR* WindowsAudioInputDevice_common::readHdrs = NULL;
WAVEHDR* WindowsAudioInputDevice_common::readHead = NULL;
WAVEHDR* WindowsAudioInputDevice_common::readTail = NULL;
struct timeval* WindowsAudioInputDevice_common::readTimes = NULL;
HANDLE WindowsAudioInputDevice_common::hAudioReady = NULL;