1 /*
2  * sound.cxx
3  *
4  * Code for pluigns sound device
5  *
6  * Portable Windows Library
7  *
8  * Copyright (c) 2003 Post Increment
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 Code is Post Increment
23  *
24  * Contributor(s): Craig Southeren
25  *                 Snark at GnomeMeeting
26  *
27  * $Revision: 28580 $
28  * $Author: rjongbloed $
29  * $Date: 2012-11-26 01:31:08 -0600 (Mon, 26 Nov 2012) $
30  */
31 
32 #ifdef __GNUC__
33 #pragma implementation "sound.h"
34 #endif
35 
36 #include <ptlib.h>
37 
38 #include <ptlib/sound.h>
39 #include <ptlib/pluginmgr.h>
40 #include <ptclib/delaychan.h>
41 
42 
43 static const char soundPluginBaseClass[] = "PSoundChannel";
44 
45 
Create(const PString & type) const46 template <> PSoundChannel * PDevicePluginFactory<PSoundChannel>::Worker::Create(const PString & type) const
47 {
48   return PSoundChannel::CreateChannel(type);
49 }
50 
51 typedef PDevicePluginAdapter<PSoundChannel> PDevicePluginSoundChannel;
52 PFACTORY_CREATE(PFactory<PDevicePluginAdapterBase>, PDevicePluginSoundChannel, "PSoundChannel", true);
53 
54 
GetDriverNames(PPluginManager * pluginMgr)55 PStringArray PSoundChannel::GetDriverNames(PPluginManager * pluginMgr)
56 {
57   if (pluginMgr == NULL)
58     pluginMgr = &PPluginManager::GetPluginManager();
59 
60   return pluginMgr->GetPluginsProviding(soundPluginBaseClass);
61 }
62 
63 
GetDriversDeviceNames(const PString & driverName,PSoundChannel::Directions dir,PPluginManager * pluginMgr)64 PStringArray PSoundChannel::GetDriversDeviceNames(const PString & driverName,
65                                                   PSoundChannel::Directions dir,
66                                                   PPluginManager * pluginMgr)
67 {
68   if (pluginMgr == NULL)
69     pluginMgr = &PPluginManager::GetPluginManager();
70 
71   return pluginMgr->GetPluginsDeviceNames(driverName, soundPluginBaseClass, dir);
72 }
73 
74 
CreateChannel(const PString & driverName,PPluginManager * pluginMgr)75 PSoundChannel * PSoundChannel::CreateChannel(const PString & driverName, PPluginManager * pluginMgr)
76 {
77   if (pluginMgr == NULL)
78     pluginMgr = &PPluginManager::GetPluginManager();
79 
80   return (PSoundChannel *)pluginMgr->CreatePluginsDevice(driverName, soundPluginBaseClass, 0);
81 }
82 
83 
CreateChannelByName(const PString & deviceName,PSoundChannel::Directions dir,PPluginManager * pluginMgr)84 PSoundChannel * PSoundChannel::CreateChannelByName(const PString & deviceName,
85                                                    PSoundChannel::Directions dir,
86                                                    PPluginManager * pluginMgr)
87 {
88   if (pluginMgr == NULL)
89     pluginMgr = &PPluginManager::GetPluginManager();
90 
91   return (PSoundChannel *)pluginMgr->CreatePluginsDeviceByName(deviceName, soundPluginBaseClass, dir);
92 }
93 
94 
CreateOpenedChannel(const PString & driverName,const PString & deviceName,PSoundChannel::Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample,PPluginManager * pluginMgr)95 PSoundChannel * PSoundChannel::CreateOpenedChannel(const PString & driverName,
96                                                    const PString & deviceName,
97                                                    PSoundChannel::Directions dir,
98                                                    unsigned numChannels,
99                                                    unsigned sampleRate,
100                                                    unsigned bitsPerSample,
101                                                    PPluginManager * pluginMgr)
102 {
103   PString adjustedDeviceName = deviceName;
104   PSoundChannel * sndChan;
105   if (driverName.IsEmpty() || driverName == "*") {
106     if (deviceName.IsEmpty() || deviceName == "*")
107       adjustedDeviceName = PSoundChannel::GetDefaultDevice(dir);
108     sndChan = CreateChannelByName(adjustedDeviceName, dir, pluginMgr);
109   }
110   else {
111     if (deviceName.IsEmpty() || deviceName == "*") {
112       PStringArray devices = PSoundChannel::GetDriversDeviceNames(driverName, PSoundChannel::Player);
113       if (devices.IsEmpty())
114         return NULL;
115       adjustedDeviceName = devices[0];
116     }
117     sndChan = CreateChannel(driverName, pluginMgr);
118   }
119 
120   if (sndChan != NULL && sndChan->Open(adjustedDeviceName, dir, numChannels, sampleRate, bitsPerSample))
121     return sndChan;
122 
123   delete sndChan;
124   return NULL;
125 }
126 
127 
GetDeviceNames(PSoundChannel::Directions dir,PPluginManager * pluginMgr)128 PStringArray PSoundChannel::GetDeviceNames(PSoundChannel::Directions dir, PPluginManager * pluginMgr)
129 {
130   if (pluginMgr == NULL)
131     pluginMgr = &PPluginManager::GetPluginManager();
132 
133   return pluginMgr->GetPluginsDeviceNames("*", soundPluginBaseClass, dir);
134 }
135 
136 
GetDefaultDevice(Directions dir)137 PString PSoundChannel::GetDefaultDevice(Directions dir)
138 {
139 #ifdef _WIN32
140   RegistryKey registry("HKEY_CURRENT_USER\\Software\\Microsoft\\Multimedia\\Sound Mapper",
141                        RegistryKey::ReadOnly);
142 
143   PString str;
144 
145   if (dir == Player) {
146     if (registry.QueryValue("ConsoleVoiceComPlayback", str) )
147       return str;
148     if (registry.QueryValue("Playback", str))
149       return str;
150   }
151   else {
152     if (registry.QueryValue("ConsoleVoiceComRecord", str))
153       return str;
154     if (registry.QueryValue("Record", str))
155       return str;
156   }
157 #endif
158 
159   PStringArray devices = GetDeviceNames(dir);
160 
161   if (devices.GetSize() == 0)
162     return PString::Empty();
163 
164   for (PINDEX i = 0; i < devices.GetSize(); ++i) {
165     if (!(devices[i] *= "NULL"))
166       return devices[i];
167   }
168 
169   return devices[0];
170 }
171 
172 
PSoundChannel()173 PSoundChannel::PSoundChannel()
174   : m_baseChannel(NULL)
175   , activeDirection(Closed)
176 {
177 }
178 
~PSoundChannel()179 PSoundChannel::~PSoundChannel()
180 {
181   delete m_baseChannel;
182 }
183 
184 
PSoundChannel(const PString & device,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)185 PSoundChannel::PSoundChannel(const PString & device,
186                              Directions dir,
187                              unsigned numChannels,
188                              unsigned sampleRate,
189                              unsigned bitsPerSample)
190   : m_baseChannel(NULL)
191   , activeDirection(dir)
192 {
193   Open(device, dir, numChannels, sampleRate, bitsPerSample);
194 }
195 
196 
Open(const PString & devSpec,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)197 PBoolean PSoundChannel::Open(const PString & devSpec,
198                          Directions dir,
199                          unsigned numChannels,
200                          unsigned sampleRate,
201                          unsigned bitsPerSample)
202 {
203   PString driver, device;
204   PINDEX colon = devSpec.Find(':');
205   if (colon == P_MAX_INDEX)
206     device = devSpec;
207   else {
208     driver = devSpec.Left(colon);
209     device = devSpec.Mid(colon+1).Trim();
210   }
211 
212   m_baseMutex.StartWrite();
213 
214   delete m_baseChannel;
215   activeDirection = dir;
216 
217   m_baseChannel = CreateOpenedChannel(driver, device, dir, numChannels, sampleRate, bitsPerSample);
218   if (m_baseChannel == NULL && !driver.IsEmpty())
219     m_baseChannel = CreateOpenedChannel(PString::Empty(), devSpec, dir, numChannels, sampleRate, bitsPerSample);
220 
221   m_baseMutex.EndWrite();
222 
223   return m_baseChannel != NULL;
224 }
225 
226 
GetName() const227 PString PSoundChannel::GetName() const
228 {
229   PReadWaitAndSignal mutex(m_baseMutex);
230   return m_baseChannel != NULL ? m_baseChannel->GetName() : PString::Empty();
231 }
232 
233 
IsOpen() const234 PBoolean PSoundChannel::IsOpen() const
235 {
236   PReadWaitAndSignal mutex(m_baseMutex);
237   return m_baseChannel != NULL && m_baseChannel->PChannel::IsOpen();
238 }
239 
240 
Close()241 PBoolean PSoundChannel::Close()
242 {
243   PReadWaitAndSignal mutex(m_baseMutex);
244   return m_baseChannel == NULL || m_baseChannel->Close();
245 }
246 
247 
GetHandle() const248 int PSoundChannel::GetHandle() const
249 {
250   PReadWaitAndSignal mutex(m_baseMutex);
251   return m_baseChannel == NULL ? -1 : m_baseChannel->PChannel::GetHandle();
252 }
253 
254 
Abort()255 PBoolean PSoundChannel::Abort()
256 {
257   PReadWaitAndSignal mutex(m_baseMutex);
258   return m_baseChannel == NULL || m_baseChannel->Abort();
259 }
260 
261 
SetFormat(unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)262 PBoolean PSoundChannel::SetFormat(unsigned numChannels, unsigned sampleRate, unsigned bitsPerSample)
263 {
264   PReadWaitAndSignal mutex(m_baseMutex);
265   return m_baseChannel != NULL && m_baseChannel->SetFormat(numChannels, sampleRate, bitsPerSample);
266 }
267 
268 
GetChannels() const269 unsigned PSoundChannel::GetChannels() const
270 {
271   PReadWaitAndSignal mutex(m_baseMutex);
272   return m_baseChannel == NULL ? 0 : m_baseChannel->GetChannels();
273 }
274 
275 
GetSampleRate() const276 unsigned PSoundChannel::GetSampleRate() const
277 {
278   PReadWaitAndSignal mutex(m_baseMutex);
279   return m_baseChannel == NULL ? 0 : m_baseChannel->GetSampleRate();
280 }
281 
282 
GetSampleSize() const283 unsigned PSoundChannel::GetSampleSize() const
284 {
285   PReadWaitAndSignal mutex(m_baseMutex);
286   return m_baseChannel == NULL ? 0 : m_baseChannel->GetSampleSize();
287 }
288 
289 
SetBuffers(PINDEX size,PINDEX count)290 PBoolean PSoundChannel::SetBuffers(PINDEX size, PINDEX count)
291 {
292   PReadWaitAndSignal mutex(m_baseMutex);
293   return m_baseChannel != NULL && m_baseChannel->SetBuffers(size, count);
294 }
295 
296 
GetBuffers(PINDEX & size,PINDEX & count)297 PBoolean PSoundChannel::GetBuffers(PINDEX & size, PINDEX & count)
298 {
299   PReadWaitAndSignal mutex(m_baseMutex);
300   return m_baseChannel != NULL && m_baseChannel->GetBuffers(size, count);
301 }
302 
303 
SetVolume(unsigned volume)304 PBoolean PSoundChannel::SetVolume(unsigned volume)
305 {
306   PReadWaitAndSignal mutex(m_baseMutex);
307   return m_baseChannel != NULL && m_baseChannel->SetVolume(volume);
308 }
309 
310 
GetMute(bool & mute)311 PBoolean PSoundChannel::GetMute(bool & mute)
312 {
313   PReadWaitAndSignal mutex(m_baseMutex);
314   return m_baseChannel != NULL && m_baseChannel->GetMute(mute);
315 }
316 
317 
SetMute(bool mute)318 PBoolean PSoundChannel::SetMute(bool mute)
319 {
320   PReadWaitAndSignal mutex(m_baseMutex);
321   return m_baseChannel != NULL && m_baseChannel->SetMute(mute);
322 }
323 
324 
GetVolume(unsigned & volume)325 PBoolean PSoundChannel::GetVolume(unsigned & volume)
326 {
327   PReadWaitAndSignal mutex(m_baseMutex);
328   return m_baseChannel != NULL && m_baseChannel->GetVolume(volume);
329 }
330 
331 
Write(const void * buf,PINDEX len)332 PBoolean PSoundChannel::Write(const void * buf, PINDEX len)
333 {
334   PAssert(activeDirection == Player, PLogicError);
335 
336   if (len == 0)
337     return IsOpen();
338 
339   PReadWaitAndSignal mutex(m_baseMutex);
340   return m_baseChannel != NULL && m_baseChannel->Write(buf, len);
341 }
342 
Write(const void * buf,PINDEX len,const void *)343 PBoolean PSoundChannel::Write(const void * buf, PINDEX len, const void * /*mark*/)
344 {
345   return Write(buf, len);
346 }
347 
GetLastWriteCount() const348 PINDEX PSoundChannel::GetLastWriteCount() const
349 {
350   PReadWaitAndSignal mutex(m_baseMutex);
351   return m_baseChannel != NULL ? m_baseChannel->GetLastWriteCount() : PChannel::GetLastWriteCount();
352 }
353 
354 
PlaySound(const PSound & sound,PBoolean wait)355 PBoolean PSoundChannel::PlaySound(const PSound & sound, PBoolean wait)
356 {
357   PAssert(activeDirection == Player, PLogicError);
358   PReadWaitAndSignal mutex(m_baseMutex);
359   return m_baseChannel != NULL && m_baseChannel->PlaySound(sound, wait);
360 }
361 
362 
PlayFile(const PFilePath & file,PBoolean wait)363 PBoolean PSoundChannel::PlayFile(const PFilePath & file, PBoolean wait)
364 {
365   PAssert(activeDirection == Player, PLogicError);
366   PReadWaitAndSignal mutex(m_baseMutex);
367   return m_baseChannel != NULL && m_baseChannel->PlayFile(file, wait);
368 }
369 
370 
HasPlayCompleted()371 PBoolean PSoundChannel::HasPlayCompleted()
372 {
373   PAssert(activeDirection == Player, PLogicError);
374   PReadWaitAndSignal mutex(m_baseMutex);
375   return m_baseChannel != NULL && m_baseChannel->HasPlayCompleted();
376 }
377 
378 
WaitForPlayCompletion()379 PBoolean PSoundChannel::WaitForPlayCompletion()
380 {
381   PAssert(activeDirection == Player, PLogicError);
382   PReadWaitAndSignal mutex(m_baseMutex);
383   return m_baseChannel != NULL && m_baseChannel->WaitForPlayCompletion();
384 }
385 
386 
Read(void * buf,PINDEX len)387 PBoolean PSoundChannel::Read(void * buf, PINDEX len)
388 {
389   PAssert(activeDirection == Recorder, PLogicError);
390 
391   if (len == 0)
392     return IsOpen();
393 
394   PReadWaitAndSignal mutex(m_baseMutex);
395   return m_baseChannel != NULL && m_baseChannel->Read(buf, len);
396 }
397 
398 
GetLastReadCount() const399 PINDEX PSoundChannel::GetLastReadCount() const
400 {
401   PReadWaitAndSignal mutex(m_baseMutex);
402   return m_baseChannel != NULL ? m_baseChannel->GetLastReadCount() : PChannel::GetLastReadCount();
403 }
404 
405 
RecordSound(PSound & sound)406 PBoolean PSoundChannel::RecordSound(PSound & sound)
407 {
408   PAssert(activeDirection == Recorder, PLogicError);
409   PReadWaitAndSignal mutex(m_baseMutex);
410   return m_baseChannel != NULL && m_baseChannel->RecordSound(sound);
411 }
412 
413 
RecordFile(const PFilePath & file)414 PBoolean PSoundChannel::RecordFile(const PFilePath & file)
415 {
416   PAssert(activeDirection == Recorder, PLogicError);
417   PReadWaitAndSignal mutex(m_baseMutex);
418   return m_baseChannel != NULL && m_baseChannel->RecordFile(file);
419 }
420 
421 
StartRecording()422 PBoolean PSoundChannel::StartRecording()
423 {
424   PAssert(activeDirection == Recorder, PLogicError);
425   PReadWaitAndSignal mutex(m_baseMutex);
426   return m_baseChannel != NULL && m_baseChannel->StartRecording();
427 }
428 
429 
IsRecordBufferFull()430 PBoolean PSoundChannel::IsRecordBufferFull()
431 {
432   PAssert(activeDirection == Recorder, PLogicError);
433   PReadWaitAndSignal mutex(m_baseMutex);
434   return m_baseChannel != NULL && m_baseChannel->IsRecordBufferFull();
435 }
436 
437 
AreAllRecordBuffersFull()438 PBoolean PSoundChannel::AreAllRecordBuffersFull()
439 {
440   PAssert(activeDirection == Recorder, PLogicError);
441   PReadWaitAndSignal mutex(m_baseMutex);
442   return m_baseChannel != NULL && m_baseChannel->AreAllRecordBuffersFull();
443 }
444 
445 
WaitForRecordBufferFull()446 PBoolean PSoundChannel::WaitForRecordBufferFull()
447 {
448   PAssert(activeDirection == Recorder, PLogicError);
449   PReadWaitAndSignal mutex(m_baseMutex);
450   return m_baseChannel != NULL && m_baseChannel->WaitForRecordBufferFull();
451 }
452 
453 
WaitForAllRecordBuffersFull()454 PBoolean PSoundChannel::WaitForAllRecordBuffersFull()
455 {
456   PAssert(activeDirection == Recorder, PLogicError);
457   PReadWaitAndSignal mutex(m_baseMutex);
458   return m_baseChannel != NULL && m_baseChannel->WaitForAllRecordBuffersFull();
459 }
460 
461 
GetDirectionText(Directions dir)462 const char * PSoundChannel::GetDirectionText(Directions dir)
463 {
464   switch (dir) {
465     case Player :
466       return "Playback";
467     case Recorder :
468       return "Recording";
469     case Closed :
470       return "Closed";
471   }
472 
473   return "<Unknown>";
474 }
475 
476 
477 ///////////////////////////////////////////////////////////////////////////
478 
479 #if !defined(_WIN32) && !defined(__BEOS__) && !defined(__APPLE__)
480 
PSound(unsigned channels,unsigned samplesPerSecond,unsigned bitsPerSample,PINDEX bufferSize,const BYTE * buffer)481 PSound::PSound(unsigned channels,
482                unsigned samplesPerSecond,
483                unsigned bitsPerSample,
484                PINDEX   bufferSize,
485                const BYTE * buffer)
486 {
487   encoding = 0;
488   numChannels = channels;
489   sampleRate = samplesPerSecond;
490   sampleSize = bitsPerSample;
491   SetSize(bufferSize);
492   if (buffer != NULL)
493     memcpy(GetPointer(), buffer, bufferSize);
494 }
495 
496 
PSound(const PFilePath & filename)497 PSound::PSound(const PFilePath & filename)
498 {
499   encoding = 0;
500   numChannels = 1;
501   sampleRate = 8000;
502   sampleSize = 16;
503   Load(filename);
504 }
505 
506 
operator =(const PBYTEArray & data)507 PSound & PSound::operator=(const PBYTEArray & data)
508 {
509   PBYTEArray::operator=(data);
510   return *this;
511 }
512 
513 
SetFormat(unsigned channels,unsigned samplesPerSecond,unsigned bitsPerSample)514 void PSound::SetFormat(unsigned channels,
515                        unsigned samplesPerSecond,
516                        unsigned bitsPerSample)
517 {
518   encoding = 0;
519   numChannels = channels;
520   sampleRate = samplesPerSecond;
521   sampleSize = bitsPerSample;
522   formatInfo.SetSize(0);
523 }
524 
525 
Load(const PFilePath &)526 PBoolean PSound::Load(const PFilePath & /*filename*/)
527 {
528   return PFalse;
529 }
530 
531 
Save(const PFilePath &)532 PBoolean PSound::Save(const PFilePath & /*filename*/)
533 {
534   return PFalse;
535 }
536 
537 
Play()538 PBoolean PSound::Play()
539 {
540   return Play(PSoundChannel::GetDefaultDevice(PSoundChannel::Player));
541 }
542 
543 
Play(const PString & device)544 PBoolean PSound::Play(const PString & device)
545 {
546 
547   PSoundChannel channel(device, PSoundChannel::Player);
548   if (!channel.IsOpen())
549     return PFalse;
550 
551   return channel.PlaySound(*this, PTrue);
552 }
553 
554 
PlayFile(const PFilePath & file,PBoolean wait)555 PBoolean PSound::PlayFile(const PFilePath & file, PBoolean wait)
556 {
557   PSoundChannel channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player),
558                         PSoundChannel::Player);
559   if (!channel.IsOpen())
560     return PFalse;
561 
562   return channel.PlayFile(file, wait);
563 }
564 
565 
566 #endif //_WIN32
567 
568 ///////////////////////////////////////////////////////////////////////////
569 
570 static const PConstString NullAudio("Null Audio");
571 
572 class PSoundChannelNull : public PSoundChannel
573 {
574  PCLASSINFO(PSoundChannelNull, PSoundChannel);
575  public:
PSoundChannelNull()576     PSoundChannelNull()
577       : m_sampleRate(0)
578     {
579     }
580 
PSoundChannelNull(const PString & device,PSoundChannel::Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)581     PSoundChannelNull(
582       const PString &device,
583       PSoundChannel::Directions dir,
584       unsigned numChannels,
585       unsigned sampleRate,
586       unsigned bitsPerSample
587     ) : m_sampleRate(0)
588     {
589       Open(device, dir, numChannels, sampleRate, bitsPerSample);
590     }
591 
GetDeviceNames(PSoundChannel::Directions=Player)592     static PStringArray GetDeviceNames(PSoundChannel::Directions = Player)
593     {
594       return NullAudio;
595     }
596 
Open(const PString &,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)597     PBoolean Open(const PString &,
598                   Directions dir,
599                   unsigned numChannels,
600                   unsigned sampleRate,
601                   unsigned bitsPerSample)
602     {
603       activeDirection = dir;
604       return SetFormat(numChannels, sampleRate, bitsPerSample);
605     }
606 
GetName() const607     virtual PString GetName() const
608     {
609       return NullAudio;
610     }
611 
Close()612     PBoolean Close()
613     {
614       m_sampleRate = 0;
615       return true;
616     }
617 
IsOpen() const618     PBoolean IsOpen() const
619     {
620       return m_sampleRate > 0;
621     }
622 
Write(const void *,PINDEX len)623     PBoolean Write(const void *, PINDEX len)
624     {
625       if (m_sampleRate <= 0)
626         return false;
627 
628       lastWriteCount = len;
629       m_Pacing.Delay(len/2*1000/m_sampleRate);
630       return true;
631     }
632 
Read(void * buf,PINDEX len)633     PBoolean Read(void * buf, PINDEX len)
634     {
635       if (m_sampleRate <= 0)
636         return false;
637 
638       memset(buf, 0, len);
639       lastReadCount = len;
640       m_Pacing.Delay(len/2*1000/m_sampleRate);
641       return true;
642     }
643 
SetFormat(unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)644     PBoolean SetFormat(unsigned numChannels,
645                    unsigned sampleRate,
646                    unsigned bitsPerSample)
647     {
648       m_sampleRate = sampleRate;
649       return numChannels == 1 && bitsPerSample == 16;
650     }
651 
GetChannels() const652     unsigned GetChannels() const
653     {
654       return 1;
655     }
656 
GetSampleRate() const657     unsigned GetSampleRate() const
658     {
659       return m_sampleRate;
660     }
661 
GetSampleSize() const662     unsigned GetSampleSize() const
663     {
664       return 16;
665     }
666 
SetBuffers(PINDEX,PINDEX)667     PBoolean SetBuffers(PINDEX, PINDEX)
668     {
669       return true;
670     }
671 
GetBuffers(PINDEX & size,PINDEX & count)672     PBoolean GetBuffers(PINDEX & size, PINDEX & count)
673     {
674       size = 2;
675       count = 1;
676       return true;
677     }
678 
679 protected:
680     unsigned       m_sampleRate;
681     PAdaptiveDelay m_Pacing;
682 };
683 
684 
685 PCREATE_SOUND_PLUGIN(NullAudio, PSoundChannelNull)
686