| |
| |
| #include "tsound.h" |
| |
| #ifndef TNZCORE_LIGHT |
| #include "tthread.h" |
| #endif |
| #include "tsop.h" |
| #include <set> |
| #include "tsystem.h" |
| |
| #include <mmsystem.h> |
| |
| |
| |
| |
| class TSoundOutputDeviceImp; |
| class TSoundInputDeviceImp; |
| |
| |
| |
| namespace { |
| void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, |
| DWORD_PTR dwParam1, DWORD dwParam2); |
| |
| bool setRecordLine(TSoundInputDevice::Source typeInput); |
| |
| MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl); |
| |
| MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID, |
| DWORD dwControlType); |
| |
| MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue); |
| |
| MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue); |
| |
| MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample, |
| int sampleType, bool input); |
| |
| DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter); |
| void getAmplitude(int &litude, const TSoundTrackP st, TINT32 sample); |
| } |
| |
| |
| |
| |
| #ifndef TNZCORE_LIGHT |
| class EndPlayMsg final : public TThread::Message { |
| public: |
| EndPlayMsg(TSoundOutputDeviceListener *notifier) { m_listener = notifier; } |
| |
| TThread::Message *clone() const override { return new EndPlayMsg(*this); } |
| |
| void onDeliver() override { m_listener->onPlayCompleted(); } |
| |
| private: |
| TSoundOutputDeviceListener *m_listener; |
| }; |
| #endif |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class WavehdrQueue; |
| |
| class TSoundOutputDeviceImp { |
| public: |
| HWAVEOUT m_wout; |
| WavehdrQueue *m_whdrQueue; |
| TSoundTrackFormat m_currentFormat; |
| std::set<int> m_supportedRate; |
| |
| TThread::Mutex m_mutex; |
| |
| bool m_stopped; |
| bool m_isPlaying; |
| bool m_looped; |
| bool m_scrubbing; |
| |
| std::set<TSoundOutputDeviceListener *> m_listeners; |
| DWORD m_notifyThreadId; |
| |
| HANDLE m_closeDevice; |
| |
| TSoundOutputDeviceImp(); |
| ~TSoundOutputDeviceImp(); |
| |
| bool doOpenDevice(const TSoundTrackFormat &format); |
| bool doPlay(WAVEHDR *whdr, const TSoundTrackFormat format); |
| bool doCloseDevice(); |
| |
| bool verifyRate(); |
| void insertAllRate(); |
| }; |
| |
| |
| |
| class WavehdrQueue { |
| public: |
| WavehdrQueue(TSoundOutputDeviceImp *devImp, int slotCount) |
| : m_devImp(devImp) |
| , m_items() |
| , m_queuedItems() |
| , m_slotCount(slotCount) |
| , m_mutex() |
| , m_lastOffset(0) {} |
| |
| ~WavehdrQueue() {} |
| |
| void put(TSoundTrackP &subTrack); |
| WAVEHDR *get(); |
| bool popFront(int count); |
| void pushBack(WAVEHDR *whdr, TSoundTrackP st); |
| int size(); |
| void clear(); |
| bool isAllQueuedItemsPlayed(); |
| |
| private: |
| std::list<std::pair<WAVEHDR *, TSoundTrackP>> m_items; |
| std::list<WAVEHDR *> m_queuedItems; |
| |
| TThread::Mutex m_mutex; |
| int m_slotCount; |
| int m_lastOffset; |
| TSoundOutputDeviceImp *m_devImp; |
| TSoundTrackP m_lastTrack; |
| }; |
| |
| |
| |
| static WAVEHDR *prepareWaveHeader(HWAVEOUT wout, const TSoundTrackP &subTrack, |
| ULONG &count) { |
| WAVEHDR *whdr = new WAVEHDR; |
| memset(whdr, 0, sizeof(WAVEHDR)); |
| whdr->dwBufferLength = subTrack->getSampleSize() * subTrack->getSampleCount(); |
| whdr->lpData = new char[whdr->dwBufferLength]; |
| whdr->dwFlags = 0; |
| whdr->dwUser = count; |
| memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength); |
| |
| MMRESULT ret = waveOutPrepareHeader(wout, whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) { |
| delete[] whdr->lpData; |
| delete whdr; |
| return 0; |
| } |
| ++count; |
| return whdr; |
| } |
| |
| |
| |
| void WavehdrQueue::put(TSoundTrackP &subTrack) { |
| assert(subTrack->getRawData()); |
| static ULONG count = 1; |
| |
| |
| |
| |
| |
| |
| if (m_devImp->m_wout && m_devImp->m_currentFormat != subTrack->getFormat()) { |
| m_devImp->doCloseDevice(); |
| TSystem::sleep(300); |
| m_devImp->doOpenDevice(subTrack->getFormat()); |
| } |
| |
| TThread::MutexLocker sl(&m_mutex); |
| if (!m_devImp->m_scrubbing) { |
| WAVEHDR *whdr2 = 0; |
| |
| |
| whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count); |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| |
| MMRESULT ret = MMSYSERR_NOERROR; |
| |
| if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr2, subTrack); |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| TThread::MutexLocker sl(&m_devImp->m_mutex); |
| m_devImp->m_isPlaying = true; |
| m_devImp->m_stopped = false; |
| } |
| } |
| return; |
| } |
| |
| if (m_queuedItems.size() == 0) { |
| WAVEHDR *whdr1 = 0; |
| WAVEHDR *whdr2 = 0; |
| WAVEHDR *whdr3 = 0; |
| |
| MMRESULT ret; |
| TSoundTrackP riseTrack, decayTrack; |
| int sampleSize = subTrack->getSampleSize(); |
| |
| |
| riseTrack = TSop::fadeIn(subTrack, 0.9); |
| whdr1 = prepareWaveHeader(m_devImp->m_wout, riseTrack, count); |
| |
| |
| whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count); |
| |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| |
| |
| decayTrack = 0; |
| if (m_lastOffset) { |
| decayTrack = TSop::fadeOut(subTrack, 0.9); |
| whdr3 = prepareWaveHeader(m_devImp->m_wout, decayTrack, count); |
| } |
| |
| if (whdr1 && (whdr1->dwFlags & WHDR_PREPARED)) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr1, riseTrack); |
| getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L); |
| } |
| } |
| if (whdr2 && (whdr2->dwFlags & WHDR_PREPARED)) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr2, subTrack); |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| TThread::MutexLocker sl(&m_devImp->m_mutex); |
| m_devImp->m_isPlaying = true; |
| m_devImp->m_stopped = false; |
| } |
| } |
| if (whdr3 && (whdr3->dwFlags & WHDR_PREPARED)) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr3, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr3, decayTrack); |
| if (decayTrack->isSampleSigned()) |
| m_lastOffset = 0; |
| else |
| m_lastOffset = 127; |
| } |
| } |
| return; |
| } |
| |
| if (m_queuedItems.size() < 10) { |
| WAVEHDR *whdr1 = 0; |
| WAVEHDR *whdr2 = 0; |
| WAVEHDR *whdr = new WAVEHDR; |
| memset(whdr, 0, sizeof(WAVEHDR)); |
| whdr->dwBufferLength = |
| subTrack->getSampleSize() * subTrack->getSampleCount(); |
| whdr->lpData = new char[whdr->dwBufferLength]; |
| whdr->dwFlags = 0; |
| memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength); |
| int sampleSize = subTrack->getSampleSize(); |
| TSoundTrackP riseTrack = 0; |
| |
| if (m_lastOffset) |
| { |
| int offset; |
| getAmplitude(offset, subTrack, 0L); |
| |
| offset = m_lastOffset - offset; |
| if (offset) { |
| TSoundTrackP st = TSop::crossFade(m_lastTrack, subTrack, 0.3); |
| memcpy(whdr->lpData, st->getRawData(), |
| st->getSampleCount() * sampleSize); |
| } |
| } else |
| { |
| riseTrack = TSop::fadeIn(subTrack, 0.3); |
| whdr1 = prepareWaveHeader(m_devImp->m_wout, riseTrack, count); |
| } |
| |
| whdr->dwUser = count; |
| ++count; |
| MMRESULT ret = |
| waveOutPrepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) { |
| delete[] whdr->lpData; |
| delete whdr; |
| return; |
| } |
| |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| |
| TSoundTrackP decayTrack = 0; |
| if (m_queuedItems.size() <= 7) { |
| if (m_lastOffset) |
| { |
| decayTrack = TSop::fadeOut(subTrack, 0.3); |
| whdr2 = prepareWaveHeader(m_devImp->m_wout, decayTrack, count); |
| } |
| } |
| |
| if (whdr1 && whdr1->dwFlags & WHDR_PREPARED) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr1, riseTrack); |
| getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L); |
| } |
| } |
| |
| if (whdr && whdr->dwFlags & WHDR_PREPARED) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr, subTrack); |
| getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L); |
| { |
| TThread::MutexLocker sl(&m_devImp->m_mutex); |
| m_devImp->m_isPlaying = true; |
| m_devImp->m_stopped = false; |
| } |
| } |
| } |
| |
| if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) { |
| ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| pushBack(whdr2, decayTrack); |
| if (decayTrack->isSampleSigned()) |
| m_lastOffset = 0; |
| else |
| m_lastOffset = 127; |
| } |
| } |
| return; |
| } |
| |
| if ((int)m_items.size() == m_slotCount) { |
| WAVEHDR *item = m_items.front().first; |
| MMRESULT ret = |
| waveOutUnprepareHeader(m_devImp->m_wout, item, sizeof(WAVEHDR)); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| delete[] item->lpData; |
| delete item; |
| } |
| |
| m_items.pop_front(); |
| |
| if (m_queuedItems.size() != 0) { |
| WAVEHDR *item = m_items.front().first; |
| int sampleSize = m_items.front().second->getSampleSize(); |
| int offset; |
| getAmplitude(offset, m_items.front().second, 0L); |
| |
| offset = m_lastOffset - offset; |
| if (offset) { |
| TSoundTrackP st = |
| TSop::crossFade(m_lastTrack, m_items.front().second, 0.3); |
| memcpy(item->lpData, st->getRawData(), |
| st->getSampleCount() * sampleSize); |
| } |
| } |
| } |
| |
| WAVEHDR *whdr = prepareWaveHeader(m_devImp->m_wout, subTrack, count); |
| |
| assert(whdr && whdr->dwFlags & WHDR_PREPARED); |
| m_items.push_back(std::make_pair(whdr, subTrack)); |
| assert((int)m_items.size() <= m_slotCount); |
| } |
| |
| |
| |
| |
| WAVEHDR *WavehdrQueue::get() { |
| TThread::MutexLocker sl(&m_mutex); |
| if (m_items.size() == 0) return 0; |
| |
| WAVEHDR *whdr = m_items.front().first; |
| assert(whdr->dwFlags & WHDR_PREPARED); |
| |
| pushBack(whdr, m_items.front().second); |
| getAmplitude(m_lastOffset, m_items.front().second, |
| m_items.front().second->getSampleCount() - 1L); |
| m_items.pop_front(); |
| |
| return whdr; |
| } |
| |
| |
| |
| |
| bool WavehdrQueue::popFront(int count) { |
| TThread::MutexLocker sl(&m_mutex); |
| |
| if (m_queuedItems.size() <= 0) return false; |
| WAVEHDR *whdr = m_queuedItems.front(); |
| |
| |
| while ((DWORD)count > whdr->dwUser) { |
| MMRESULT ret = |
| waveOutUnprepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| m_queuedItems.pop_front(); |
| delete[] whdr->lpData; |
| delete whdr; |
| whdr = m_queuedItems.front(); |
| } |
| } |
| assert(whdr->dwFlags & WHDR_DONE); |
| m_queuedItems.pop_front(); |
| delete[] whdr->lpData; |
| delete whdr; |
| return true; |
| } |
| |
| |
| |
| void WavehdrQueue::pushBack(WAVEHDR *whdr, TSoundTrackP st) { |
| TThread::MutexLocker sl(&m_mutex); |
| m_queuedItems.push_back(whdr); |
| m_lastTrack = st; |
| } |
| |
| |
| |
| int WavehdrQueue::size() { |
| TThread::MutexLocker sl(&m_mutex); |
| int size = m_queuedItems.size(); |
| return size; |
| } |
| |
| |
| |
| void WavehdrQueue::clear() { |
| TThread::MutexLocker sl(&m_mutex); |
| m_items.clear(); |
| m_lastTrack = TSoundTrackP(); |
| std::list<WAVEHDR *>::iterator it; |
| for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) { |
| WAVEHDR *wvhdr = *it; |
| delete[] wvhdr->lpData; |
| delete wvhdr; |
| } |
| m_queuedItems.clear(); |
| } |
| |
| |
| |
| bool WavehdrQueue::isAllQueuedItemsPlayed() { |
| std::list<WAVEHDR *>::iterator it; |
| bool finished = true; |
| for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) { |
| WAVEHDR *wvhdr = *it; |
| finished = finished && (wvhdr->dwFlags & WHDR_DONE); |
| } |
| return finished; |
| } |
| |
| |
| |
| TSoundOutputDeviceImp::TSoundOutputDeviceImp() |
| : m_stopped(true) |
| , m_isPlaying(false) |
| , m_looped(false) |
| , m_scrubbing(false) |
| , m_wout(0) { |
| m_whdrQueue = new WavehdrQueue(this, 4); |
| |
| insertAllRate(); |
| if (!verifyRate()) |
| throw TSoundDeviceException(TSoundDeviceException::FailedInit, |
| "Unable to verify supported rates"); |
| |
| m_closeDevice = CreateEvent(NULL, |
| FALSE, |
| FALSE, |
| NULL); |
| } |
| |
| |
| |
| TSoundOutputDeviceImp::~TSoundOutputDeviceImp() { delete m_whdrQueue; } |
| |
| |
| |
| bool TSoundOutputDeviceImp::doOpenDevice(const TSoundTrackFormat &format) { |
| WAVEFORMATEX wf; |
| wf.wFormatTag = format.m_sampleType & TSound::WMASK; |
| wf.nChannels = format.m_channelCount; |
| wf.nSamplesPerSec = format.m_sampleRate; |
| wf.wBitsPerSample = format.m_bitPerSample; |
| wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) >> 3; |
| wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; |
| wf.cbSize = 0; |
| |
| TThread::MutexLocker sl(&m_mutex); |
| CloseHandle(CreateThread(NULL, 0, MyWaveOutCallbackThread, (LPVOID)this, 0, |
| &m_notifyThreadId)); |
| |
| MMRESULT ret; |
| if ((ret = waveOutOpen(&m_wout, WAVE_MAPPER, &wf, (DWORD_PTR)m_notifyThreadId, |
| (DWORD_PTR)this, CALLBACK_THREAD)) != |
| MMSYSERR_NOERROR) { |
| while (!PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0)) |
| ; |
| } |
| if (ret != MMSYSERR_NOERROR) return false; |
| |
| if (ret != MMSYSERR_NOERROR) return false; |
| m_currentFormat = format; |
| return (ret == MMSYSERR_NOERROR); |
| } |
| |
| |
| |
| bool TSoundOutputDeviceImp::doPlay(WAVEHDR *whdr, |
| const TSoundTrackFormat format) { |
| TThread::MutexLocker sl(&m_mutex); |
| if (!m_wout || (m_wout && m_currentFormat != format)) doOpenDevice(format); |
| |
| MMRESULT ret; |
| ret = waveOutWrite(m_wout, whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) return false; |
| m_stopped = false; |
| m_isPlaying = true; |
| |
| return true; |
| } |
| |
| |
| |
| bool TSoundOutputDeviceImp::doCloseDevice() { |
| TThread::MutexLocker sl(&m_mutex); |
| if (m_wout) { |
| MMRESULT ret = waveOutReset(m_wout); |
| if (ret != MMSYSERR_NOERROR) return false; |
| ret = waveOutClose(m_wout); |
| if (ret != MMSYSERR_NOERROR) return false; |
| m_wout = 0; |
| m_stopped = true; |
| m_isPlaying = false; |
| m_looped = false; |
| } |
| |
| PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0); |
| return true; |
| } |
| |
| |
| |
| void TSoundOutputDeviceImp::insertAllRate() { |
| m_supportedRate.insert(8000); |
| m_supportedRate.insert(11025); |
| m_supportedRate.insert(16000); |
| m_supportedRate.insert(22050); |
| m_supportedRate.insert(32000); |
| m_supportedRate.insert(44100); |
| m_supportedRate.insert(48000); |
| m_supportedRate.insert(96000); |
| m_supportedRate.insert(192000); |
| } |
| |
| |
| |
| bool TSoundOutputDeviceImp::verifyRate() { |
| std::set<int>::iterator it; |
| |
| for (it = m_supportedRate.begin(); it != m_supportedRate.end();) { |
| MMRESULT ret; |
| WAVEFORMATEX wf; |
| |
| wf.wFormatTag = WAVE_FORMAT_PCM; |
| wf.nChannels = 1; |
| wf.nSamplesPerSec = *it; |
| wf.wBitsPerSample = 8; |
| wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) >> 3; |
| wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; |
| wf.cbSize = 0; |
| |
| ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| ++it; |
| continue; |
| } |
| if (ret == WAVERR_BADFORMAT) |
| it = m_supportedRate.erase(it); |
| else |
| return false; |
| } |
| if (m_supportedRate.end() == m_supportedRate.begin()) return false; |
| |
| return true; |
| } |
| |
| |
| |
| TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp()) {} |
| |
| |
| |
| TSoundOutputDevice::~TSoundOutputDevice() { |
| close(); |
| WaitForSingleObject(m_imp->m_closeDevice, INFINITE); |
| CloseHandle(m_imp->m_closeDevice); |
| } |
| |
| |
| |
| namespace { |
| DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter) { |
| TSoundOutputDeviceImp *devImp = (TSoundOutputDeviceImp *)lpParameter; |
| if (!devImp) return 0; |
| |
| MSG msg; |
| BOOL bRet; |
| while (bRet = GetMessage(&msg, NULL, 0, 0)) { |
| if (bRet == -1) { |
| |
| break; |
| } |
| |
| switch (msg.message) { |
| case MM_WOM_DONE: { |
| WAVEHDR *pWaveHdr = ((WAVEHDR *)msg.lParam); |
| { |
| TThread::MutexLocker sl(&devImp->m_mutex); |
| if (devImp->m_looped) { |
| devImp->doPlay(pWaveHdr, devImp->m_currentFormat); |
| continue; |
| } |
| } |
| |
| WAVEHDR *whdr = 0; |
| if (devImp->m_whdrQueue->popFront(pWaveHdr->dwUser)) { |
| whdr = devImp->m_whdrQueue->get(); |
| if (whdr) devImp->doPlay(whdr, devImp->m_currentFormat); |
| |
| WaitForSingleObject(devImp->m_closeDevice, INFINITE); |
| CloseHandle(devImp->m_closeDevice); |
| |
| MMRESULT ret = |
| waveOutUnprepareHeader(devImp->m_wout, pWaveHdr, sizeof(WAVEHDR)); |
| if (ret == MMSYSERR_NOERROR) { |
| delete pWaveHdr->lpData; |
| delete pWaveHdr; |
| } |
| } |
| |
| if (!whdr && devImp->m_whdrQueue->size() == 0) { |
| std::set<TSoundOutputDeviceListener *>::iterator it = |
| devImp->m_listeners.begin(); |
| for (; it != devImp->m_listeners.end(); ++it) { |
| #ifndef TNZCORE_LIGHT |
| EndPlayMsg *event = new EndPlayMsg(*it); |
| event->send(); |
| #else |
| assert(false); |
| #endif |
| } |
| devImp->doCloseDevice(); |
| } |
| |
| continue; |
| } |
| |
| case MM_WOM_CLOSE: |
| break; |
| |
| default: |
| continue; |
| } |
| } |
| |
| SetEvent(devImp->m_closeDevice); |
| return 0; |
| } |
| |
| void getAmplitude(int &litude, const TSoundTrackP st, TINT32 sample) { |
| TSoundTrackP snd = st; |
| amplitude = 0; |
| int k = 0; |
| for (k = 0; k < snd->getChannelCount(); ++k) |
| amplitude += (int)snd->getPressure(sample, k); |
| amplitude /= k; |
| } |
| } |
| |
| |
| |
| bool TSoundOutputDevice::installed() { |
| int ndev = waveOutGetNumDevs(); |
| if (ndev <= 0) return false; |
| return true; |
| } |
| |
| |
| |
| bool TSoundOutputDevice::open(const TSoundTrackP &st) { |
| return m_imp->doOpenDevice(st->getFormat()); |
| } |
| |
| |
| |
| void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) { |
| m_imp->m_listeners.insert(listener); |
| } |
| |
| |
| |
| void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) { |
| m_imp->m_listeners.erase(listener); |
| } |
| |
| |
| |
| void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, |
| bool loop, bool scrubbing) { |
| assert((scrubbing && !loop) || !scrubbing); |
| if (!st->getSampleCount()) return; |
| TSoundTrackFormat format; |
| TSoundTrackP subTrack; |
| |
| { |
| TThread::MutexLocker sl(&m_imp->m_mutex); |
| if (m_imp->m_looped) |
| throw TSoundDeviceException( |
| TSoundDeviceException::Busy, |
| "Unable to queue another playback when the sound player is looping"); |
| |
| format = st->getFormat(); |
| try { |
| TSoundTrackFormat fmt = getPreferredFormat(format); |
| if (fmt != format) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| "Unsupported Format"); |
| } |
| } catch (TSoundDeviceException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| e.getMessage()); |
| } |
| |
| assert(s1 >= s0); |
| subTrack = st->extract(s0, s1); |
| m_imp->m_looped = loop; |
| m_imp->m_scrubbing = scrubbing; |
| } |
| |
| if (!m_imp->m_wout) m_imp->doOpenDevice(format); |
| |
| m_imp->m_whdrQueue->put(subTrack); |
| } |
| |
| |
| |
| void TSoundOutputDevice::stop() { |
| if ((m_imp->m_wout) && m_imp->m_isPlaying) { |
| MMRESULT ret = waveOutReset(m_imp->m_wout); |
| if (ret != MMSYSERR_NOERROR) return; |
| |
| TThread::MutexLocker sl(&m_imp->m_mutex); |
| m_imp->m_looped = false; |
| m_imp->m_stopped = true; |
| m_imp->m_isPlaying = false; |
| m_imp->m_whdrQueue->clear(); |
| } |
| } |
| |
| |
| |
| bool TSoundOutputDevice::close() { return m_imp->doCloseDevice(); } |
| |
| |
| |
| bool TSoundOutputDevice::isPlaying() const { return m_imp->m_isPlaying; } |
| |
| |
| |
| bool TSoundOutputDevice::isAllQueuedItemsPlayed() { |
| return m_imp->m_whdrQueue->isAllQueuedItemsPlayed(); |
| } |
| |
| |
| |
| bool TSoundOutputDevice::isLooping() { |
| TThread::MutexLocker sl(&m_imp->m_mutex); |
| return m_imp->m_looped; |
| } |
| |
| |
| |
| void TSoundOutputDevice::setLooping(bool loop) { |
| TThread::MutexLocker sl(&m_imp->m_mutex); |
| m_imp->m_looped = loop; |
| } |
| |
| |
| |
| TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(TUINT32 sampleRate, |
| int channelCount, |
| int bitPerSample) { |
| TSoundTrackFormat fmt; |
| |
| |
| |
| std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate); |
| if (it == m_imp->m_supportedRate.end()) { |
| it = std::max_element(m_imp->m_supportedRate.begin(), |
| m_imp->m_supportedRate.end()); |
| if (it != m_imp->m_supportedRate.end()) |
| sampleRate = *(m_imp->m_supportedRate.rbegin()); |
| else |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| "There isn't a supported format"); |
| } else |
| sampleRate = *it; |
| |
| |
| if (bitPerSample <= 8) { |
| bitPerSample = 8; |
| fmt.m_sampleType = TSound::UINT; |
| } else if (bitPerSample <= 16) { |
| bitPerSample = 16; |
| fmt.m_sampleType = TSound::INT; |
| } else if (bitPerSample <= 24) { |
| bitPerSample = 24; |
| fmt.m_sampleType = TSound::INT; |
| } else if (bitPerSample <= 32) { |
| bitPerSample = 32; |
| fmt.m_sampleType = TSound::FLOAT; |
| } |
| |
| |
| if (channelCount <= 1) |
| channelCount = 1; |
| else |
| channelCount = 2; |
| |
| fmt.m_bitPerSample = bitPerSample; |
| fmt.m_channelCount = channelCount; |
| fmt.m_sampleRate = sampleRate; |
| |
| return fmt; |
| } |
| |
| |
| |
| TSoundTrackFormat TSoundOutputDevice::getPreferredFormat( |
| const TSoundTrackFormat &format) { |
| try { |
| return getPreferredFormat(format.m_sampleRate, format.m_channelCount, |
| format.m_bitPerSample); |
| } catch (TSoundDeviceException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| e.getMessage()); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class WaveFormat final : public WAVEFORMATEX { |
| public: |
| WaveFormat(){}; |
| WaveFormat(unsigned char channelCount, TUINT32 sampleRate, |
| unsigned char bitPerSample, int sampleFormat); |
| |
| ~WaveFormat(){}; |
| }; |
| |
| WaveFormat::WaveFormat(unsigned char channelCount, TUINT32 sampleRate, |
| unsigned char bitPerSample, int sampleFormat) { |
| wFormatTag = sampleFormat & TSound::WMASK; |
| nChannels = channelCount; |
| nSamplesPerSec = sampleRate; |
| wBitsPerSample = bitPerSample; |
| nBlockAlign = (channelCount * bitPerSample) >> 3; |
| nAvgBytesPerSec = nBlockAlign * sampleRate; |
| cbSize = 0; |
| } |
| |
| |
| |
| class WinSoundInputDevice { |
| public: |
| WinSoundInputDevice(); |
| ~WinSoundInputDevice(); |
| |
| void open(const WaveFormat &wf); |
| void close(); |
| |
| void prepareHeader(char *sampleBuffer, TUINT32 sampleBufferSize, |
| WAVEHDR &whdr); |
| |
| void unprepareHeader(WAVEHDR &whdr); |
| |
| void addBlock(WAVEHDR &whdr); |
| |
| void start(); |
| |
| void reset(); |
| void stop(); |
| |
| HANDLE m_hBlockDone; |
| |
| private: |
| HWAVEIN m_hWaveIn; |
| }; |
| |
| |
| |
| WinSoundInputDevice::WinSoundInputDevice() : m_hWaveIn(0) { |
| m_hBlockDone = CreateEvent(NULL, |
| FALSE, |
| FALSE, |
| NULL); |
| } |
| |
| |
| |
| WinSoundInputDevice::~WinSoundInputDevice() { CloseHandle(m_hBlockDone); } |
| |
| |
| |
| void WinSoundInputDevice::open(const WaveFormat &wf) { |
| if (m_hWaveIn) close(); |
| |
| MMRESULT ret = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &wf, (DWORD_PTR)recordCB, |
| (DWORD_PTR)m_hBlockDone, CALLBACK_FUNCTION); |
| |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Error to open the input device"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::close() { |
| if (!m_hWaveIn) return; |
| |
| MMRESULT ret = waveInClose(m_hWaveIn); |
| |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Error to close the input device"); |
| } |
| m_hWaveIn = 0; |
| } |
| |
| |
| |
| void WinSoundInputDevice::prepareHeader(char *sampleBuffer, |
| TUINT32 sampleBufferSize, |
| WAVEHDR &whdr) { |
| whdr.lpData = sampleBuffer; |
| whdr.dwBufferLength = sampleBufferSize; |
| whdr.dwFlags = 0; |
| whdr.dwLoops = 0; |
| |
| MMRESULT ret = waveInPrepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to prepare a wave header"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::unprepareHeader(WAVEHDR &whdr) { |
| MMRESULT ret = waveInUnprepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to unprepare a wave header"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::addBlock(WAVEHDR &whdr) { |
| MMRESULT ret = waveInAddBuffer(m_hWaveIn, &whdr, sizeof(WAVEHDR)); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to add a waveheader"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::start() { |
| int ret = waveInStart(m_hWaveIn); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to add a waveheader"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::reset() { |
| if (!m_hWaveIn) return; |
| |
| MMRESULT ret = waveInReset(m_hWaveIn); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to add a waveheader"); |
| } |
| } |
| |
| |
| |
| void WinSoundInputDevice::stop() { |
| MMRESULT ret = waveInStop(m_hWaveIn); |
| if (ret != MMSYSERR_NOERROR) { |
| throw TException("Unable to add a waveheader"); |
| } |
| } |
| |
| |
| |
| #ifndef TNZCORE_LIGHT |
| class RecordTask final : public TThread::Runnable { |
| public: |
| RecordTask(std::shared_ptr<TSoundInputDeviceImp> dev) |
| : Runnable(), m_dev(std::move(dev)) {} |
| |
| ~RecordTask() {} |
| |
| void run() override; |
| |
| std::shared_ptr<TSoundInputDeviceImp> m_dev; |
| }; |
| |
| #endif |
| |
| |
| |
| class TSoundInputDeviceImp final : public WinSoundInputDevice { |
| public: |
| bool m_allocateBuff; |
| bool m_isRecording; |
| bool m_supportVolume; |
| int m_index; |
| TINT32 m_byteRecorded; |
| |
| TSoundTrackP m_st; |
| TSoundTrackFormat m_format; |
| |
| #ifndef TNZCORE_LIGHT |
| TThread::Executor m_executor; |
| #endif |
| std::vector<WAVEHDR> m_whdr; |
| std::vector<char *> m_recordedBlocks; |
| std::set<int> m_supportedRate; |
| |
| HANDLE m_hLastBlockDone; |
| |
| TSoundInputDeviceImp(); |
| ~TSoundInputDeviceImp(); |
| |
| void insertAllRate(); |
| bool verifyRate(); |
| }; |
| |
| |
| |
| TSoundInputDeviceImp::TSoundInputDeviceImp() |
| : m_allocateBuff(false) |
| , m_isRecording(false) |
| , m_supportVolume(false) |
| , m_index(0) |
| , m_byteRecorded(0) |
| , m_format() |
| , m_whdr(3) |
| , m_recordedBlocks() |
| , m_supportedRate() { |
| m_hLastBlockDone = CreateEvent(NULL, |
| FALSE, |
| FALSE, |
| NULL); |
| } |
| |
| |
| |
| TSoundInputDeviceImp::~TSoundInputDeviceImp() { |
| if (m_isRecording) { |
| try { |
| reset(); |
| WaitForSingleObject(m_hLastBlockDone, INFINITE); |
| int i; |
| for (i = 0; i < (int)m_recordedBlocks.size(); ++i) |
| delete[] m_recordedBlocks[i]; |
| close(); |
| } catch (TException &) { |
| } |
| } |
| CloseHandle(m_hLastBlockDone); |
| } |
| |
| |
| |
| void TSoundInputDeviceImp::insertAllRate() { |
| m_supportedRate.insert(8000); |
| m_supportedRate.insert(11025); |
| m_supportedRate.insert(16000); |
| m_supportedRate.insert(22050); |
| m_supportedRate.insert(32000); |
| m_supportedRate.insert(44100); |
| m_supportedRate.insert(48000); |
| m_supportedRate.insert(96000); |
| m_supportedRate.insert(192000); |
| } |
| |
| |
| |
| bool TSoundInputDeviceImp::verifyRate() { |
| std::set<int>::iterator it; |
| |
| for (it = m_supportedRate.begin(); it != m_supportedRate.end();) { |
| MMRESULT ret; |
| WAVEFORMATEX wf; |
| |
| wf.wFormatTag = WAVE_FORMAT_PCM; |
| wf.nChannels = 1; |
| wf.nSamplesPerSec = *it; |
| wf.wBitsPerSample = 8; |
| wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) >> 3; |
| wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; |
| wf.cbSize = 0; |
| |
| ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| ++it; |
| continue; |
| } |
| if (ret == WAVERR_BADFORMAT) |
| it = m_supportedRate.erase(it); |
| else |
| return false; |
| } |
| if (m_supportedRate.end() == m_supportedRate.begin()) return false; |
| |
| return true; |
| } |
| |
| |
| namespace { |
| void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, |
| DWORD_PTR dwParam1, DWORD dwParam2) { |
| WAVEHDR *whdr = (WAVEHDR *)dwParam1; |
| HANDLE *blockDone = (HANDLE *)dwInstance; |
| |
| if (uMsg != MM_WIM_DATA) return; |
| SetEvent(blockDone); |
| } |
| } |
| |
| |
| |
| TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp()) { |
| m_imp->insertAllRate(); |
| if (!m_imp->verifyRate()) |
| throw TSoundDeviceException(TSoundDeviceException::FailedInit, |
| "Unable to verify supported rates"); |
| if (supportsVolume()) m_imp->m_supportVolume = true; |
| } |
| |
| |
| |
| TSoundInputDevice::~TSoundInputDevice() {} |
| |
| |
| |
| bool TSoundInputDevice::installed() { |
| int ndev = waveInGetNumDevs(); |
| if (ndev <= 0) return false; |
| return true; |
| } |
| |
| |
| |
| #ifndef TNZCORE_LIGHT |
| |
| void TSoundInputDevice::record(const TSoundTrackFormat &format, |
| Source devtype) { |
| if (m_imp->m_isRecording) |
| throw TSoundDeviceException(TSoundDeviceException::Busy, |
| "Just another recoding is in progress"); |
| |
| |
| |
| |
| try { |
| TSoundTrackFormat fmt = getPreferredFormat(format); |
| if (fmt != format) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| "Unsupported Format"); |
| } |
| } catch (TSoundDeviceException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| e.getMessage()); |
| } |
| |
| if (!setRecordLine(devtype)) |
| throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice, |
| "Problems to set input source line to record"); |
| |
| m_imp->m_format = format; |
| m_imp->m_st = 0; |
| ResetEvent(m_imp->m_hLastBlockDone); |
| ResetEvent(m_imp->m_hBlockDone); |
| m_imp->m_allocateBuff = true; |
| m_imp->m_isRecording = true; |
| m_imp->m_index = 0; |
| m_imp->m_recordedBlocks.clear(); |
| m_imp->m_byteRecorded = 0; |
| TINT32 bytePerSec = format.m_sampleRate * |
| ((format.m_bitPerSample * format.m_channelCount) >> 3); |
| |
| try { |
| WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate, |
| m_imp->m_format.m_bitPerSample, m_imp->m_format.m_sampleType); |
| |
| m_imp->open(wf); |
| } catch (TException &e) { |
| m_imp->m_isRecording = false; |
| throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice, |
| e.getMessage()); |
| } |
| for (; m_imp->m_index < (int)(m_imp->m_whdr.size() - 1); ++m_imp->m_index) { |
| try { |
| m_imp->prepareHeader(new char[bytePerSec], bytePerSec, |
| m_imp->m_whdr[m_imp->m_index]); |
| m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]); |
| } catch (TException &e) { |
| m_imp->m_isRecording = false; |
| for (int j = 0; j < (int)(m_imp->m_whdr.size() - 1); ++j) { |
| if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) { |
| try { |
| m_imp->unprepareHeader(m_imp->m_whdr[j]); |
| } catch (TException &e) { |
| throw TSoundDeviceException( |
| TSoundDeviceException::UnableCloseDevice, e.getMessage()); |
| } |
| delete[] m_imp->m_whdr[j].lpData; |
| } else if (j == m_imp->m_index) |
| delete[] m_imp->m_whdr[j].lpData; |
| } |
| throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, |
| e.getMessage()); |
| } |
| } |
| |
| m_imp->m_executor.addTask(new RecordTask(m_imp)); |
| } |
| |
| |
| |
| void TSoundInputDevice::record(const TSoundTrackP &st, Source devtype) { |
| if (m_imp->m_isRecording) |
| throw TSoundDeviceException(TSoundDeviceException::Busy, |
| "Just another recoding is in progress"); |
| |
| m_imp->m_format = st->getFormat(); |
| |
| |
| |
| |
| try { |
| TSoundTrackFormat fmt = getPreferredFormat(st->getFormat()); |
| if (fmt != st->getFormat()) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| "Unsupported Format"); |
| } |
| } catch (TSoundDeviceException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| e.getMessage()); |
| } |
| |
| if (!setRecordLine(devtype)) |
| throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice, |
| "Problems to set input source line to record"); |
| |
| m_imp->m_st = st; |
| m_imp->m_allocateBuff = false; |
| m_imp->m_isRecording = true; |
| ResetEvent(m_imp->m_hLastBlockDone); |
| ResetEvent(m_imp->m_hBlockDone); |
| m_imp->m_index = 0; |
| m_imp->m_recordedBlocks.clear(); |
| m_imp->m_byteRecorded = 0; |
| try { |
| WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate, |
| m_imp->m_format.m_bitPerSample, m_imp->m_format.m_sampleType); |
| |
| m_imp->open(wf); |
| m_imp->prepareHeader( |
| (char *)st->getRawData(), |
| st->getSampleCount() * |
| ((st->getBitPerSample() * st->getChannelCount()) >> 3), |
| m_imp->m_whdr[m_imp->m_index]); |
| m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]); |
| } catch (TException &e) { |
| m_imp->m_isRecording = false; |
| if (m_imp->m_whdr[m_imp->m_index].dwFlags & WHDR_PREPARED) |
| m_imp->unprepareHeader(m_imp->m_whdr[m_imp->m_index]); |
| |
| throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, |
| e.getMessage()); |
| } |
| |
| m_imp->m_executor.addTask(new RecordTask(m_imp)); |
| } |
| #endif |
| |
| |
| |
| TSoundTrackP TSoundInputDevice::stop() { |
| if (!m_imp->m_isRecording) return 0; |
| |
| m_imp->m_isRecording = false; |
| try { |
| m_imp->reset(); |
| } catch (TException &e) { |
| for (int j = 0; j < (int)m_imp->m_whdr.size(); ++j) { |
| if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) { |
| try { |
| m_imp->unprepareHeader(m_imp->m_whdr[j]); |
| } catch (TException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, |
| e.getMessage()); |
| } |
| delete[] m_imp->m_whdr[j].lpData; |
| } |
| } |
| throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice, |
| e.getMessage()); |
| } |
| |
| if (m_imp->m_allocateBuff) { |
| WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE); |
| |
| TSoundTrackP st = TSoundTrack::create( |
| m_imp->m_format, |
| m_imp->m_byteRecorded / ((m_imp->m_format.m_bitPerSample * |
| m_imp->m_format.m_channelCount) >> |
| 3)); |
| |
| TINT32 bytePerSec = |
| m_imp->m_format.m_sampleRate * |
| ((m_imp->m_format.m_bitPerSample * m_imp->m_format.m_channelCount) >> |
| 3); |
| |
| int i; |
| for (i = 0; i < (int)(m_imp->m_recordedBlocks.size() - 1); ++i) { |
| memcpy((void *)(st->getRawData() + bytePerSec * i), |
| m_imp->m_recordedBlocks[i], bytePerSec); |
| delete[] m_imp->m_recordedBlocks[i]; |
| } |
| |
| TINT32 lastBlockSize = m_imp->m_byteRecorded - (bytePerSec * i); |
| |
| if (lastBlockSize != 0) { |
| memcpy((void *)(st->getRawData() + bytePerSec * i), |
| m_imp->m_recordedBlocks[i], lastBlockSize); |
| delete[] m_imp->m_recordedBlocks[i]; |
| } |
| |
| try { |
| m_imp->close(); |
| } catch (TException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice, |
| e.getMessage()); |
| } |
| return st; |
| } else { |
| WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE); |
| try { |
| m_imp->close(); |
| } catch (TException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice, |
| e.getMessage()); |
| } |
| return m_imp->m_st; |
| } |
| } |
| |
| |
| #ifndef TNZCORE_LIGHT |
| |
| void RecordTask::run() { |
| m_dev->start(); |
| |
| if (m_dev->m_allocateBuff) { |
| TINT32 bytePerSec = |
| m_dev->m_format.m_sampleRate * |
| ((m_dev->m_format.m_bitPerSample * m_dev->m_format.m_channelCount) >> |
| 3); |
| |
| while (m_dev->m_whdr[(m_dev->m_index + 1) % m_dev->m_whdr.size()].dwFlags & |
| WHDR_PREPARED) { |
| if (m_dev->m_isRecording) |
| WaitForSingleObject(m_dev->m_hBlockDone, INFINITE); |
| int indexToPrepare = m_dev->m_index; |
| |
| |
| m_dev->m_index = (m_dev->m_index + 1) % m_dev->m_whdr.size(); |
| if (m_dev->m_whdr[m_dev->m_index].dwFlags & WHDR_DONE) { |
| TINT32 byteRecorded = m_dev->m_whdr[m_dev->m_index].dwBytesRecorded; |
| if (byteRecorded) { |
| m_dev->m_recordedBlocks.push_back( |
| m_dev->m_whdr[m_dev->m_index].lpData); |
| m_dev->m_byteRecorded += byteRecorded; |
| } |
| |
| try { |
| m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]); |
| } catch (TException &) { |
| for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i) |
| delete[] m_dev->m_recordedBlocks[i]; |
| return; |
| } |
| |
| if (byteRecorded == 0) { |
| delete[] m_dev->m_whdr[m_dev->m_index].lpData; |
| } |
| |
| |
| |
| if (m_dev->m_isRecording) { |
| try { |
| m_dev->prepareHeader(new char[bytePerSec], bytePerSec, |
| m_dev->m_whdr[indexToPrepare]); |
| |
| m_dev->addBlock(m_dev->m_whdr[indexToPrepare]); |
| } catch (TException &) { |
| m_dev->m_isRecording = false; |
| for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i) |
| delete[] m_dev->m_recordedBlocks[i]; |
| return; |
| } |
| } |
| |
| } else |
| m_dev->m_index = indexToPrepare; |
| } |
| } else { |
| if (m_dev->m_isRecording) |
| WaitForSingleObject(m_dev->m_hBlockDone, INFINITE); |
| try { |
| m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]); |
| m_dev->m_isRecording = false; |
| } catch (TException &) { |
| m_dev->m_isRecording = false; |
| return; |
| } |
| } |
| |
| SetEvent(m_dev->m_hLastBlockDone); |
| return; |
| } |
| |
| |
| #endif |
| |
| double TSoundInputDevice::getVolume() { |
| DWORD dwVolumeControlID; |
| |
| UINT nNumMixers; |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| nNumMixers = mixerGetNumDevs(); |
| |
| if (nNumMixers == 0) |
| throw TSoundDeviceException(TSoundDeviceException::NoMixer, |
| "Doesn't exist a audio mixer device"); |
| |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| |
| MIXERCONTROL mxc; |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_VOLUME); |
| if (ret == MIXERR_INVALCONTROL) |
| throw TSoundDeviceException( |
| TSoundDeviceException::UnableVolume, |
| "Is not possible to obtain info of volume by mixer"); |
| |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| dwVolumeControlID = mxc.dwControlID; |
| |
| MIXERCONTROLDETAILS_UNSIGNED mxcdVolume; |
| |
| ret = getControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems, |
| &mxcdVolume); |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| DWORD dwVal = mxcdVolume.dwValue; |
| |
| return (double)dwVal * 10.0 / (double)mxc.Bounds.dwMaximum; |
| } |
| |
| |
| |
| bool TSoundInputDevice::setVolume(double value) { |
| DWORD dwVolumeControlID, dwMaximum; |
| |
| UINT nNumMixers; |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| nNumMixers = mixerGetNumDevs(); |
| |
| if (nNumMixers == 0) |
| throw TSoundDeviceException(TSoundDeviceException::NoMixer, |
| "Doesn't exist a audio mixer device"); |
| |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| throw TException("Error to obtain info by mixer"); |
| |
| |
| MIXERCONTROL mxc; |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_VOLUME); |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| dwMaximum = mxc.Bounds.dwMaximum; |
| dwVolumeControlID = mxc.dwControlID; |
| |
| int newValue; |
| double fattProp = ((double)(mxc.Metrics.cSteps - 1) * value) / 10; |
| double delta = (double)(dwMaximum / (mxc.Metrics.cSteps - 1)); |
| newValue = (int)(tround(fattProp) * delta); |
| |
| MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = {(DWORD)newValue}; |
| ret = setControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems, |
| &mxcdVolume); |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| return true; |
| } |
| |
| |
| |
| bool TSoundInputDevice::supportsVolume() { |
| UINT nNumMixers; |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| nNumMixers = mixerGetNumDevs(); |
| |
| if (nNumMixers == 0) |
| throw TSoundDeviceException(TSoundDeviceException::NoMixer, |
| "Doesn't exist a audio mixer device"); |
| |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| |
| MIXERCONTROL mxc; |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_VOLUME); |
| if (ret == MIXERR_INVALCONTROL) return false; |
| |
| if (ret != MMSYSERR_NOERROR) |
| throw TSoundDeviceException(TSoundDeviceException::UnableVolume, |
| "Error to obtain info by mixer"); |
| |
| return true; |
| } |
| |
| |
| |
| bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TSoundTrackFormat TSoundInputDevice::getPreferredFormat(TUINT32 sampleRate, |
| int channelCount, |
| int bitPerSample) { |
| TSoundTrackFormat fmt; |
| |
| |
| |
| std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate); |
| if (it == m_imp->m_supportedRate.end()) { |
| it = std::max_element(m_imp->m_supportedRate.begin(), |
| m_imp->m_supportedRate.end()); |
| if (it != m_imp->m_supportedRate.end()) |
| sampleRate = *(m_imp->m_supportedRate.rbegin()); |
| else |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| "There isn't a supported format"); |
| } else |
| sampleRate = *it; |
| |
| |
| if (bitPerSample <= 8) { |
| bitPerSample = 8; |
| fmt.m_sampleType = TSound::UINT; |
| } else if (bitPerSample <= 16) { |
| bitPerSample = 16; |
| fmt.m_sampleType = TSound::INT; |
| } else if (bitPerSample <= 24) { |
| bitPerSample = 24; |
| fmt.m_sampleType = TSound::INT; |
| } else if (bitPerSample <= 32) { |
| bitPerSample = 32; |
| fmt.m_sampleType = TSound::FLOAT; |
| } |
| |
| |
| if (channelCount <= 1) |
| channelCount = 1; |
| else |
| channelCount = 2; |
| |
| fmt.m_bitPerSample = bitPerSample; |
| fmt.m_channelCount = channelCount; |
| fmt.m_sampleRate = sampleRate; |
| |
| return fmt; |
| } |
| |
| |
| |
| TSoundTrackFormat TSoundInputDevice::getPreferredFormat( |
| const TSoundTrackFormat &format) { |
| try { |
| return getPreferredFormat(format.m_sampleRate, format.m_channelCount, |
| format.m_bitPerSample); |
| } catch (TSoundDeviceException &e) { |
| throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, |
| e.getMessage()); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| namespace { |
| |
| |
| |
| |
| MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID, |
| DWORD dwControlType) { |
| MIXERLINECONTROLS mxlc; |
| mxlc.cbStruct = sizeof(MIXERLINECONTROLS); |
| mxlc.dwLineID = dwLineID; |
| mxlc.dwControlType = dwControlType; |
| mxlc.cControls = 1; |
| mxlc.cbmxctrl = sizeof(MIXERCONTROL); |
| mxlc.pamxctrl = &mxc; |
| MMRESULT ret = mixerGetLineControls( |
| (HMIXEROBJ)hMixer, &mxlc, |
| MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD destination, |
| DWORD source) { |
| MMRESULT ret; |
| |
| mxl.cbStruct = sizeof(mxl); |
| mxl.dwDestination = destination; |
| mxl.dwSource = source; |
| ret = mixerGetLineInfo(0, &mxl, |
| MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_SOURCE); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD dwLineID) { |
| MMRESULT ret; |
| |
| mxl.cbStruct = sizeof(mxl); |
| mxl.dwLineID = dwLineID; |
| ret = mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl, |
| MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_LINEID); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl) { |
| MMRESULT ret; |
| |
| mxl.cbStruct = sizeof(MIXERLINE); |
| mxl.dwComponentType = dwComponentType; |
| ret = |
| mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl, |
| MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_BOOLEAN *mxcdSelectValue) { |
| MMRESULT ret; |
| MIXERCONTROLDETAILS mxcd; |
| mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
| mxcd.dwControlID = dwSelectControlID; |
| mxcd.cChannels = 1; |
| mxcd.cMultipleItems = dwMultipleItems; |
| mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); |
| mxcd.paDetails = mxcdSelectValue; |
| ret = mixerSetControlDetails( |
| (HMIXEROBJ)hMixer, &mxcd, |
| MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) { |
| MMRESULT ret; |
| MIXERCONTROLDETAILS mxcd; |
| mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
| mxcd.dwControlID = dwSelectControlID; |
| mxcd.cChannels = 1; |
| mxcd.cMultipleItems = dwMultipleItems; |
| mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); |
| mxcd.paDetails = mxcdSelectValue; |
| ret = mixerSetControlDetails( |
| (HMIXEROBJ)hMixer, &mxcd, |
| MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); |
| return ret; |
| } |
| |
| |
| |
| |
| |
| MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) { |
| MMRESULT ret; |
| MIXERCONTROLDETAILS mxcd; |
| mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
| mxcd.dwControlID = dwSelectControlID; |
| mxcd.cChannels = 1; |
| mxcd.cMultipleItems = dwMultipleItems; |
| mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); |
| mxcd.paDetails = mxcdSelectValue; |
| ret = mixerGetControlDetails( |
| (HMIXEROBJ)hMixer, &mxcd, |
| MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); |
| return ret; |
| } |
| |
| |
| |
| |
| MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID, |
| DWORD dwMultipleItems, |
| MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText) { |
| MMRESULT ret; |
| MIXERCONTROLDETAILS mxcd; |
| |
| mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
| mxcd.dwControlID = dwSelectControlID; |
| mxcd.cChannels = 1; |
| mxcd.cMultipleItems = dwMultipleItems; |
| mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT); |
| mxcd.paDetails = pmxcdSelectText; |
| ret = mixerGetControlDetails((HMIXEROBJ)0, &mxcd, |
| MIXER_GETCONTROLDETAILSF_LISTTEXT); |
| return ret; |
| } |
| |
| |
| |
| |
| std::string getMixerLineName(DWORD lineID) { |
| MIXERLINE mxl; |
| MMRESULT ret; |
| |
| ret = getLineInfo((HMIXEROBJ)0, mxl, lineID); |
| #ifdef TNZCORE_LIGHT |
| assert(false); |
| return ""; |
| #else |
| return QString::fromWCharArray(mxl.szName).toStdString(); |
| #endif |
| } |
| |
| |
| |
| |
| |
| std::list<DWORD> getMixerSrcLines(DWORD dstComponentType) { |
| std::list<DWORD> srcList; |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| |
| return srcList; |
| |
| int v; |
| for (v = 0; v < (int)mxl.cConnections; v++) { |
| MIXERLINE mxl1; |
| |
| ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v); |
| if (ret == MMSYSERR_NOERROR) srcList.push_back(mxl1.dwLineID); |
| } |
| return srcList; |
| } |
| |
| |
| |
| |
| |
| std::list<DWORD> getMixerSrcLines(DWORD dstComponentType, |
| DWORD srcComponentType) { |
| std::list<DWORD> srcList; |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| |
| return srcList; |
| |
| int v; |
| for (v = 0; v < (int)mxl.cConnections; v++) { |
| MIXERLINE mxl1; |
| |
| ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v); |
| if (ret == MMSYSERR_NOERROR) |
| if (mxl1.dwComponentType == srcComponentType) |
| srcList.push_back(mxl1.dwLineID); |
| } |
| return srcList; |
| } |
| |
| |
| |
| |
| |
| bool isSrcLineSupported(DWORD dstComponentType, DWORD srcComponentType) { |
| |
| |
| MMRESULT ret; |
| MIXERLINE mxl; |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| return false; |
| |
| int v; |
| for (v = 0; v < (int)mxl.cConnections; v++) { |
| MIXERLINE mxl1; |
| |
| ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v); |
| if (ret == MMSYSERR_NOERROR) |
| if (mxl1.dwComponentType == srcComponentType) return true; |
| } |
| |
| return false; |
| } |
| |
| |
| |
| bool activateSrcLine(const MIXERLINE &mxlDst, DWORD componentTypeSrc) { |
| if (!isSrcLineSupported(mxlDst.dwComponentType, componentTypeSrc)) |
| return false; |
| |
| bool bRetVal = true; |
| |
| for (DWORD v = 0; v < mxlDst.cConnections; v++) { |
| MIXERLINE mxlSrc; |
| MMRESULT ret = getLineInfo((HMIXEROBJ)0, mxlSrc, mxlDst.dwDestination, v); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| |
| MIXERCONTROL mxc; |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxlSrc.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_MUTE); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| MIXERCONTROLDETAILS_BOOLEAN mxcdSelectValue; |
| mxcdSelectValue.fValue = |
| mxlSrc.dwComponentType == componentTypeSrc ? 0L : 1L; |
| |
| ret = setControlDetails((HMIXEROBJ)0, mxc.dwControlID, |
| mxc.cMultipleItems, &mxcdSelectValue); |
| if (ret != MMSYSERR_NOERROR) bRetVal = false; |
| } |
| } |
| } |
| return bRetVal; |
| } |
| |
| |
| |
| bool setSrcMixMuxControl(MIXERCONTROL mxc, DWORD componentTypeSrc) { |
| MMRESULT ret; |
| DWORD dwIndexLine; |
| bool found = false; |
| |
| |
| |
| DWORD dwSelectControlID = mxc.dwControlID; |
| DWORD dwMultipleItems = mxc.cMultipleItems; |
| |
| if (dwMultipleItems == 0) return false; |
| |
| |
| |
| |
| std::unique_ptr<MIXERCONTROLDETAILS_LISTTEXT[]> pmxcdSelectText( |
| new MIXERCONTROLDETAILS_LISTTEXT[dwMultipleItems]); |
| |
| if (pmxcdSelectText) { |
| |
| ret = getControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems, |
| pmxcdSelectText.get()); |
| |
| if (ret == MMSYSERR_NOERROR) { |
| for (DWORD dwi = 0; dwi < dwMultipleItems; dwi++) { |
| |
| MIXERLINE mxl; |
| ret = getLineInfo((HMIXEROBJ)0, mxl, pmxcdSelectText[dwi].dwParam1); |
| if (ret == MMSYSERR_NOERROR && |
| mxl.dwComponentType == componentTypeSrc) { |
| dwIndexLine = dwi; |
| found = true; |
| break; |
| } |
| } |
| } |
| |
| if (!found) return false; |
| } |
| |
| if (dwIndexLine >= dwMultipleItems) return false; |
| |
| bool bRetVal = false; |
| |
| std::unique_ptr<MIXERCONTROLDETAILS_BOOLEAN[]> pmxcdSelectValue( |
| new MIXERCONTROLDETAILS_BOOLEAN[dwMultipleItems]); |
| |
| if (pmxcdSelectValue) { |
| ::ZeroMemory(pmxcdSelectValue.get(), |
| dwMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN)); |
| |
| |
| pmxcdSelectValue[dwIndexLine].fValue = |
| (TINT32)1; |
| |
| ret = setControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems, |
| pmxcdSelectValue.get()); |
| if (ret == MMSYSERR_NOERROR) bRetVal = true; |
| } |
| return bRetVal; |
| } |
| |
| |
| |
| bool setRecordLine(TSoundInputDevice::Source typeInput) { |
| DWORD dwComponentTypeSrc; |
| |
| UINT nNumMixers; |
| MMRESULT ret; |
| MIXERLINE mxl = {0}; |
| |
| switch (typeInput) { |
| case TSoundInputDevice::LineIn: |
| dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_LINE |
| |
| |
| ; |
| break; |
| case TSoundInputDevice::DigitalIn: |
| dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_DIGITAL; |
| break; |
| case TSoundInputDevice::CdAudio: |
| dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC; |
| break; |
| default: |
| dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE; |
| } |
| |
| nNumMixers = mixerGetNumDevs(); |
| |
| if (nNumMixers == 0) return false; |
| |
| |
| |
| ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl); |
| if (ret != MMSYSERR_NOERROR) |
| return false; |
| |
| |
| |
| |
| MIXERCONTROL mxc = {0}; |
| |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_MIXER); |
| |
| if (ret != MMSYSERR_NOERROR) { |
| |
| ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID, |
| MIXERCONTROL_CONTROLTYPE_MUX); |
| |
| if (ret != MMSYSERR_NOERROR) { |
| |
| return activateSrcLine(mxl, dwComponentTypeSrc); |
| } else { |
| |
| return setSrcMixMuxControl(mxc, dwComponentTypeSrc); |
| } |
| } else { |
| |
| return setSrcMixMuxControl(mxc, dwComponentTypeSrc); |
| } |
| } |
| |
| |
| |
| MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample, |
| int sampleType, bool input) { |
| WAVEFORMATEX wf; |
| MMRESULT ret; |
| |
| wf.wFormatTag = sampleType & TSound::WMASK; |
| wf.nChannels = channelCount; |
| wf.nSamplesPerSec = sampleRate; |
| wf.wBitsPerSample = bitPerSample; |
| wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) >> 3; |
| wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; |
| wf.cbSize = 0; |
| |
| if (input) |
| ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY); |
| else |
| ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY); |
| return ret; |
| } |
| } |
| |