1 /*
2  * sound_directsound.cxx
3  *
4  * DirectX Sound driver implementation.
5  *
6  * Portable Windows Library
7  *
8  * Copyright (c) 2006-2007 Novacom, a division of IT-Optics
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Portable Windows Library.
21  *
22  * The Initial Developer of the Original DirectSound Code is
23  * Vincent Luba <vincent.luba@novacom.be>
24  *
25  * Contributor(s): Ted Szoczei, Nimajin Software Consulting
26  *
27  * $Revision: 28575 $
28  * $Author: rjongbloed $
29  * $Date: 2012-11-25 21:59:04 -0600 (Sun, 25 Nov 2012) $
30  */
31 
32 #pragma implementation "sound_directsound.h"
33 
34 #include <ptlib.h>
35 
36 #if P_DIRECTSOUND
37 
38 #include <ptlib/pprocess.h>
39 #include <algorithm>
40 
41 #define INITGUID
42 #include <ptlib/msos/ptlib/sound_directsound.h>
43 #include <ptlib/msos/ptlib/sound_win32.h>
44 
45 #include <tchar.h>
46 #include <math.h>
47 
48 #include <ks.h>
49 #include <dsconf.h>
50 typedef HRESULT (STDAPICALLTYPE *LPFNDLLGETCLASSOBJECT )(REFCLSID, REFIID, LPVOID * );
51 
52 // It takes a lot of fiddling in configure to find dxerr.h
53 // but don't worry about it, it's not essential, see GetErrorText
54 #ifdef P_DIRECTSOUND_DXERR_H
55 #include <dxerr.h>    // for DirectSound DXGetErrorDescription9
56 #pragma comment(lib, "dxerr.lib")
57 #endif
58 
59 #ifdef _MSC_VER
60   #pragma comment(lib, "dsound.lib")
61   #pragma message("Direct Sound support enabled")
62 #endif
63 
64 // I made up these HRESULT facility codes to simplify MME error reporting in GetErrorText
65 #define FACILITY_WAVEIN  100
66 #define FACILITY_WAVEOUT 101
67 
68 /* Instantiate the PWLIBsound plugin */
69 PCREATE_SOUND_PLUGIN(DirectSound, PSoundChannelDirectSound)
70 
71 
72 #ifdef _WIN32_WCE
73 #include <initguid.h>
74 #define IID_IDirectSoundBuffer8 IID_IDirectSoundBuffer
75 #define IID_IDirectSoundCaptureBuffer8 IID_IDirectSoundCaptureBuffer
76 DEFINE_GUID(DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
77 DEFINE_GUID(DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
78 DEFINE_GUID(DSDEVID_DefaultVoicePlayback, 0xdef00002, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
79 DEFINE_GUID(DSDEVID_DefaultVoiceCapture, 0xdef00003, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
80 #endif
81 
82 
83 ///////////////////////////////////////////////////////////////////////////////
84 // Enumeration of devices
85 
86 // Establish access to system-wide DirectSound device properties
87 
DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * outKsPropertySet)88 static HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * outKsPropertySet)
89 {
90   HMODULE dSoundModule = LoadLibrary(TEXT("dsound.dll"));
91   if (!dSoundModule)
92     return DSERR_GENERIC;
93 
94   LPFNDLLGETCLASSOBJECT getClassObjectFn = (LPFNDLLGETCLASSOBJECT)GetProcAddress(dSoundModule, "DllGetClassObject");
95   if (!getClassObjectFn) {
96     FreeLibrary(dSoundModule);
97     return DSERR_GENERIC;
98   }
99   // Create a class factory object
100   LPCLASSFACTORY classFactory = NULL;
101   HRESULT result = getClassObjectFn(CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&classFactory);
102 
103   // Create the DirectSoundPrivate object and query for an IKsPropertySet interface
104   LPKSPROPERTYSET ksPropertySet = NULL;
105   if (SUCCEEDED(result))
106     result = classFactory->CreateInstance(NULL, IID_IKsPropertySet, (LPVOID *)&ksPropertySet);
107 
108   if (classFactory)
109     classFactory->Release();
110 
111   if (SUCCEEDED(result))
112     *outKsPropertySet = ksPropertySet;
113 
114   else if (ksPropertySet)
115     ksPropertySet->Release();
116 
117   FreeLibrary(dSoundModule);
118   return result;
119 }
120 
121 
122 // Enumerate all DSound devices, performing callback provided with context for each device
123 
EnumerateDSoundDeviceInfo(LPFNDIRECTSOUNDDEVICEENUMERATECALLBACKA callback,LPVOID context)124 static HRESULT EnumerateDSoundDeviceInfo(LPFNDIRECTSOUNDDEVICEENUMERATECALLBACKA callback, LPVOID context)
125 {
126   LPKSPROPERTYSET ksPropertySet = NULL;
127   HRESULT result = DirectSoundPrivateCreate(&ksPropertySet);
128   if (FAILED(result))
129     return result;
130 
131   DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_A_DATA enumerateData;
132   enumerateData.Callback = callback;
133   enumerateData.Context = context;
134   result = ksPropertySet->Get(DSPROPSETID_DirectSoundDevice,
135     DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_A, // DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION doesn't return wave ID!
136     NULL,
137     0,
138     &enumerateData,
139     sizeof(enumerateData),
140     NULL);
141   ksPropertySet->Release();
142   return result;
143 }
144 
145 
146 // Structures for working with DirectSound device properties
147 
148 struct PDSoundDeviceInfo {
149   GUID m_DeviceId;                        // directSound id of device we want wave id for
150   PSoundChannelDirectSound::Directions m_Direction; // direction of the device
151   PString m_Description;        // device name
152   int m_WaveDeviceId;                     // matching multimedia system device id
153 };
154 
155 typedef vector<PDSoundDeviceInfo> PDSoundDeviceInfoVector;
156 
157 struct PDSoundDeviceFilterInfo {
158   PDSoundDeviceInfo m_filter;
159   PDSoundDeviceInfoVector * m_result;
160 };
161 
162 
163 // DIRECTSOUNDDEVICE_ENUMERATE callback adds new DSoundDeviceInfo to results list
164 
DeviceEnumCallBackFilter(PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_A_DATA dsDescription,LPVOID context)165 static BOOL CALLBACK DeviceEnumCallBackFilter(PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_A_DATA dsDescription, LPVOID context)
166 {
167   PDSoundDeviceFilterInfo * filterInfo = (PDSoundDeviceFilterInfo *)context;
168   if (filterInfo->m_result == NULL)
169     return TRUE;
170 
171   if (!IsEqualGUID(GUID_NULL, filterInfo->m_filter.m_DeviceId) && dsDescription->DeviceId != filterInfo->m_filter.m_DeviceId)
172     return TRUE;  // reject unmatching DeviceId
173 
174   PSoundChannelDirectSound::Directions direction = (dsDescription->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER)? PSoundChannelDirectSound::Player
175                                                                                                              : PSoundChannelDirectSound::Recorder;
176   if (filterInfo->m_filter.m_Direction != PSoundChannelDirectSound::Closed && direction != filterInfo->m_filter.m_Direction)
177     return TRUE;  // reject unmatching Direction
178 
179   if (!filterInfo->m_filter.m_Description.IsEmpty() && filterInfo->m_filter.m_Description != dsDescription->Description)
180     return TRUE;  // reject unmatching Description
181 
182   if (filterInfo->m_filter.m_WaveDeviceId >= 0 && dsDescription->WaveDeviceId != (ULONG)filterInfo->m_filter.m_WaveDeviceId)
183     return TRUE;  // reject unmatching WaveDeviceId
184 
185   PDSoundDeviceInfo info;
186   info.m_DeviceId = dsDescription->DeviceId;
187   info.m_Direction = direction;
188   info.m_Description = dsDescription->Description;
189   info.m_WaveDeviceId = dsDescription->WaveDeviceId;
190   filterInfo->m_result->push_back(info);
191   return TRUE;
192 }
193 
194 
195 // Populate a DSoundDeviceInfo vector with devices that satisfy filter criteria
196 // Specify GUID_NULL to accept any DeviceId
197 // Specify Closed to accept any direction
198 // Specify empty name to accept any device name
199 // Specify -1 to accept any WaveDeviceId
200 
GetFilteredDSoundDeviceInfo(const GUID & deviceId,PSoundChannelDirectSound::Directions direction,PString name,int waveDeviceId,PDSoundDeviceInfoVector & info)201 static HRESULT GetFilteredDSoundDeviceInfo(const GUID & deviceId, PSoundChannelDirectSound::Directions direction, PString name, int waveDeviceId, PDSoundDeviceInfoVector & info)
202 {
203   PDSoundDeviceFilterInfo filterInfo;
204   filterInfo.m_filter.m_DeviceId = deviceId;
205   filterInfo.m_filter.m_Direction = direction;
206   filterInfo.m_filter.m_Description = name;
207   filterInfo.m_filter.m_WaveDeviceId = waveDeviceId;
208   filterInfo.m_result = &info;
209   return EnumerateDSoundDeviceInfo(DeviceEnumCallBackFilter, &filterInfo);
210 }
211 
212 
213 // Get the DSoundDeviceInfo for the default device (communications or audio) in the given direction
214 
GetDefaultDeviceGUID(const PCaselessString & name,PSoundChannelDirectSound::Directions direction,GUID & guid)215 static HRESULT GetDefaultDeviceGUID(const PCaselessString & name, PSoundChannelDirectSound::Directions direction, GUID & guid) // static
216 {
217   PAssert(direction == PSoundChannelDirectSound::Player || direction == PSoundChannelDirectSound::Recorder, "Invalid device direction parameter");
218 
219   LPCGUID defaultGUID = NULL;
220   if (name == "Default communications" && PProcess::IsOSVersion(6, 1)) // Windows 7
221     defaultGUID = (direction == PSoundChannelDirectSound::Player)? &DSDEVID_DefaultVoicePlayback  : &DSDEVID_DefaultVoiceCapture;
222   else {
223     defaultGUID = (direction == PSoundChannelDirectSound::Player)? &DSDEVID_DefaultPlayback  : &DSDEVID_DefaultCapture;
224     PTRACE_IF(4, name != "Default audio" && name != "Default",
225               "dsound\tOpen " << PSoundChannelDirectSound::GetDirectionText(direction)
226               << ": device \"" << name << "\" not found, substituting default");
227   }
228   return GetDeviceID(defaultGUID, &guid);
229 }
230 
231 
232 ///////////////////////////////////////////////////////////////////////////////
233 // Construction
234 
PSoundChannelDirectSound()235 PSoundChannelDirectSound::PSoundChannelDirectSound ()
236 {
237   Construct();
238 }
239 
240 
PSoundChannelDirectSound(const PString & device,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)241 PSoundChannelDirectSound::PSoundChannelDirectSound (const PString &device,
242                                                     Directions dir,
243                                                     unsigned numChannels,
244                                                     unsigned sampleRate,
245                                                     unsigned bitsPerSample)
246 {
247   Construct();
248   Open(device, dir, numChannels, sampleRate, bitsPerSample);
249 }
250 
251 
Construct()252 void PSoundChannelDirectSound::Construct () // private
253 {
254   m_captureDevice = NULL;
255   m_captureBuffer = NULL;
256 
257   m_playbackDevice = NULL;
258   m_playbackBuffer = NULL;
259 
260   m_isStreaming = true;
261   m_bufferSize = 0;
262   m_mixer = NULL;
263 
264   memset(&m_waveFormat, 0, sizeof(m_waveFormat));
265 
266   m_triggerEvent[SOUNDEVENT_SOUND] = CreateEvent(NULL, false, false, NULL);// auto-reset
267   m_triggerEvent[SOUNDEVENT_ABORT] = CreateEvent(NULL, false, false, NULL);// auto-reset
268 
269   SetBuffers(4000, 4); // 1 second at 8kHz
270 }
271 
272 
~PSoundChannelDirectSound()273 PSoundChannelDirectSound::~PSoundChannelDirectSound ()
274 {
275   Close();
276   if (m_triggerEvent[SOUNDEVENT_SOUND] != NULL)
277     CloseHandle(m_triggerEvent[SOUNDEVENT_SOUND]);
278 
279   if (m_triggerEvent[SOUNDEVENT_ABORT] != NULL)
280     CloseHandle(m_triggerEvent[SOUNDEVENT_ABORT]);
281 
282   PTRACE(4, "dsound\t" << GetDirectionText() << " destroyed");
283 }
284 
285 
286 ///////////////////////////////////////////////////////////////////////////////
287 // Open
288 
289 
GetDefaultDevice(Directions dir)290 PString PSoundChannelDirectSound::GetDefaultDevice (Directions dir) // static
291 {
292   PAssert(dir == Player || dir == Recorder, "Invalid device direction parameter");
293 
294   GUID deviceGUID;
295   HRESULT result = GetDeviceID((dir == Player)? &DSDEVID_DefaultPlayback  : &DSDEVID_DefaultCapture, &deviceGUID);
296   if (result != S_OK) {
297     PTRACE(4, "dsound\tCould not find default device: " << GetErrorText(Miscellaneous, result));
298     return PString();
299   }
300   PDSoundDeviceInfoVector devices;
301   result = GetFilteredDSoundDeviceInfo(deviceGUID, dir, PString::Empty(), -1, devices);
302   if (result != S_OK) {
303     PTRACE(4, "dsound\tOpen: Could not retrieve device information: " << GetErrorText(Miscellaneous, result));
304     return PString();
305   }
306   if (devices.size() == 0) {
307     PTRACE(4, "dsound\tOpen: Default device not found");
308     return PString();
309   }
310   return devices[0].m_Description;
311 }
312 
313 
GetDeviceNames(Directions dir)314 PStringArray PSoundChannelDirectSound::GetDeviceNames (Directions dir) // static
315 {
316   PAssert(dir == Player || dir == Recorder, "Invalid device direction parameter");
317 
318   PDSoundDeviceInfoVector devices;
319   HRESULT result = GetFilteredDSoundDeviceInfo(GUID_NULL, dir, PString::Empty(), -1, devices);
320   if (result != S_OK) {
321     PTRACE(4, "dsound\tCould not get device list: " << GetErrorText(Miscellaneous, result));
322     return PString();
323   }
324   PStringArray names;
325   if (devices.size() > 0) {
326     if (PProcess::IsOSVersion(6, 1)) { // Windows 7
327       names.AppendString("Default audio");
328       names.AppendString("Default communications");
329     }
330     else
331       names.AppendString("Default");
332   }
333   PDSoundDeviceInfoVector::const_iterator iterator = devices.begin();
334   while (iterator != devices.end()) {
335     names.AppendString(iterator->m_Description);
336     iterator++;
337   }
338   return names;
339 }
340 
341 
Open(const PString & device,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)342 PBoolean PSoundChannelDirectSound::Open (const PString & device, // public
343                                          Directions dir,
344                                          unsigned numChannels,
345                                          unsigned sampleRate,
346                                          unsigned bitsPerSample)
347 {
348   PAssert(dir == Player || dir == Recorder, "Invalid device direction parameter");
349 
350   Close();
351 
352   // remove the driver name prefix
353   PINDEX tab = device.Find(PDevicePluginServiceDescriptor::SeparatorChar);
354   m_deviceName = (tab == P_MAX_INDEX)? device : device.Mid(tab+1).Trim();
355 
356   activeDirection = dir;
357   m_available = 0;
358   m_dsMoved = 0ui64;
359   m_moved = 0ui64;
360   m_lost = 0ui64;
361 
362   // get info for all devices for direction
363   PDSoundDeviceInfoVector devices;
364   HRESULT result = GetFilteredDSoundDeviceInfo(GUID_NULL, dir, PString::Empty(), -1, devices);
365   if (result != S_OK) {
366     SetErrorValues(Miscellaneous, result);
367     PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Could not get device list: " << GetErrorText());
368     return false;
369   }
370   // validate the device name, use default if bad
371   PDSoundDeviceInfoVector::iterator deviceInfo;
372   for (deviceInfo = devices.begin(); deviceInfo != devices.end(); ++deviceInfo) {
373     if (deviceInfo->m_Description == m_deviceName)
374       break;
375   }
376   if (deviceInfo == devices.end()) {
377     GUID deviceGUID;
378     result = GetDefaultDeviceGUID(m_deviceName, activeDirection, deviceGUID);
379     if (result != S_OK) {
380       SetErrorValues(Miscellaneous, result);
381       PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Could not get default device ID: " << GetErrorText());
382       return false;
383     }
384     for (deviceInfo = devices.begin(); deviceInfo != devices.end(); ++deviceInfo) {
385       if (deviceInfo->m_DeviceId == deviceGUID)
386         break;
387     }
388     if (deviceInfo == devices.end()) {
389       PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Could not find default device");
390       return SetErrorValues(NotFound, HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
391     }
392     m_deviceName = deviceInfo->m_Description;
393   }
394   PTRACE(4, "dsound\tOpening " << GetDirectionText() << " device \"" << m_deviceName << '"');
395 
396   SetFormat(numChannels, sampleRate, bitsPerSample);
397 
398   // open for playback
399   if (activeDirection == Player) {
400     if (!OpenPlayback(&deviceInfo->m_DeviceId))
401       return false;
402   }
403   else { // open for recording
404     if (!OpenCapture(&deviceInfo->m_DeviceId))
405       return false;
406   }
407   OpenMixer(deviceInfo->m_WaveDeviceId);
408 
409   if (m_notifier.GetObject() != NULL) { // notify that channel is starting
410     m_notifier(*this, SOUNDNOTIFY_UNDERRUN);
411     m_notifier(*this, SOUNDNOTIFY_OVERRUN);
412   }
413   return true;
414 }
415 
416 // This stops play or record in progress (breaks Wait loop)
417 
Abort()418 PBoolean PSoundChannelDirectSound::Abort () // public
419 {
420   if (IsOpen())
421     SetEvent(m_triggerEvent[SOUNDEVENT_ABORT]); // signal read or write to stop
422   else {
423     // Reset these even when opening
424     m_movePos = 0; // reset public read/write position
425     m_dsPos = 0; // DirectSound read/write position
426     m_tick.SetInterval(0i64);
427   }
428   return true;
429 }
430 
431 
Close()432 PBoolean PSoundChannelDirectSound::Close () // public
433 {
434   PWaitAndSignal mutex(m_bufferMutex);  // wait for read/write completion
435   Abort(); // abort waiting for I/O
436 
437   switch (activeDirection) {
438   case Player:
439     PTRACE(4, "dsound\tClosing Playback device \"" << m_deviceName << '"');
440     ClosePlayback();
441     PTRACE(4, "dsound\tPlayback closed: wrote " << GetSamplesMoved() << ", played " << GetSamplesBuffered() << ", lost " <<  GetSamplesLost() << " samples");
442     break;
443 
444   case Recorder:
445     PTRACE(4, "dsound\tClosing Recording device \"" << m_deviceName << '"');
446     CloseCapture();
447     PTRACE(4, "dsound\tRecording closed: captured " << GetSamplesBuffered() << ", read " << GetSamplesMoved() << ", lost " <<  GetSamplesLost() << " samples");
448     break;
449   }
450   CloseMixer();
451   activeDirection = Closed;
452   return true;
453 }
454 
455 
456 ///////////////////////////////////////////////////////////////////////////////
457 // Setup
458 // It should be OK to do these even while reading/writing
459 
460 
SetWaveFormat(WAVEFORMATEX & format,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)461 void SetWaveFormat (WAVEFORMATEX & format,
462                     unsigned numChannels,
463                     unsigned sampleRate,
464                     unsigned bitsPerSample)
465 {
466   memset(&format, 0, sizeof(WAVEFORMATEX));
467   format.wFormatTag = WAVE_FORMAT_PCM;
468   format.nChannels = (WORD)numChannels;
469   format.nSamplesPerSec = sampleRate;
470   format.wBitsPerSample = (WORD)bitsPerSample;
471   format.nBlockAlign = format.nChannels * ((format.wBitsPerSample + 7) / 8);
472   format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
473 }
474 
475 
SetFormat(unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)476 PBoolean PSoundChannelDirectSound::SetFormat (unsigned numChannels, // public
477                                               unsigned sampleRate,
478                                               unsigned bitsPerSample)
479 {
480   if (IsOpen()) {
481     PWaitAndSignal mutex(m_bufferMutex); // don't do this while CheckxBuffer is running!
482     if (m_waveFormat.nChannels == (WORD)numChannels && m_waveFormat.nSamplesPerSec == sampleRate && m_waveFormat.wBitsPerSample == (WORD)bitsPerSample)
483       return true;
484 
485     Abort(); // abort waiting for I/O
486     SetWaveFormat(m_waveFormat, numChannels, sampleRate, bitsPerSample);
487     if (activeDirection == Player) {
488       if (!OpenPlaybackBuffer()) // if this fails, channel is closed
489         return false;
490     }
491     else if (activeDirection == Recorder) {
492       if (!OpenCaptureBuffer()) // if this fails, channel is closed
493         return false;
494 	}
495   }
496   else // Closed, no buffers yet
497     SetWaveFormat(m_waveFormat, numChannels, sampleRate, bitsPerSample);
498 
499   PTRACE(4, "dsound\t" << GetDirectionText() << " SetFormat:"
500     << " Channels " << m_waveFormat.nChannels
501     << " Rate " << m_waveFormat.nSamplesPerSec
502     << " Bits " << m_waveFormat.wBitsPerSample);
503   return true;
504 }
505 
506 
SetBufferSections(PINDEX size,PINDEX count)507 PBoolean PSoundChannelDirectSound::SetBufferSections (PINDEX size, PINDEX count)
508 {
509   m_bufferSectionCount = count;
510   m_bufferSectionSize = size;
511   m_bufferSize = m_bufferSectionCount * m_bufferSectionSize;
512   return true;
513 }
514 
515 
SetBuffers(PINDEX size,PINDEX count)516 PBoolean PSoundChannelDirectSound::SetBuffers (PINDEX size, PINDEX count) // public
517 {
518   if (size < DSBSIZE_MIN || size > DSBSIZE_MAX) {
519     PTRACE(4, "dsound\t" << GetDirectionText() << " SetBuffers: invalid buffer size " << size << " bytes");
520     return SetErrorValues(BadParameter, E_INVALIDARG);
521   }
522   if (IsOpen()) {
523     PWaitAndSignal mutex(m_bufferMutex); // don't do this while CheckxBuffer is running!
524     if (m_bufferSectionCount == count && m_bufferSectionSize == size)
525       return true;
526 
527     Abort(); // abort waiting for I/O
528     SetBufferSections(size, count);
529     if (activeDirection == Player) {
530       if (!OpenPlaybackBuffer())
531         return false;
532     }
533     else if (activeDirection == Recorder) {
534       if (!OpenCaptureBuffer()) // if this fails, channel is closed
535         return false;
536 	}
537   }
538   else
539     SetBufferSections(size, count);
540 
541 #if PTRACING
542   if (PTrace::CanTrace(4)) {
543     ostream & stream = PTrace::Begin(4, __FILE__, __LINE__);
544     stream << "dsound\t" << GetDirectionText() << " SetBuffers: count " << m_bufferSectionCount << " x size " << m_bufferSectionSize << " = " << m_bufferSize << " bytes";
545     if (m_waveFormat.nAvgBytesPerSec)
546       stream << " = " << m_bufferSize * 1000 / m_waveFormat.nAvgBytesPerSec << " ms";
547     stream << PTrace::End;
548   }
549 #endif
550 
551   return true;
552 }
553 
554 
GetBuffers(PINDEX & size,PINDEX & count)555 PBoolean PSoundChannelDirectSound::GetBuffers (PINDEX & size, PINDEX & count) // public
556 {
557   count = m_bufferSectionCount;
558   size = m_bufferSectionSize;
559   return true;
560 }
561 
562 
563 ///////////////////////////////////////////////////////////////////////////////
564 // Error reporting
565 
566 
GetErrorText(Errors lastError,int osError)567 PString PSoundChannelDirectSound::GetErrorText (Errors lastError, int osError) // static
568 {
569   PString text;
570   DWORD facility = HRESULT_FACILITY((HRESULT)osError);
571 #ifdef P_DIRECTSOUND_DXERR_H
572   if (facility == _FACDS) { // DirectX errors not available in FormatMessage
573     return text = DXGetErrorDescription(osError);
574   }
575   else if (facility <= 84) { // Standard Windows errors (WinError.h)
576 #else
577   if (facility == _FACDS || facility <= 84) { // handle DSound and standard Windows errors (WinError.h)
578 #endif
579     if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, osError, 0, text.GetPointer(256), 255, 0) == 0) {
580       // FormatMessage error or unknown HRESULT
581       return psprintf("HRESULT 0x%08X", osError);
582     }
583   }
584   else if (facility == FACILITY_WAVEIN) {
585     MMRESULT mmError = HRESULT_CODE(osError);
586     if (waveInGetErrorText(mmError, text.GetPointer(256), 256) != MMSYSERR_NOERROR)
587       return PChannel::GetErrorText(lastError, mmError);
588   }
589   else if (facility == FACILITY_WAVEOUT) {
590     MMRESULT mmError = HRESULT_CODE(osError);
591     if (waveOutGetErrorText(mmError, text.GetPointer(256), 256) != MMSYSERR_NOERROR)
592       return PChannel::GetErrorText(lastError, mmError);
593   }
594   else
595     return PChannel::GetErrorText(lastError, osError);
596 
597   // strip trailing CR/LF
598   int Length = text.GetLength();
599   int Index = Length;
600   while (Index > 0 && (text[Index - 1] == '\r' || text[Index - 1] == '\n'))
601     Index--;
602 
603   return (Index < Length)? text.Mid(0, Index) : text;
604 }
605 
606 
607 // Override of PChannel virtual method to ensure PSoundChannelDirectSound's static method is used
608 
609 PString PSoundChannelDirectSound::GetErrorText(ErrorGroup group) const // public
610 {
611   return GetErrorText(lastErrorCode[group], lastErrorNumber[group]);
612 }
613 
614 
615 ///////////////////////////////////////////////////////////////////////////////
616 // Performance monitoring
617 
618 
619 // measure time since last check
620 
621 PTimeInterval PSoundChannelDirectSound::GetInterval (void) // private
622 {
623   PTimeInterval tick = PTimer::Tick();
624   PTimeInterval Interval = (m_tick.GetInterval() == 0i64)? 0i64 : (tick - m_tick);
625   m_tick = tick;
626   return Interval;
627 }
628 
629 
630 // Get the number of entire Buffers that DirectSound has filled or emptied since last
631 // time we checked, to detect buffer wrap around
632 
633 DWORD PSoundChannelDirectSound::GetCyclesPassed (void) // private
634 {
635   // measure time since last check
636   PTimeInterval interval = GetInterval();
637   // convert time to number of buffers filled
638   DWORD bytes = interval.GetInterval() * m_waveFormat.nAvgBytesPerSec / 1000;
639   return bytes / m_bufferSize;
640 }
641 
642 
643 ///////////////////////////////////////////////////////////////////////////////
644 // Playback
645 
646 // requires buffer size and media format
647 // if any failure occurs in here, device is closed
648 
649 PBoolean PSoundChannelDirectSound::OpenPlayback (LPCGUID deviceId) // private
650 {
651   HRESULT result = DirectSoundCreate8(deviceId, &m_playbackDevice, NULL);
652   if (result != S_OK) {
653     SetErrorValues(Miscellaneous, result);
654     PTRACE(4, "dsound\tOpen Playback: Could not create device: " << GetErrorText());
655     return false;
656   }
657   HWND window = GetForegroundWindow();
658   if (window == NULL)
659     window = GetDesktopWindow();
660 
661   result = m_playbackDevice->SetCooperativeLevel(window, DSSCL_PRIORITY);
662   if (result != S_OK) {
663     SetErrorValues(Miscellaneous, result);
664     PTRACE(4, "dsound\tOpen Playback: Could not set cooperative level: " << GetErrorText());
665 	ClosePlayback();
666     return false;
667   }
668   return OpenPlaybackBuffer();
669 }
670 
671 
672 // if any failure occurs in here, device is closed
673 
674 PBoolean PSoundChannelDirectSound::OpenPlaybackBuffer () // private
675 {
676   if (m_playbackBuffer != NULL)
677     m_playbackBuffer.Release();
678 
679   m_movePos = 0; // reset public read/write position
680   m_dsPos = 0; // DirectSound read/write position
681   m_tick.SetInterval(0i64);
682 
683   DSBUFFERDESC bufferDescription = {
684     sizeof(DSBUFFERDESC),
685     DSBCAPS_GLOBALFOCUS + DSBCAPS_CTRLPOSITIONNOTIFY + DSBCAPS_GETCURRENTPOSITION2,
686     m_bufferSize, // calculated by SetBuffers
687     0,            // reserved
688     &m_waveFormat // format
689   };
690   if (PProcess::IsOSVersion(6, 0)) // Vista
691     bufferDescription.dwFlags += DSBCAPS_TRUEPLAYPOSITION;
692 
693   HRESULT result = m_playbackDevice->CreateSoundBuffer(&bufferDescription, &m_playbackBuffer, NULL);
694   if (FAILED(result)) {
695     SetErrorValues(Miscellaneous, result);
696     PTRACE(4, "dsound\tOpenPlaybackBuffer: CreateSoundBuffer fail: " << GetErrorText());
697 	ClosePlayback();
698     return false;
699   }
700   IDirectSoundNotify * notify; // temporary pointer to the interface
701   result = m_playbackBuffer->QueryInterface(IID_IDirectSoundNotify, (LPVOID *) &notify);
702   if (FAILED(result)) {
703     SetErrorValues(Miscellaneous, result);
704     PTRACE(4, "dsound\tOpenPlaybackBuffer: notify interface query fail: " << GetErrorText());
705 	ClosePlayback();
706     return false;
707   }
708   PTRACE(4, "dsound\tOpen Playback: Setting up notification for " << m_bufferSectionCount << " blocks of " << m_bufferSectionSize << " bytes");
709   DSBPOSITIONNOTIFY * position = new DSBPOSITIONNOTIFY[m_bufferSectionCount];
710   if (position == 0) {
711     SetErrorValues(NoMemory, E_OUTOFMEMORY);
712     PTRACE(4, "dsound\tOpenPlaybackBuffer: notify allocation fail");
713     notify->Release();
714 	ClosePlayback();
715     return false;
716   }
717   DWORD blockOffset = m_bufferSectionSize - 1;
718   for (PINDEX i = 0; i < m_bufferSectionCount; i++) {
719     position[i].dwOffset = blockOffset;
720     position[i].hEventNotify = m_triggerEvent[SOUNDEVENT_SOUND]; // all use same event
721     blockOffset += m_bufferSectionSize;
722   }
723   result = notify->SetNotificationPositions(m_bufferSectionCount, position);
724   notify->Release();
725   if (FAILED(result)) {
726     SetErrorValues(Miscellaneous, result);
727     PTRACE(4, "dsound\tOpenPlaybackBuffer: Notify interface query fail: " << GetErrorText());
728 	ClosePlayback();
729     return false;
730   }
731   delete [] position;
732   m_playbackBuffer->SetCurrentPosition(0);
733   return true;
734 }
735 
736 
737 void PSoundChannelDirectSound::ClosePlayback() // private
738 {
739   if (m_playbackBuffer != NULL) {
740     m_playbackBuffer->Stop();
741     int notification;
742     CheckPlayBuffer(notification); // last effort to see how many bytes played
743   }
744   m_playbackBuffer.Release();
745   m_playbackDevice.Release();
746 }
747 
748 
749 PBoolean PSoundChannelDirectSound::CheckPlayBuffer (int & notification) // private
750 {
751   notification = SOUNDNOTIFY_NOTHING;
752   PWaitAndSignal mutex(m_bufferMutex); // make Close & SetBuffers wait
753 
754   if (!m_playbackBuffer) { // closed
755     PTRACE(4, "dsound\tPlayback closed while checking");
756     return false;
757   }               // Write is ahead of Play, DirectSound is playing data from Play to Write - do not put data between them.
758                   // we can write ahead of Write, from m_movePos to PlayPos
759   DWORD playPos;  // circular buffer byte offset to start of section DirectSound is playing (end of where we can write)
760   DWORD writePos; // circular buffer byte offset ahead of which it is safe to write data (start of where we can write)
761   HRESULT result = m_playbackBuffer->GetCurrentPosition(&playPos, &writePos);
762   if (FAILED(result)) {
763     SetErrorValues(Miscellaneous, result, LastWriteError);
764     notification = SOUNDNOTIFY_ERROR;
765     return false;
766   }
767   // Record the driver performance, include check for writer getting way behind player
768   // bytes played since last check
769   DWORD played = (playPos + m_bufferSize - m_dsPos) % m_bufferSize;
770   m_dsPos = playPos;
771   // add in bytes from possible complete buffer cyclings since last check
772   played += GetCyclesPassed() * m_bufferSize;
773   // count bytes played since open
774   m_dsMoved += played;
775 
776   DWORD status;
777   result = m_playbackBuffer->GetStatus(&status);
778   if (FAILED(result)) {
779     SetErrorValues(Miscellaneous, result, LastWriteError);
780     notification = SOUNDNOTIFY_ERROR;
781     return false;
782   }
783   if ((status & DSBSTATUS_PLAYING) == 0) { // not started yet, or we let it run empty (not looping)
784     m_available = m_bufferSize;
785     return true;
786   }
787   // check for underrun
788   if (m_dsMoved > m_moved + m_lost)
789   { // Write has been delayed so much that card is playing from empty or old space
790     unsigned underruns = (unsigned)(m_dsMoved - (m_moved + m_lost));
791     m_lost += underruns;
792     PTRACE(3, "dsound\tPlayback underrun: wrote " << GetSamplesMoved() << " played " << GetSamplesBuffered() << " replayed " << underruns / m_waveFormat.nBlockAlign << " total loss " << GetSamplesLost() << " samples");
793     notification = SOUNDNOTIFY_UNDERRUN;
794     m_movePos = writePos;
795   }
796   // calculate available space in circular buffer (between our last write position and DirectSound read position)
797   m_available = (playPos + m_bufferSize - m_movePos) % m_bufferSize;
798   if (m_available >= (unsigned)m_bufferSectionSize)
799     return true;
800 
801   // some space, but not enough yet
802   PTRACE(6, "dsound\tPlayer buffer overrun, waiting for space");
803   notification = SOUNDNOTIFY_OVERRUN;
804   return false;
805 }
806 
807 
808 PBoolean PSoundChannelDirectSound::WaitForPlayBufferFree () // protected
809 {
810   ResetEvent(m_triggerEvent[SOUNDEVENT_SOUND]);
811   int notification;
812   do {
813     notification = SOUNDNOTIFY_NOTHING;
814     PBoolean isSpaceAvailable = CheckPlayBuffer(notification);
815     // report errors
816     if (notification != SOUNDNOTIFY_NOTHING && m_notifier.GetObject() != NULL)
817       m_notifier(*this, notification); // can close here!
818 
819     if (!m_playbackBuffer) { // closed
820       PTRACE(4, "dsound\tPlayback closed while writing");
821       return SetErrorValues(NotOpen, EBADF, LastWriteError);
822     }
823     if (isSpaceAvailable) // Ok to write
824       return true;
825   }  // wait for DirectSound to notify us that space is available
826   while (WaitForMultipleObjects(2, m_triggerEvent, FALSE, INFINITE) == WAIT_OBJECT_0);
827 
828   { // Aborted
829     PWaitAndSignal mutex(m_bufferMutex);
830     PTRACE(4, "dsound\tPlayback write abort");
831     if (m_playbackBuffer != NULL) { // still open
832       m_playbackBuffer->Stop();
833       CheckPlayBuffer(notification);
834 	}
835   }
836   return SetErrorValues(Interrupted, EINTR, LastWriteError);
837 }
838 
839 
840 PBoolean PSoundChannelDirectSound::Write (const void *buf, PINDEX len) // public
841 {
842   {
843     PWaitAndSignal mutex(m_bufferMutex); // prevent closing while active
844     ResetEvent(m_triggerEvent[SOUNDEVENT_ABORT]);
845     lastWriteCount = 0;
846     if (!m_playbackBuffer) { // check this before direction=Closed causes assertion
847       SetErrorValues(NotOpen, EBADF, LastWriteError);
848       PTRACE(4, "dsound\tWrite fail: Device closed");
849       return false;
850     }
851   }                                     // unlock to allow Abort while waiting for buffer
852 
853   PAssert(activeDirection == Player, "Invalid device direction");
854   PAssertNULL(buf);
855 
856   char * src = (char *) buf;
857   while (lastWriteCount < len) {
858     // wait for output space to become available
859     if (!WaitForPlayBufferFree())       // sets m_movePos and m_available
860       return false;                     // aborted/closed
861     {
862       PWaitAndSignal mutex(m_bufferMutex);  // prevent closing while writing
863       LPVOID pointer1, pointer2;
864       DWORD length1, length2;
865       HRESULT Result = m_playbackBuffer->Lock(m_movePos, PMIN((PINDEX)m_available, len),
866                                               &pointer1, &length1, &pointer2, &length2, 0L);
867       if (Result == DSERR_BUFFERLOST) {   // Buffer was lost, need to restore it
868         m_playbackBuffer->Restore();
869         Result = m_playbackBuffer->Lock(m_movePos, PMIN((PINDEX)m_available, len),
870                                         &pointer1, &length1, &pointer2, &length2, 0L);
871       }
872       if (FAILED(Result)) {
873         SetErrorValues(Miscellaneous, Result, LastWriteError);
874         PTRACE(1, "dsound\tWrite failure: " << GetErrorText() << " len " << len << " pos " << m_movePos);
875         return false;
876       }
877       // Copy supplied buffer into locked DirectSound memory
878       memcpy((char *)pointer1, src, length1);
879       if (pointer2 != NULL)
880         memcpy(pointer2, (char *)src + length1, length2);
881 
882       m_playbackBuffer->Unlock(pointer1, length1, pointer2, length2);
883 
884       PTRACE_IF(4, m_moved == 0, "dsound\tPlayback starting");
885 
886       PINDEX writeCount = length1 + length2;
887       src += writeCount;
888       len -= writeCount;
889       lastWriteCount += writeCount;
890       m_movePos += lastWriteCount;
891       m_movePos %= m_bufferSize;
892       m_moved += writeCount;
893                                           // tell DirectSound to play
894       m_playbackBuffer->Play(0, 0, m_isStreaming ? DSBPLAY_LOOPING : 0L);
895     }
896   }
897   return true;
898 }
899 
900 
901 PBoolean PSoundChannelDirectSound::HasPlayCompleted () // public
902 {
903   // only works for non-streaming player
904   PWaitAndSignal mutex(m_bufferMutex); // prevent closing while active
905 
906   if (!m_playbackBuffer)
907     return true;
908 
909   DWORD status;
910   HRESULT result = m_playbackBuffer->GetStatus(&status);
911   if (FAILED(result)) {
912     SetErrorValues(Miscellaneous, result, LastWriteError);
913     return true; // it's done if we get an error here
914   }
915   return ((status & DSBSTATUS_PLAYING) == 0);
916 }
917 
918 
919 PBoolean PSoundChannelDirectSound::WaitForPlayCompletion () // public
920 {
921   // only works for non-streaming player
922   while (!HasPlayCompleted())
923     Sleep(50);
924 
925   return true;
926 }
927 
928 
929 PBoolean PSoundChannelDirectSound::PlaySound (const PSound & sound, PBoolean wait) // public
930 {
931   PAssert(activeDirection == Player, "Invalid device direction");
932 
933   if (!SetBuffers(sound.GetSize(), 1)) // Aborts
934     return false;
935 
936   m_isStreaming = false;
937 
938   if (!Write((const void *)sound, sound.GetSize()))
939     return false;
940 
941   if (wait)
942     return WaitForPlayCompletion();
943 
944   return true;
945 }
946 
947 
948 PBoolean PSoundChannelDirectSound::PlayFile (const PFilePath & filename, PBoolean wait) // public
949 {
950   PAssert(activeDirection == Player, "Invalid device direction");
951 
952   PMultiMediaFile mediaFile;
953   PWaveFormat fileFormat;
954   DWORD dataSize;
955   if (!mediaFile.OpenWaveFile(filename, fileFormat, dataSize))
956     return SetErrorValues(NotOpen, mediaFile.GetLastError() | PWIN32ErrorFlag, LastWriteError);
957 
958   //Abort();
959   if (!SetFormat(fileFormat->nChannels, fileFormat->nSamplesPerSec, fileFormat->wBitsPerSample))
960     return false;
961 
962   int bufferSize = m_waveFormat.nAvgBytesPerSec / 2; // 1/2 second
963   if (!SetBuffers(bufferSize, 4))                    // 2 seconds
964     return false;
965 
966   PBYTEArray buffer;
967   m_isStreaming = false;
968 
969   while (dataSize) {
970     // Read the waveform data subchunk
971     PINDEX count = PMIN(dataSize,((DWORD)bufferSize));
972     if (!mediaFile.Read(buffer.GetPointer(bufferSize), count)) {
973       SetErrorValues(Miscellaneous, mediaFile.GetLastError() | PWIN32ErrorFlag, LastReadError);
974       PTRACE(4, "dsound\tPlayFile read error: " << GetErrorText());
975       return false;
976     }
977     if (!Write(buffer, count))
978       break;
979 
980     dataSize -= count;
981   }
982   mediaFile.Close();
983 
984   if (wait)
985     return WaitForPlayCompletion();
986 
987   return true;
988 }
989 
990 
991 ///////////////////////////////////////////////////////////////////////////////
992 // Recording
993 
994 // requires buffer size and media format
995 // if any failure occurs in here, device is closed
996 
997 PBoolean PSoundChannelDirectSound::OpenCapture (LPCGUID deviceId) // private
998 {
999   HRESULT result = DirectSoundCaptureCreate8(deviceId, &m_captureDevice, NULL);
1000   if (result != S_OK) {
1001     SetErrorValues(Miscellaneous, result);
1002     PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Could not create device: " << GetErrorText());
1003     return false;
1004   }
1005   return OpenCaptureBuffer();
1006 }
1007 
1008 
1009 // if any failure occurs in here, device is closed
1010 
1011 PBoolean PSoundChannelDirectSound::OpenCaptureBuffer () // private
1012 {
1013   if (m_captureBuffer != NULL)
1014     m_captureBuffer.Release();
1015 
1016   m_movePos = 0; // reset public read/write position
1017   m_dsPos = 0; // DirectSound read/write position
1018   m_tick.SetInterval(0i64);
1019 
1020   DSCBUFFERDESC bufferDescription = {
1021     sizeof(DSCBUFFERDESC),
1022     DSCBCAPS_WAVEMAPPED,    // use wave mapper for formats unsupported by device
1023     m_bufferSize,           // calculated by SetBuffers
1024     0,                      // reserved
1025     &m_waveFormat           // format
1026   };
1027   HRESULT result = m_captureDevice->CreateCaptureBuffer(&bufferDescription, &m_captureBuffer, NULL);
1028   if (FAILED(result)) {
1029     SetErrorValues(Miscellaneous, result);
1030     PTRACE(4, "dsound\tOpenCaptureBuffer: Create Sound Buffer fail: " << GetErrorText());
1031     return false;
1032   }
1033   IDirectSoundNotify * notify; // temporary pointer to the interface
1034   result = m_captureBuffer->QueryInterface(IID_IDirectSoundNotify, (LPVOID *) &notify);
1035   if (FAILED(result)) {
1036     SetErrorValues(Miscellaneous, result);
1037     PTRACE(4, "dsound\tOpenCaptureBuffer: Notify interface query fail: " << GetErrorText());
1038     CloseCapture();
1039     return false;
1040   }
1041   PTRACE(4, "dsound\tOpenCaptureBuffer: Setting up notification for " << m_bufferSectionCount << " blocks of " << m_bufferSectionSize << " bytes");
1042   DSBPOSITIONNOTIFY * position = new DSBPOSITIONNOTIFY[m_bufferSectionCount];
1043   if (position == NULL) {
1044     SetErrorValues(NoMemory, E_OUTOFMEMORY);
1045     PTRACE(4, "dsound\tOpenCaptureBuffer: Notify allocation fail");
1046     notify->Release();
1047     CloseCapture();
1048     return false;
1049   }
1050   DWORD blockOffset = m_bufferSectionSize - 1;
1051   for (PINDEX i = 0; i < m_bufferSectionCount; i++) {
1052     position[i].dwOffset = blockOffset;
1053     position[i].hEventNotify = m_triggerEvent[SOUNDEVENT_SOUND]; // all use same event
1054     blockOffset += m_bufferSectionSize;
1055   }
1056   result = notify->SetNotificationPositions(m_bufferSectionCount, position);
1057   notify->Release();
1058   if (FAILED(result)) {
1059     SetErrorValues(Miscellaneous, result);
1060     PTRACE(4, "dsound\tOpenCaptureBuffer: Notify interface query fail: " << GetErrorText());
1061     CloseCapture();
1062     return false;
1063   }
1064   delete [] position;
1065 
1066   return true;
1067 }
1068 
1069 
1070 void PSoundChannelDirectSound::CloseCapture () // private
1071 {
1072   if (m_captureBuffer != NULL) {
1073     m_captureBuffer->Stop();
1074     int notification;
1075     CheckCaptureBuffer(notification); // last effort to see how many bytes played
1076   }
1077   m_captureBuffer.Release();
1078   m_captureDevice.Release();
1079 }
1080 
1081 
1082 PBoolean PSoundChannelDirectSound::StartRecording () // public
1083 {
1084   PWaitAndSignal mutex(m_bufferMutex);
1085 
1086   if (!m_captureBuffer) { // closed
1087     SetErrorValues(NotOpen, EBADF, LastReadError);
1088     PTRACE(4, "dsound\tStartRecording fail: Device closed");
1089     return false;
1090   }
1091   DWORD Status = 0;
1092   HRESULT Result = m_captureBuffer->GetStatus(&Status);
1093   if (FAILED(Result)) {
1094     SetErrorValues(Miscellaneous, Result, LastReadError);
1095     PTRACE(4, "dsound\tStartRecording: Failed GetStatus - " << GetErrorText());
1096       return false;
1097   }
1098   if ((Status & DSCBSTATUS_CAPTURING) != 0)
1099     return true; // already started
1100 
1101   Result = m_captureBuffer->Start(DSCBSTART_LOOPING);
1102   if (FAILED(Result)) {
1103     SetErrorValues(Miscellaneous, Result, LastReadError);
1104     PTRACE(4, "dsound\tStartRecording: Failed Start - " << GetErrorText());
1105       return false;
1106   }
1107   PTRACE(4, "dsound\tRecorder starting");
1108   return true;
1109 }
1110 
1111 
1112 PBoolean PSoundChannelDirectSound::CheckCaptureBuffer (int & notification) // private
1113 {
1114   notification = SOUNDNOTIFY_NOTHING;
1115   PWaitAndSignal mutex(m_bufferMutex); // make Abort wait
1116 
1117   if (!m_captureBuffer) { // closed
1118     PTRACE(4, "dsound\tRecording closed while checking");
1119     return false;
1120   }                 // Capture is ahead of Read, do not get data from between them. Data for us is at m_movePos behind Read.
1121   DWORD readPos;    // circular buffer byte offset to the end of the data that has been fully captured (end of what we can read)
1122   DWORD capturePos; // circular buffer byte offset to the head of the block that card has locked for new data
1123   HRESULT result = m_captureBuffer->GetCurrentPosition(&capturePos, &readPos);
1124   if (FAILED(result)) {
1125     notification = SOUNDNOTIFY_ERROR;
1126     return SetErrorValues(Miscellaneous, result, LastReadError);
1127   }
1128   // Record the driver performance, include check for reader getting way behind recorder
1129   // bytes recorded since last check (this count includes stuff we can't even read yet)
1130   DWORD captured = (capturePos + m_bufferSize - m_dsPos) % m_bufferSize;
1131   m_dsPos = capturePos;
1132   // add in bytes from possible complete buffer cycling since last check
1133   DWORD cycles = GetCyclesPassed();
1134   captured += cycles * m_bufferSize;
1135   // count bytes recorded since open
1136   m_dsMoved += captured;
1137 
1138   // check for overrun
1139   if (m_dsMoved > m_moved + m_lost + m_bufferSize)
1140   { // Read has been delayed so much that card is writing into space that we have not read yet
1141     // overruns are what will never be read
1142     unsigned overruns = (unsigned)((m_dsMoved - m_bufferSize) - (m_moved + m_lost));
1143     m_lost += overruns;
1144     PTRACE(3, "dsound\tRecorder overrun: captured " << GetSamplesBuffered() << " read " << GetSamplesMoved() << " lost " << overruns / m_waveFormat.nBlockAlign << " total loss " << GetSamplesLost() << " samples");
1145     notification = SOUNDNOTIFY_OVERRUN;
1146     // Move read position to DirectSound's capture position, where audio has not been overwritten yet
1147     m_movePos = m_dsPos;
1148   }
1149   // calculate available data in circular buffer (between our last write position and DirectSound read position)
1150   m_available = (readPos + m_bufferSize - m_movePos) % m_bufferSize;
1151   return m_available >= (unsigned)m_bufferSectionSize;
1152 }
1153 
1154 
1155 PBoolean PSoundChannelDirectSound::IsRecordBufferFull () // public
1156 {
1157   if (!StartRecording()) // Start the first read
1158     return false;
1159 
1160   int notification = SOUNDNOTIFY_NOTHING;
1161   PBoolean isDataAvailable = CheckCaptureBuffer(notification);
1162   // report errors
1163   if (notification != SOUNDNOTIFY_NOTHING && m_notifier.GetObject() != NULL)
1164     m_notifier(*this, notification); // can close here!
1165 
1166   return m_captureBuffer != NULL && isDataAvailable;
1167 }
1168 
1169 
1170 PBoolean PSoundChannelDirectSound::WaitForRecordBufferFull () // public
1171 {
1172   if (!StartRecording()) // Start the first read
1173     return false;
1174 
1175   int notification;
1176   ResetEvent(m_triggerEvent[SOUNDEVENT_SOUND]);
1177   do {
1178     notification = SOUNDNOTIFY_NOTHING;
1179     PBoolean isDataAvailable = CheckCaptureBuffer(notification);
1180     // report errors
1181     if (notification != SOUNDNOTIFY_NOTHING && m_notifier.GetObject() != NULL)
1182       m_notifier(*this, notification); // can close here!
1183 
1184     if (!m_captureBuffer) { // closed
1185       PTRACE(4, "dsound\tRecording closed while reading");
1186       return SetErrorValues(NotOpen, EBADF, LastReadError);
1187     }
1188     if (isDataAvailable) // Ok to read
1189       return true;
1190   }  // wait for DirectSound to notify us that space is available
1191   while (WaitForMultipleObjects(2, m_triggerEvent, FALSE, INFINITE) == WAIT_OBJECT_0);
1192 
1193   { // Aborted
1194     PWaitAndSignal mutex(m_bufferMutex);
1195     PTRACE(4, "dsound\tRecording read abort");
1196     if (m_captureBuffer != NULL) { // still open
1197       m_captureBuffer->Stop();
1198       CheckCaptureBuffer(notification);
1199 	}
1200   }
1201   return SetErrorValues(Interrupted, EINTR, LastReadError);
1202 }
1203 
1204 
1205 PBoolean PSoundChannelDirectSound::Read (void * buf, PINDEX len) // public
1206 {
1207   {
1208     PWaitAndSignal mutex(m_bufferMutex); // prevent closing while active
1209     ResetEvent(m_triggerEvent[SOUNDEVENT_ABORT]);
1210     lastReadCount = 0;
1211     if (!m_captureBuffer) { // check this before direction=Closed causes assertion
1212       SetErrorValues(NotOpen, EBADF, LastReadError);
1213       PTRACE(4, "dsound\tRead fail: Device closed");
1214       return false;
1215     }
1216   }                                     // unlock to allow Abort while waiting for buffer
1217 
1218   PAssert(activeDirection == Recorder, "Invalid device direction");
1219   PAssertNULL(buf);
1220 
1221   char * dest = (char *) buf;
1222   while (lastReadCount < len) {
1223     if (!WaitForRecordBufferFull())     // sets m_movePos and m_available
1224       return false;                     // aborted/closed
1225     {
1226       PWaitAndSignal mutex(m_bufferMutex);  // prevent closing while active
1227 
1228       // Read from device buffer minimum between the data required and data available
1229       LPVOID pointer1, pointer2;
1230       DWORD length1, length2;
1231       HRESULT Result = m_captureBuffer->Lock(m_movePos, PMIN((PINDEX)m_available, len),
1232                                              &pointer1, &length1, &pointer2, &length2, 0L);
1233       if (FAILED(Result)) {
1234         SetErrorValues(Miscellaneous, Result, LastReadError);
1235         PTRACE(1, "dsound\tRead Lock fail: " << GetErrorText());
1236         return false;
1237       }
1238       // Copy from DirectSound locked memory into return buffer
1239       memcpy((char *)dest, pointer1, length1);
1240       if (pointer2 != NULL)
1241         memcpy((char *)dest + length1, pointer2, length2);
1242 
1243       m_captureBuffer->Unlock(pointer1, length1, pointer2, length2);
1244 
1245       PINDEX readCount = length1 + length2;
1246       dest += readCount;
1247       len -= readCount;
1248       lastReadCount += readCount;
1249       m_movePos += readCount;
1250       m_movePos %= m_bufferSize;
1251       m_moved += readCount;
1252     }
1253   }
1254   return true;
1255 }
1256 
1257 
1258 PBoolean PSoundChannelDirectSound::AreAllRecordBuffersFull() // public
1259 {
1260   PTRACE(4, "dsound\tAreAllRecordBuffersFull unimplemented");
1261   return true;
1262 }
1263 
1264 
1265 PBoolean PSoundChannelDirectSound::WaitForAllRecordBuffersFull() // public
1266 {
1267   PTRACE(4, "dsound\tWaitForAllRecordBuffersFull unimplemented");
1268   return false;
1269 }
1270 
1271 
1272 PBoolean PSoundChannelDirectSound::RecordSound (PSound & /*sound*/) // public
1273 {
1274   PAssert(activeDirection == Recorder, "Invalid device direction");
1275   PTRACE(4, "dsound\tRecordSound unimplemented");
1276   return false;
1277 }
1278 
1279 
1280 PBoolean PSoundChannelDirectSound::RecordFile (const PFilePath & /*filename*/) // public
1281 {
1282   PAssert(activeDirection == Recorder, "Invalid device direction");
1283   PTRACE(4, "dsound\tRecordFile unimplemented");
1284   return false;
1285 }
1286 
1287 
1288 ///////////////////////////////////////////////////////////////////////////////
1289 // Volume
1290 
1291 // Hook up to mixer volume control for device
1292 // Use wave mixer because DirectX does not let you change the capture buffer volume
1293 // and player buffer volume changes do not interact with Control Panel
1294 
1295 PBoolean PSoundChannelDirectSound::OpenMixer (UINT waveDeviceId)
1296 {
1297   mixerOpen(&m_mixer, waveDeviceId, NULL, NULL, (activeDirection == Player)? MIXER_OBJECTF_WAVEOUT : MIXER_OBJECTF_WAVEIN);
1298   if (m_mixer == NULL) {
1299     PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Failed to open mixer - volume control will not function");
1300 	return false;
1301   }
1302   MIXERLINE line = { sizeof(MIXERLINE) };
1303   line.dwComponentType = (activeDirection == Player)? MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT : MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
1304   if (mixerGetLineInfo((HMIXEROBJ)m_mixer, &line, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR) {
1305     PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Failed to access mixer line - volume control will not function");
1306     CloseMixer();
1307 	return false;
1308   }
1309   m_volumeControl.cbStruct = sizeof(m_volumeControl);
1310 
1311   MIXERLINECONTROLS controls;
1312   controls.cbStruct = sizeof(controls);
1313   controls.dwLineID = line.dwLineID & 0xffff;
1314   controls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
1315   controls.cControls = 1;
1316   controls.pamxctrl = &m_volumeControl;
1317   controls.cbmxctrl = m_volumeControl.cbStruct;
1318 
1319   if (mixerGetLineControls((HMIXEROBJ)m_mixer, &controls, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) {
1320     PTRACE(4, "dsound\tOpen" << GetDirectionText() << ": Failed to configure volume control - volume control will not function");
1321     CloseMixer();
1322 	return false;
1323   }
1324   return true;
1325 }
1326 
1327 
1328 void PSoundChannelDirectSound::CloseMixer ()
1329 {
1330   if (m_mixer == NULL)
1331     return;
1332 
1333   mixerClose(m_mixer);
1334   m_mixer = NULL;
1335 }
1336 
1337 
1338 PBoolean PSoundChannelDirectSound::SetVolume (unsigned newVal) // public
1339 {
1340   PWaitAndSignal mutex(m_bufferMutex);
1341   if (!IsOpen() || m_mixer == NULL) {
1342     PTRACE(4, "dsound\t" << GetDirectionText() << " SetVolume fail: Device closed");
1343     return SetErrorValues(NotOpen, EBADF);
1344   }
1345   MIXERCONTROLDETAILS_UNSIGNED volume;
1346   if (newVal >= MaxVolume)
1347     volume.dwValue = m_volumeControl.Bounds.dwMaximum;
1348   else
1349     volume.dwValue = m_volumeControl.Bounds.dwMinimum +
1350             (DWORD)((m_volumeControl.Bounds.dwMaximum - m_volumeControl.Bounds.dwMinimum) *
1351                                                    log10(9.0 * newVal / MaxVolume + 1.0));
1352   MIXERCONTROLDETAILS details;
1353   details.cbStruct = sizeof(details);
1354   details.dwControlID = m_volumeControl.dwControlID;
1355   details.cChannels = 1;
1356   details.cMultipleItems = 0;
1357   details.cbDetails = sizeof(volume);
1358   details.paDetails = &volume;
1359 
1360   MMRESULT result = mixerSetControlDetails((HMIXEROBJ)m_mixer, &details, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
1361   if (result != MMSYSERR_NOERROR) {
1362     SetErrorValues(Miscellaneous, MAKE_HRESULT(1, FACILITY_WAVEIN, result));
1363     PTRACE(4, "dsound\t" << GetDirectionText() << " SetVolume fail: " << GetErrorText());
1364     return false;
1365   }
1366   return true;
1367 }
1368 
1369 
1370 PBoolean PSoundChannelDirectSound::GetVolume (unsigned & devVol) // public
1371 {
1372   PWaitAndSignal mutex(m_bufferMutex);
1373   if (!IsOpen() || m_mixer == NULL) {
1374     PTRACE(4, "dsound\t" << GetDirectionText() << " GetVolume fail: Device closed");
1375     return SetErrorValues(NotOpen, EBADF);
1376   }
1377   MIXERCONTROLDETAILS_UNSIGNED volume;
1378 
1379   MIXERCONTROLDETAILS details;
1380   details.cbStruct = sizeof(details);
1381   details.dwControlID = m_volumeControl.dwControlID;
1382   details.cChannels = 1;
1383   details.cMultipleItems = 0;
1384   details.cbDetails = sizeof(volume);
1385   details.paDetails = &volume;
1386 
1387   MMRESULT result = mixerGetControlDetails((HMIXEROBJ)m_mixer, &details, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE);
1388   if (result != MMSYSERR_NOERROR) {
1389     SetErrorValues(Miscellaneous, MAKE_HRESULT(1, FACILITY_WAVEIN, result));
1390     PTRACE(4, "dsound\t" << GetDirectionText() << " GetVolume fail: " << GetErrorText());
1391     return false;
1392   }
1393   devVol = 100 * (volume.dwValue - m_volumeControl.Bounds.dwMinimum) / (m_volumeControl.Bounds.dwMaximum - m_volumeControl.Bounds.dwMinimum);
1394   return true;
1395 }
1396 
1397 
1398 ///////////////////////////////////////////////////////////////////////////////
1399 
1400 
1401 #else
1402 
1403   #ifdef _MSC_VER
1404     #pragma message("Direct Sound support DISABLED")
1405   #endif
1406 
1407 #endif // P_DIRECTSOUND
1408