| /********** |
| 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; |