]> git.sesse.net Git - casparcg/blob - server/audio/DirectSoundManager.cpp
1.8.1:
[casparcg] / server / audio / DirectSoundManager.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\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
10 *\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
15 \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
18 *\r
19 */\r
20  \r
21 #include "..\StdAfx.h"\r
22 \r
23 #include "..\utils\thread.h"\r
24 #include "..\utils\lockable.h"\r
25 #include <queue>\r
26 \r
27 #include <mmsystem.h>\r
28 #include <dsound.h>\r
29 \r
30 #include "..\MediaProducerInfo.h"\r
31 #include "..\frame\FrameMediaController.h"\r
32 #include "DirectSoundManager.h"\r
33 \r
34 namespace caspar {\r
35 namespace directsound {\r
36 \r
37 using namespace audio;\r
38 using namespace utils;\r
39 \r
40 ///////////////////////////////\r
41 // DirectSoundBufferWorker\r
42 ///////////////////////////////\r
43 class DirectSoundBufferWorker : public caspar::audio::ISoundBufferWorker, private utils::IRunnable, private utils::LockableObject\r
44 {\r
45         friend class DirectSoundManager;\r
46 \r
47         DirectSoundBufferWorker(const DirectSoundBufferWorker&);\r
48         DirectSoundBufferWorker& operator=(const DirectSoundBufferWorker&);\r
49 \r
50 public:\r
51         static const int BufferLengthInFrames;\r
52 \r
53         virtual ~DirectSoundBufferWorker();\r
54 \r
55         void Start() {\r
56                 worker_.Start(this);\r
57         }\r
58         void Stop() {\r
59                 worker_.Stop();\r
60         }\r
61 \r
62         bool PushChunk(AudioDataChunkPtr);\r
63 \r
64         HANDLE GetWaitHandle() {\r
65                 return writeEvent_;\r
66         }\r
67 \r
68 private:\r
69         AudioDataChunkPtr GetNextChunk();\r
70 \r
71 private:\r
72         explicit DirectSoundBufferWorker(LPDIRECTSOUNDBUFFER8 pDirectSound);\r
73         HRESULT InitSoundBuffer(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps);\r
74 \r
75         virtual void Run(HANDLE stopEvent);\r
76         virtual bool OnUnhandledException(const std::exception&) throw();\r
77         utils::Thread worker_;\r
78 \r
79 \r
80         void WriteChunkToBuffer(int offset, AudioDataChunkPtr pChunk);\r
81         int IncreaseFrameIndex(int frameIndex) {\r
82                 return (frameIndex+1)%BufferLengthInFrames;\r
83         }\r
84 \r
85         HANDLE notificationEvents_[2];\r
86 \r
87         int bytesPerFrame_;\r
88         bool bIsRunning_;\r
89 \r
90         LPDIRECTSOUNDBUFFER8 pSoundBuffer_;\r
91 \r
92         std::queue<AudioDataChunkPtr> chunkQueue_;\r
93         utils::Event writeEvent_;\r
94         utils::Event startPlayback_;\r
95 \r
96         int soundBufferLoadIndex_;\r
97         int lastPlayIndex_;\r
98 };\r
99 typedef std::tr1::shared_ptr<DirectSoundBufferWorker> DirectSoundBufferWorkerPtr;\r
100 \r
101 \r
102 ///////////////////////////////\r
103 //\r
104 // DirectSoundManager\r
105 //\r
106 ///////////////////////////////\r
107 DirectSoundManager::DirectSoundManager() : pDirectSound_(0)\r
108 {\r
109 }\r
110 \r
111 DirectSoundManager::~DirectSoundManager()\r
112 {\r
113         Destroy();\r
114 }\r
115 \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
120         if(FAILED(hr)) {\r
121                 LOG << TEXT("DirectSound: Failed to create device.");\r
122                 return false;\r
123         }\r
124 \r
125         hr = pDirectSound_->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);\r
126         if(FAILED(hr)) {\r
127                 LOG << TEXT("DirectSound: Failed to set CooperativeLevel.");\r
128                 return false;\r
129         }\r
130 \r
131         hr = SetPrimaryBufferFormat(channels, samplesPerSec, bitsPerSample);\r
132         if(FAILED(hr)) {\r
133                 LOG << TEXT("DirectSound: Failed to set Primarybuffer format.");\r
134                 return false;\r
135         }\r
136 #endif\r
137         return true;\r
138 }\r
139 \r
140 void DirectSoundManager::Destroy() {\r
141         if(pDirectSound_ != 0) {\r
142                 pDirectSound_->Release();\r
143                 pDirectSound_ = 0;\r
144         }\r
145 }\r
146 \r
147 bool DirectSoundManager::CueAudio(FrameMediaController* pController) \r
148 {\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
153                 if(pSBW) {\r
154                         pController->AddSoundBufferWorker(pSBW);\r
155                         return true;\r
156                 }\r
157         }\r
158 #endif\r
159         return false;\r
160 }\r
161 \r
162 bool DirectSoundManager::StartAudio(FrameMediaController* pController)\r
163 {\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
169         {\r
170                 (*it)->Start();\r
171         }\r
172 #endif\r
173         return true;\r
174 }\r
175 \r
176 bool DirectSoundManager::StopAudio(FrameMediaController* pController)\r
177 {\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
183         {\r
184                 (*it)->Stop();\r
185         }\r
186 #endif\r
187         return true;\r
188 }\r
189 \r
190 bool DirectSoundManager::PushAudioData(FrameMediaController* pController, FramePtr pFrame)\r
191 {\r
192 #ifndef DISABLE_AUDIO\r
193         SoundBufferWorkerList& sbwList = pController->GetSoundBufferWorkers();\r
194         AudioDataChunkList data = pFrame->GetAudioData();\r
195 \r
196         SoundBufferWorkerList::iterator it = sbwList.begin();\r
197         SoundBufferWorkerList::iterator end = sbwList.end();\r
198 \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
202                 else\r
203                         break;\r
204         }\r
205 #endif\r
206         return true;\r
207 }\r
208 \r
209 SoundBufferWorkerPtr DirectSoundManager::CreateSoundBufferWorker(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps) {\r
210         SoundBufferWorkerPtr result;\r
211 \r
212         if(pDirectSound_ != 0) {\r
213                 WAVEFORMATEX wfx;\r
214                 DSBUFFERDESC bufferDesc;\r
215                 LPDIRECTSOUNDBUFFER pSoundBuffer;\r
216                 LPDIRECTSOUNDBUFFER8 pSoundBuffer8;\r
217 \r
218                 HRESULT hr;\r
219 \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
228 \r
229                 int bytesPerFrame = wfx.nAvgBytesPerSec / fps;\r
230 \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
236 \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
241                         if(FAILED(hr)) {\r
242                                 LOG << TEXT("DirectSound: Failed to create SoundBuffer.");\r
243                                 return result;\r
244                         }\r
245                 }\r
246                 else {\r
247                         LOG << TEXT("DirectSound: Failed to create SoundBuffer.");\r
248                         return result;\r
249                 }\r
250 \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
254                         return result;\r
255                 }\r
256                 pSBW->Start();\r
257                 result = pSBW;\r
258         }\r
259 \r
260         return result;\r
261 }\r
262 \r
263 HRESULT DirectSoundManager::SetPrimaryBufferFormat(DWORD dwPrimaryChannels, DWORD dwPrimaryFreq,  DWORD dwPrimaryBitRate)\r
264 {\r
265         HRESULT hr;\r
266         LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;\r
267 \r
268         if(pDirectSound_ == NULL )\r
269                 return CO_E_NOTINITIALIZED;\r
270 \r
271         // Get the primary buffer \r
272         DSBUFFERDESC dsbd;\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
278 \r
279         hr = pDirectSound_->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL );\r
280         if(FAILED(hr))\r
281                 return hr;\r
282 \r
283         WAVEFORMATEX wfx;\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
291 \r
292         hr = pDSBPrimary->SetFormat(&wfx);\r
293         if(FAILED(hr)) {\r
294                 pDSBPrimary->Release();\r
295                 pDSBPrimary = 0;\r
296                 return hr;\r
297         }\r
298 \r
299         pDSBPrimary->Release();\r
300         pDSBPrimary = 0;\r
301 \r
302         return S_OK;\r
303 }\r
304 \r
305 /////////////////////////////////\r
306 //\r
307 // DirectSoundBufferWorker Impl\r
308 //\r
309 /////////////////////////////////\r
310 const int DirectSoundBufferWorker::BufferLengthInFrames = 3;\r
311 \r
312 DirectSoundBufferWorker::DirectSoundBufferWorker(LPDIRECTSOUNDBUFFER8 pSoundBuffer) : writeEvent_(TRUE, TRUE), bytesPerFrame_(0), pSoundBuffer_(pSoundBuffer), bIsRunning_(false), startPlayback_(FALSE, FALSE), soundBufferLoadIndex_(0), lastPlayIndex_(0)\r
313 {\r
314         //reserve the first event for the stopEvent\r
315         notificationEvents_[0] = 0;\r
316         notificationEvents_[1] = CreateEvent(NULL, FALSE, FALSE, NULL);\r
317 }\r
318 \r
319 DirectSoundBufferWorker::~DirectSoundBufferWorker(void) {\r
320         Stop();\r
321 \r
322         pSoundBuffer_->Release();\r
323         pSoundBuffer_ = 0;\r
324 \r
325         CloseHandle(notificationEvents_[1]);\r
326         notificationEvents_[1] = 0;\r
327 }\r
328 \r
329 void DirectSoundBufferWorker::Run(HANDLE stopEvent) {\r
330         bool bQuit = false;\r
331         notificationEvents_[0] = stopEvent;\r
332 \r
333         {\r
334                 HANDLE waitEvents[2] = {stopEvent, startPlayback_};\r
335                 HRESULT waitResult = WAIT_TIMEOUT;\r
336                 while(waitResult == WAIT_TIMEOUT || waitResult == WAIT_OBJECT_0)\r
337                 {\r
338                         waitResult = WaitForMultipleObjects(2, waitEvents, FALSE, 2500);\r
339                         if(waitResult == WAIT_OBJECT_0)\r
340                                 goto workerloop_end;\r
341                 }\r
342         }\r
343 \r
344         bIsRunning_ = true;\r
345         HRESULT hr = pSoundBuffer_->Play(0, 0, DSBPLAY_LOOPING);\r
346         while(!bQuit) {\r
347                 DWORD waitResult = WaitForMultipleObjects(2, notificationEvents_, FALSE, 2500);\r
348                 switch(waitResult) {\r
349                         case WAIT_OBJECT_0:     //stopEvent\r
350                                 bQuit = true;\r
351                                 break;\r
352 \r
353                         case WAIT_TIMEOUT:\r
354                                 break;\r
355 \r
356                         case WAIT_FAILED:\r
357                                 bQuit = true;\r
358                                 break;\r
359 \r
360                         default:\r
361                                 {\r
362                                         DWORD currentPlayCursor = 0, currentWriteCursor = 0;\r
363                                         HRESULT hr = pSoundBuffer_->GetCurrentPosition(&currentPlayCursor, &currentWriteCursor);\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
369 \r
370                                                         WriteChunkToBuffer(offset, pChunk);\r
371 \r
372                                                         lastPlayIndex_ = currentPlayCursorIndex;\r
373                                                 }\r
374                                         }\r
375                                 }\r
376                                 break;\r
377                 }\r
378         }\r
379 \r
380 workerloop_end:\r
381         pSoundBuffer_->Stop();\r
382         bIsRunning_ = false;\r
383 }\r
384 \r
385 bool DirectSoundBufferWorker::OnUnhandledException(const std::exception&) throw() {\r
386         try {\r
387                 if(pSoundBuffer_ != 0)\r
388                         pSoundBuffer_->Stop();\r
389 \r
390                 LOG << TEXT("UNEXPECTED EXCEPTION in SoundBufferWorker.");\r
391         }\r
392         catch(...) \r
393         {}\r
394 \r
395         return false;\r
396 }\r
397 \r
398 void DirectSoundBufferWorker::WriteChunkToBuffer(int offset, AudioDataChunkPtr pChunk) {\r
399         void* pPtr;\r
400         DWORD len;\r
401 \r
402         HRESULT hr = pSoundBuffer_->Lock(offset, bytesPerFrame_, &pPtr, &len, NULL, NULL, 0); \r
403         if(SUCCEEDED(hr)) {\r
404                 if(pChunk != 0) {\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
407                 }\r
408                 else {\r
409                         memset(pPtr, 0, len);\r
410                 }\r
411 \r
412                 pSoundBuffer_->Unlock(pPtr, len, NULL, 0);\r
413         }\r
414 }\r
415 \r
416 bool DirectSoundBufferWorker::PushChunk(AudioDataChunkPtr pChunk) {\r
417         Lock lock(*this);\r
418 \r
419         //WaitForSingleObject(writeEvent_, 200);\r
420 \r
421         if(!bIsRunning_) {\r
422                 if(soundBufferLoadIndex_ < 3) {\r
423                         WriteChunkToBuffer(bytesPerFrame_ * soundBufferLoadIndex_, pChunk);\r
424                         ++soundBufferLoadIndex_;\r
425                         return true;\r
426                 }\r
427                 else\r
428                         startPlayback_.Set();\r
429         }\r
430 \r
431         chunkQueue_.push(pChunk);\r
432 \r
433         if(chunkQueue_.size() >= 5)\r
434                 writeEvent_.Reset();\r
435 \r
436         return true;\r
437 }\r
438 \r
439 AudioDataChunkPtr DirectSoundBufferWorker::GetNextChunk() {\r
440         Lock lock(*this);\r
441         AudioDataChunkPtr pChunk;\r
442 \r
443         if(chunkQueue_.size() > 0) {\r
444                 pChunk = chunkQueue_.front();\r
445                 chunkQueue_.pop();\r
446         }\r
447 \r
448         if(chunkQueue_.size() < 5)\r
449                 writeEvent_.Set();\r
450 \r
451         return pChunk;\r
452 }\r
453 \r
454 HRESULT DirectSoundBufferWorker::InitSoundBuffer(WORD channels, WORD bits, DWORD samplesPerSec, DWORD fps) {\r
455 \r
456         bytesPerFrame_ = samplesPerSec * channels * (bits/8) / fps;\r
457         DWORD bufferSize = bytesPerFrame_ * BufferLengthInFrames;\r
458         \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
463 \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
467                 }\r
468 \r
469                 hr = pBufferNotify->SetNotificationPositions(BufferLengthInFrames, &(pPositionNotifies[0]));\r
470                 pBufferNotify->Release();\r
471         }\r
472         else \r
473                 return hr;\r
474 \r
475         //Init the buffer to silence\r
476         void* pPtr = 0;\r
477         DWORD len = 0;\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
482         }\r
483 \r
484         return hr;\r
485 }\r
486 \r
487 }       //namespace directsound\r
488 }       //namespace caspar