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 *) ¬ify);
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 *) ¬ify);
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