2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG.
\r
6 * CasparCG is free software: you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation, either version 3 of the License, or
\r
9 * (at your option) any later version.
\r
11 * CasparCG is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
\r
21 #include "..\StdAfx.h"
\r
23 #include "..\utils\thread.h"
\r
24 #include "..\utils\lockable.h"
\r
27 #include <mmsystem.h>
\r
30 #include "..\MediaProducerInfo.h"
\r
31 #include "..\frame\FrameMediaController.h"
\r
32 #include "DirectSoundManager.h"
\r
35 namespace directsound {
\r
37 using namespace audio;
\r
38 using namespace utils;
\r
40 ///////////////////////////////
\r
41 // DirectSoundBufferWorker
\r
42 ///////////////////////////////
\r
43 class DirectSoundBufferWorker : public caspar::audio::ISoundBufferWorker, private utils::IRunnable, private utils::LockableObject
\r
45 friend class DirectSoundManager;
\r
47 DirectSoundBufferWorker(const DirectSoundBufferWorker&);
\r
48 DirectSoundBufferWorker& operator=(const DirectSoundBufferWorker&);
\r
51 static const int BufferLengthInFrames;
\r
53 virtual ~DirectSoundBufferWorker();
\r
56 worker_.Start(this);
\r
62 bool PushChunk(AudioDataChunkPtr);
\r
64 HANDLE GetWaitHandle() {
\r
69 AudioDataChunkPtr GetNextChunk();
\r
72 explicit DirectSoundBufferWorker(LPDIRECTSOUNDBUFFER8 pDirectSound);
\r
73 HRESULT InitSoundBuffer(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps);
\r
75 virtual void Run(HANDLE stopEvent);
\r
76 virtual bool OnUnhandledException(const std::exception&) throw();
\r
77 utils::Thread worker_;
\r
80 void WriteChunkToBuffer(int offset, AudioDataChunkPtr pChunk);
\r
81 int IncreaseFrameIndex(int frameIndex) {
\r
82 return (frameIndex+1)%BufferLengthInFrames;
\r
85 HANDLE notificationEvents_[2];
\r
90 LPDIRECTSOUNDBUFFER8 pSoundBuffer_;
\r
92 std::queue<AudioDataChunkPtr> chunkQueue_;
\r
93 utils::Event writeEvent_;
\r
94 utils::Event startPlayback_;
\r
96 int soundBufferLoadIndex_;
\r
99 typedef std::tr1::shared_ptr<DirectSoundBufferWorker> DirectSoundBufferWorkerPtr;
\r
102 ///////////////////////////////
\r
104 // DirectSoundManager
\r
106 ///////////////////////////////
\r
107 DirectSoundManager::DirectSoundManager() : pDirectSound_(0)
\r
111 DirectSoundManager::~DirectSoundManager()
\r
116 bool DirectSoundManager::Initialize(HWND hWnd, DWORD channels, DWORD samplesPerSec, DWORD bitsPerSample) {
\r
117 #ifndef DISABLE_AUDIO
\r
118 HRESULT hr = E_FAIL;
\r
119 hr = DirectSoundCreate8(NULL, &pDirectSound_, NULL);
\r
121 LOG << TEXT("DirectSound: Failed to create device.");
\r
125 hr = pDirectSound_->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);
\r
127 LOG << TEXT("DirectSound: Failed to set CooperativeLevel.");
\r
131 hr = SetPrimaryBufferFormat(channels, samplesPerSec, bitsPerSample);
\r
133 LOG << TEXT("DirectSound: Failed to set Primarybuffer format.");
\r
140 void DirectSoundManager::Destroy() {
\r
141 if(pDirectSound_ != 0) {
\r
142 pDirectSound_->Release();
\r
147 bool DirectSoundManager::CueAudio(FrameMediaController* pController)
\r
149 MediaProducerInfo clipInfo;
\r
150 #ifndef DISABLE_AUDIO
\r
151 if(pController->GetProducerInfo(&clipInfo) && clipInfo.HaveAudio) {
\r
152 caspar::audio::SoundBufferWorkerPtr pSBW = CreateSoundBufferWorker(clipInfo.AudioChannels, clipInfo.BitsPerAudioSample, clipInfo.AudioSamplesPerSec, 25);
\r
154 pController->AddSoundBufferWorker(pSBW);
\r
162 bool DirectSoundManager::StartAudio(FrameMediaController* pController)
\r
164 #ifndef DISABLE_AUDIO
\r
165 SoundBufferWorkerList& sbwList = pController->GetSoundBufferWorkers();
\r
166 SoundBufferWorkerList::iterator it = sbwList.begin();
\r
167 SoundBufferWorkerList::iterator end = sbwList.end();
\r
168 for(;it != end; ++it)
\r
176 bool DirectSoundManager::StopAudio(FrameMediaController* pController)
\r
178 #ifndef DISABLE_AUDIO
\r
179 SoundBufferWorkerList& sbwList = pController->GetSoundBufferWorkers();
\r
180 SoundBufferWorkerList::iterator it = sbwList.begin();
\r
181 SoundBufferWorkerList::iterator end = sbwList.end();
\r
182 for(;it != end; ++it)
\r
190 bool DirectSoundManager::PushAudioData(FrameMediaController* pController, FramePtr pFrame)
\r
192 #ifndef DISABLE_AUDIO
\r
193 SoundBufferWorkerList& sbwList = pController->GetSoundBufferWorkers();
\r
194 AudioDataChunkList data = pFrame->GetAudioData();
\r
196 SoundBufferWorkerList::iterator it = sbwList.begin();
\r
197 SoundBufferWorkerList::iterator end = sbwList.end();
\r
199 for(int dataIndex = 0;it != end && dataIndex < data.size(); ++it, ++dataIndex) {
\r
200 if(dataIndex < pFrame->GetAudioData().size())
\r
201 (*it)->PushChunk(pFrame->GetAudioData()[dataIndex]);
\r
209 SoundBufferWorkerPtr DirectSoundManager::CreateSoundBufferWorker(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps) {
\r
210 SoundBufferWorkerPtr result;
\r
212 if(pDirectSound_ != 0) {
\r
214 DSBUFFERDESC bufferDesc;
\r
215 LPDIRECTSOUNDBUFFER pSoundBuffer;
\r
216 LPDIRECTSOUNDBUFFER8 pSoundBuffer8;
\r
220 ZeroMemory(&wfx, sizeof(wfx));
\r
221 wfx.cbSize = sizeof(wfx);
\r
222 wfx.wFormatTag = WAVE_FORMAT_PCM;
\r
223 wfx.nChannels = channels;
\r
224 wfx.wBitsPerSample = bits;
\r
225 wfx.nSamplesPerSec = samplesPerSec;
\r
226 wfx.nBlockAlign = (wfx.nChannels * wfx.wBitsPerSample) / 8;
\r
227 wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
\r
229 int bytesPerFrame = wfx.nAvgBytesPerSec / fps;
\r
231 ZeroMemory(&bufferDesc, sizeof(bufferDesc));
\r
232 bufferDesc.dwSize = sizeof(bufferDesc);
\r
233 bufferDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME;
\r
234 bufferDesc.dwBufferBytes = bytesPerFrame * DirectSoundBufferWorker::BufferLengthInFrames;
\r
235 bufferDesc.lpwfxFormat = &wfx;
\r
237 hr = pDirectSound_->CreateSoundBuffer(&bufferDesc, &pSoundBuffer, NULL);
\r
238 if(SUCCEEDED(hr)) {
\r
239 hr = pSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) &pSoundBuffer8);
\r
240 pSoundBuffer->Release();
\r
242 LOG << TEXT("DirectSound: Failed to create SoundBuffer.");
\r
247 LOG << TEXT("DirectSound: Failed to create SoundBuffer.");
\r
251 DirectSoundBufferWorkerPtr pSBW(new DirectSoundBufferWorker(pSoundBuffer8));
\r
252 if(FAILED(pSBW->InitSoundBuffer(channels, bits, samplesPerSec, fps))) {
\r
253 LOG << TEXT("DirectSound: Failed to init SoundBuffer.");
\r
263 HRESULT DirectSoundManager::SetPrimaryBufferFormat(DWORD dwPrimaryChannels, DWORD dwPrimaryFreq, DWORD dwPrimaryBitRate)
\r
266 LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
\r
268 if(pDirectSound_ == NULL )
\r
269 return CO_E_NOTINITIALIZED;
\r
271 // Get the primary buffer
\r
273 ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
\r
274 dsbd.dwSize = sizeof(DSBUFFERDESC);
\r
275 dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
\r
276 dsbd.dwBufferBytes = 0;
\r
277 dsbd.lpwfxFormat = NULL;
\r
279 hr = pDirectSound_->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL );
\r
284 ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
\r
285 wfx.wFormatTag = (WORD) WAVE_FORMAT_PCM;
\r
286 wfx.nChannels = (WORD) dwPrimaryChannels;
\r
287 wfx.nSamplesPerSec = (DWORD) dwPrimaryFreq;
\r
288 wfx.wBitsPerSample = (WORD) dwPrimaryBitRate;
\r
289 wfx.nBlockAlign = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels);
\r
290 wfx.nAvgBytesPerSec = (DWORD) (wfx.nSamplesPerSec * wfx.nBlockAlign);
\r
292 hr = pDSBPrimary->SetFormat(&wfx);
\r
294 pDSBPrimary->Release();
\r
299 pDSBPrimary->Release();
\r
305 /////////////////////////////////
\r
307 // DirectSoundBufferWorker Impl
\r
309 /////////////////////////////////
\r
310 const int DirectSoundBufferWorker::BufferLengthInFrames = 3;
\r
312 DirectSoundBufferWorker::DirectSoundBufferWorker(LPDIRECTSOUNDBUFFER8 pSoundBuffer) : writeEvent_(TRUE, TRUE), bytesPerFrame_(0), pSoundBuffer_(pSoundBuffer), bIsRunning_(false), startPlayback_(FALSE, FALSE), soundBufferLoadIndex_(0), lastPlayIndex_(0)
\r
314 //reserve the first event for the stopEvent
\r
315 notificationEvents_[0] = 0;
\r
316 notificationEvents_[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
\r
319 DirectSoundBufferWorker::~DirectSoundBufferWorker(void) {
\r
322 pSoundBuffer_->Release();
\r
325 CloseHandle(notificationEvents_[1]);
\r
326 notificationEvents_[1] = 0;
\r
329 void DirectSoundBufferWorker::Run(HANDLE stopEvent) {
\r
330 bool bQuit = false;
\r
331 notificationEvents_[0] = stopEvent;
\r
334 HANDLE waitEvents[2] = {stopEvent, startPlayback_};
\r
335 HRESULT waitResult = WAIT_TIMEOUT;
\r
336 while(waitResult == WAIT_TIMEOUT || waitResult == WAIT_OBJECT_0)
\r
338 waitResult = WaitForMultipleObjects(2, waitEvents, FALSE, 2500);
\r
339 if(waitResult == WAIT_OBJECT_0)
\r
340 goto workerloop_end;
\r
344 bIsRunning_ = true;
\r
345 HRESULT hr = pSoundBuffer_->Play(0, 0, DSBPLAY_LOOPING);
\r
347 DWORD waitResult = WaitForMultipleObjects(2, notificationEvents_, FALSE, 2500);
\r
348 switch(waitResult) {
\r
349 case WAIT_OBJECT_0: //stopEvent
\r
362 DWORD currentPlayCursor = 0, currentWriteCursor = 0;
\r
363 HRESULT hr = pSoundBuffer_->GetCurrentPosition(¤tPlayCursor, ¤tWriteCursor);
\r
364 if(SUCCEEDED(hr)) {
\r
365 int currentPlayCursorIndex = currentPlayCursor / bytesPerFrame_;
\r
366 if(currentPlayCursorIndex != lastPlayIndex_) {
\r
367 AudioDataChunkPtr pChunk = GetNextChunk();
\r
368 int offset = lastPlayIndex_ * bytesPerFrame_;
\r
370 WriteChunkToBuffer(offset, pChunk);
\r
372 lastPlayIndex_ = currentPlayCursorIndex;
\r
381 pSoundBuffer_->Stop();
\r
382 bIsRunning_ = false;
\r
385 bool DirectSoundBufferWorker::OnUnhandledException(const std::exception&) throw() {
\r
387 if(pSoundBuffer_ != 0)
\r
388 pSoundBuffer_->Stop();
\r
390 LOG << TEXT("UNEXPECTED EXCEPTION in SoundBufferWorker.");
\r
398 void DirectSoundBufferWorker::WriteChunkToBuffer(int offset, AudioDataChunkPtr pChunk) {
\r
402 HRESULT hr = pSoundBuffer_->Lock(offset, bytesPerFrame_, &pPtr, &len, NULL, NULL, 0);
\r
403 if(SUCCEEDED(hr)) {
\r
405 //len and pChunk-length SHOULD be the same, but better safe than sorry
\r
406 memcpy(pPtr, pChunk->GetDataPtr(), min(len, pChunk->GetLength()));
\r
409 memset(pPtr, 0, len);
\r
412 pSoundBuffer_->Unlock(pPtr, len, NULL, 0);
\r
416 bool DirectSoundBufferWorker::PushChunk(AudioDataChunkPtr pChunk) {
\r
419 //WaitForSingleObject(writeEvent_, 200);
\r
422 if(soundBufferLoadIndex_ < 3) {
\r
423 WriteChunkToBuffer(bytesPerFrame_ * soundBufferLoadIndex_, pChunk);
\r
424 ++soundBufferLoadIndex_;
\r
428 startPlayback_.Set();
\r
431 chunkQueue_.push(pChunk);
\r
433 if(chunkQueue_.size() >= 5)
\r
434 writeEvent_.Reset();
\r
439 AudioDataChunkPtr DirectSoundBufferWorker::GetNextChunk() {
\r
441 AudioDataChunkPtr pChunk;
\r
443 if(chunkQueue_.size() > 0) {
\r
444 pChunk = chunkQueue_.front();
\r
448 if(chunkQueue_.size() < 5)
\r
454 HRESULT DirectSoundBufferWorker::InitSoundBuffer(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps) {
\r
456 bytesPerFrame_ = samplesPerSec * channels * (bits/8) / fps;
\r
457 DWORD bufferSize = bytesPerFrame_ * BufferLengthInFrames;
\r
459 LPDIRECTSOUNDNOTIFY8 pBufferNotify;
\r
460 HRESULT hr = pSoundBuffer_->QueryInterface(IID_IDirectSoundNotify8, (LPVOID*) &pBufferNotify);
\r
461 if(SUCCEEDED(hr)) {
\r
462 DSBPOSITIONNOTIFY pPositionNotifies[BufferLengthInFrames];
\r
464 for(int i=0; i < BufferLengthInFrames; ++i) {
\r
465 pPositionNotifies[i].dwOffset = (i+1)*bytesPerFrame_ - 1;
\r
466 pPositionNotifies[i].hEventNotify = notificationEvents_[1];
\r
469 hr = pBufferNotify->SetNotificationPositions(BufferLengthInFrames, &(pPositionNotifies[0]));
\r
470 pBufferNotify->Release();
\r
475 //Init the buffer to silence
\r
478 hr = pSoundBuffer_->Lock(0, bufferSize, &pPtr, &len, NULL, NULL, 0);
\r
479 if(SUCCEEDED(hr) && pPtr != 0) {
\r
480 memset(pPtr, 0, len);
\r
481 pSoundBuffer_->Unlock(pPtr, len, NULL, 0);
\r
487 } //namespace directsound
\r