--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/emulator/emulatorbsp/specific/soundsc_tx.cpp Tue Feb 02 01:39:10 2010 +0200
@@ -0,0 +1,1574 @@
+// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// wins\specific\soundsc_tx.cpp
+// Emulator playback functions for the shared chunk sound driver PDD.
+//
+//
+
+/**
+ @file
+ @internalTechnology
+ @prototype
+*/
+
+#include "winssoundsc.h"
+
+_LIT(KSoundScPddName,"SoundSc.Wins");
+_LIT(KTxSoundDriverThreadName,"SoundDriverPlayThread");
+const TInt KSoundDriverThreadPriority=26; // One less than DFC thread 0
+
+GLDEF_C TInt RateInSamplesPerSecond(TSoundRate aRate)
+ {
+ switch(aRate)
+ {
+ case ESoundRate7350Hz: return(7350);
+ case ESoundRate8000Hz: return(8000);
+ case ESoundRate8820Hz: return(8820);
+ case ESoundRate9600Hz: return(9600);
+ case ESoundRate11025Hz: return(11025);
+ case ESoundRate12000Hz: return(12000);
+ case ESoundRate14700Hz: return(14700);
+ case ESoundRate16000Hz: return(16000);
+ case ESoundRate22050Hz: return(22050);
+ case ESoundRate24000Hz: return(24000);
+ case ESoundRate29400Hz: return(29400);
+ case ESoundRate32000Hz: return(32000);
+ case ESoundRate44100Hz: return(44100);
+ case ESoundRate48000Hz: return(48000);
+ default: return(0);
+ };
+ }
+
+// This utility function is used instead of WaitForSingleObject() for places
+// where the API call is made from either the driver thread or the play
+// thread.If the call is made from the driver thread, the thread is removed from
+// the kernel for the duration of the WaitForSingleObject() call.
+GLDEF_C DWORD WaitForSingleObjectDualThread(HANDLE hHandle,DWORD dwMilliseconds)
+ {
+ TBool epocThread = (NKern::CurrentContext() == NKern::EInterrupt)?EFalse:ETrue;
+ if (epocThread)
+ Emulator::Escape();
+ DWORD dwRet = WaitForSingleObject(hHandle, dwMilliseconds);
+ if (epocThread)
+ Emulator::Reenter();
+ return dwRet;
+ }
+
+/**
+Remove an audio buffer from the head of the specified buffer list.
+Each list holds buffers which are waiting to be transferred. These lists are only used when no audio hardware is present.
+@param aList The pending buffer list from which the buffer should be removed (either record or playback).
+@return A pointer to the audio buffer reoved or NULL if the list is empty.
+*/
+GLDEF_C WAVEHDR* RemoveFromPendingList(WAVEHDR** aList)
+ {
+ WAVEHDR* buffer;
+
+ buffer=aList[0];
+ if (buffer)
+ {
+ // Move any remaining up one in the list.
+ WAVEHDR* b;
+ do
+ {
+ b=aList[1];
+ *aList++=b;
+ }
+ while(b);
+ }
+ return(buffer);
+ }
+
+/**
+Add an audio buffer to the tail of the the specified buffer list.
+Each list holds buffers which are waiting to be transferred. These lists are only used when no audio hardware is present.
+@param aBuffer The audio buffer to be added.
+@param aList The pending buffer list into which the buffer should be added (either record or playback).
+*/
+GLDEF_C void AddToPendingList(WAVEHDR* aBuffer,WAVEHDR** aList)
+ {
+ while (*aList)
+ aList++;
+ *aList=aBuffer;
+ }
+
+/**
+The thread function for the play windows thread.
+This function is always executed in windows thread context.
+*/
+LOCAL_C TUint PlayThreadFunction(DWinsSoundScTxPdd *aSoundPdd)
+ {
+ aSoundPdd->PlayThread();
+ return 0;
+ }
+
+/**
+The waveform output callback function. This can receive the following messages:-
+WOM_OPEN when the output device is opened, WOM_CLOSE when the output device is closed,
+and WOM_DONE each time a data block play transfer is completed (i.e. completion of waveOutWrite).
+This function is always executed in windows thread context.
+*/
+LOCAL_C void CALLBACK WaveOutProc(HWAVEOUT /*hwo*/, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD /*dwParam2*/)
+ {
+ if (uMsg == WOM_DONE)
+ {
+ DWinsSoundScTxPdd* pdd = (DWinsSoundScTxPdd*)dwInstance;
+ pdd->WaveOutProc((WAVEHDR*)dwParam1);
+ }
+ }
+
+THostLock::THostLock() : iLocked(EFalse)
+ {
+ Lock();
+ }
+
+THostLock::THostLock(TBool /*aLock*/) : iLocked(EFalse)
+ {
+ }
+
+THostLock::~THostLock()
+ {
+ if (iLocked)
+ Unlock();
+ }
+
+void THostLock::Lock()
+ {
+ __ASSERT_DEBUG(!iLocked, PANIC());
+ Emulator::Lock();
+ iLocked = ETrue;
+ }
+
+void THostLock::Unlock()
+ {
+ __ASSERT_DEBUG(iLocked, PANIC());
+ Emulator::Unlock();
+ iLocked = EFalse;
+ }
+
+TCondHostLock::TCondHostLock() : THostLock(EFalse)
+ {
+ iEpocThread = (NKern::CurrentContext() == NKern::EInterrupt)?EFalse:ETrue;
+ Lock();
+ }
+
+void TCondHostLock::Lock()
+ {
+ if (iEpocThread)
+ THostLock::Lock();
+ }
+
+void TCondHostLock::Unlock()
+ {
+ if (iEpocThread)
+ THostLock::Unlock();
+ }
+
+/**
+Standard export function for PDDs. This creates a DPhysicalDevice derived object,
+in this case - DWinsSoundScPddFactory.
+*/
+DECLARE_STANDARD_PDD()
+ {
+ return new DWinsSoundScPddFactory;
+ }
+
+/**
+Constructor for the sound PDD factory class.
+*/
+DWinsSoundScPddFactory::DWinsSoundScPddFactory()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScPddFactory::DWinsSoundScPddFactory"));
+
+// iDfcQ=NULL;
+
+ // Support units KSoundScTxUnit0 & KSoundScRxUnit0.
+ iUnitsMask=(1<<KSoundScRxUnit0)|(1<<KSoundScTxUnit0);
+
+ // Set version number for this device.
+ iVersion=RSoundSc::VersionRequired();
+ }
+
+/**
+Destructor for the sound PDD factory class.
+*/
+DWinsSoundScPddFactory::~DWinsSoundScPddFactory()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScPddFactory::~DWinsSoundScPddFactory"));
+
+ // Destroy the kernel thread.
+ if (iDfcQ)
+ iDfcQ->Destroy();
+ }
+
+/**
+Second stage constructor for the sound PDD factory class.
+This must at least set a name for the driver object.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScPddFactory::Install()
+ {
+ TInt r=KErrNone;
+ if (iDfcQ==NULL)
+ {
+ // Create a new sound driver DFC queue (and associated kernel thread).
+ r=Kern::DynamicDfcQCreate(iDfcQ,KSoundDriverThreadPriority,KTxSoundDriverThreadName);
+ }
+
+ if (r==KErrNone)
+ {
+ r=SetName(&KSoundScPddName); // Set the name of the driver object
+ }
+
+ __KTRACE_SND(Kern::Printf("<DWinsSoundScPddFactory::Install - %d",r));
+ return(r);
+ }
+
+/**
+Returns the PDD's capabilities. This is not used by the Symbian OS device driver framework
+or by the LDD.
+@param aDes A descriptor to write capabilities information into.
+*/
+void DWinsSoundScPddFactory::GetCaps(TDes8& /*aDes*/) const
+ {}
+
+/**
+Called by the kernel's device driver framework to check if this PDD is suitable for use
+with a logical channel.
+This is called in the context of the client thread which requested the creation of a logical
+channel - through a call to RBusLogicalChannel::DoCreate().
+The thread is in a critical section.
+@param aUnit The unit argument supplied by the client to RBusLogicalChannel::DoCreate().
+@param aInfo The info argument supplied by the client to RBusLogicalChannel::DoCreate() - not used.
+@param aVer The version number of the logical channel which will use this physical channel.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScPddFactory::Validate(TInt aUnit, const TDesC8* /*aInfo*/, const TVersion& aVer)
+ {
+ // Check that the version specified is compatible.
+ if (!Kern::QueryVersionSupported(RSoundSc::VersionRequired(),aVer))
+ return(KErrNotSupported);
+
+ // Check the unit number is compatible
+ if (aUnit!=KSoundScTxUnit0 && aUnit!=KSoundScRxUnit0)
+ return(KErrNotSupported);
+
+ return(KErrNone);
+ }
+
+/**
+Called by the kernel's device driver framework to create a physical channel object.
+This is called in the context of the client thread which requested the creation of a logical
+channel - through a call to RBusLogicalChannel::DoCreate().
+The thread is in a critical section.
+@param aChannel Set by this function to point to the created physical channel object.
+@param aUnit The unit argument supplied by the client to RBusLogicalChannel::DoCreate().
+@param aInfo The info argument supplied by the client to RBusLogicalChannel::DoCreate().
+@param aVer The version number of the logical channel which will use this physical channel.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScPddFactory::Create(DBase*& aChannel, TInt aUnit, const TDesC8* /*anInfo*/, const TVersion& /*aVer*/)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScPddFactory::Create"));
+
+ // Create the appropriate PDD channel object.
+ TInt r=KErrNoMemory;
+ if (aUnit==KSoundScRxUnit0)
+ {
+ DWinsSoundScRxPdd* pD=new DWinsSoundScRxPdd;
+ aChannel=pD;
+ if (pD)
+ r=pD->DoCreate(this);
+ }
+
+ else
+ {
+ DWinsSoundScTxPdd* pD=new DWinsSoundScTxPdd;
+ aChannel=pD;
+ if (pD)
+ r=pD->DoCreate(this);
+ }
+ return(r);
+ }
+
+/**
+Constructor for the WINS shared chunk playback PDD.
+This function is always executed in driver thread context.
+*/
+DWinsSoundScTxPdd::DWinsSoundScTxPdd()
+ : iDfc(DWinsSoundScTxPdd::PlayDfc,this,2)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::DWinsSoundScTxPdd"));
+
+// iVolume=0;
+// iDriverThreadSem=0;
+// iPlayThread=0;
+// iPlayThreadMutex=0;
+// iPlayThreadSem=0;
+// iStopSemaphore=0;
+// iDeathSemaphore=0;
+// iPlayCommand=ESendData;
+// iPlayCommandArg0=0;
+// iPlayCommandArg1=0;
+// iPendingPlay=0;
+// iPlayThreadError=0;
+// iWaveformBufMgr=NULL;
+// iCompletedPlayBufHdrMask=0;
+// iPlayBufferSize=0;
+// iNoHardware=EFalse;
+// iPlayTimerEvent=0;
+// iTimerID=0;
+// iTimerActive=EFalse;
+// iWinWaveVolume=0;
+ }
+
+/**
+Destructor for the WINS shared chunk playback PDD.
+This function is always executed in driver thread context.
+*/
+DWinsSoundScTxPdd::~DWinsSoundScTxPdd()
+ {
+ // If the Windows thread started up successfully, signal it to shut down and wait for it to do so
+ if (iPlayThreadRunning)
+ {
+ // Signal the windows thread to close down the play device and exit the windows thread.
+ iDeathSemaphore = CreateSemaphore(NULL, 0, 2, NULL);
+ PlayThreadCommand(EExit);
+
+ // Wait for the play thread to terminate.
+ if (iDeathSemaphore)
+ {
+ Emulator::Escape();
+ WaitForSingleObject(iDeathSemaphore, INFINITE);
+ Emulator::Reenter();
+
+ __HOST_LOCK;
+ CloseHandle(iDeathSemaphore);
+ }
+ }
+
+ if (iPlayTimerEvent)
+ CloseHandle(iPlayTimerEvent);
+ if (iPlayThreadSem)
+ CloseHandle(iPlayThreadSem);
+ if (iPlayThread)
+ CloseHandle(iPlayThread);
+ if (iDriverThreadSem)
+ CloseHandle(iDriverThreadSem);
+
+ if (iWaveformBufMgr)
+ delete iWaveformBufMgr;
+ }
+
+/**
+Second stage constructor for the WINS shared chunk playback PDD.
+Note that this constructor is called before the second stage constructor for the LDD so it is not
+possible to call methods on the LDD here.
+This function is always executed in driver thread context.
+@param aPhysicalDevice A pointer to the factory class that is creating this PDD.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::DoCreate(DWinsSoundScPddFactory* aPhysicalDevice)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::DoCreate"));
+
+ iPhysicalDevice=aPhysicalDevice;
+
+ // Set up the correct DFC queue.
+ iDfc.SetDfcQ(iPhysicalDevice->iDfcQ);
+
+ SetCaps(); // Setup the capabilities of this device.
+
+ // Setup the default audio configuration
+ iSoundConfig.iChannels=2;
+ iSoundConfig.iRate=ESoundRate48000Hz;
+ iSoundConfig.iEncoding=ESoundEncoding16BitPCM;
+ iSoundConfig.iDataFormat=ESoundDataFormatInterleaved;
+
+ __HOST_LOCK;
+
+ // Query the waveform device capabilities using the default device identifier in order
+ // to check if there is a functioning waveform device present. Note that some versions of
+ // Windows (such as Windows Server 2003) will actually return MMSYSERR_NOERROR when this is
+ // called, even if there is no waveform device present, so we have a further check in
+ // when waveOutOpen() is called
+ WAVEOUTCAPS waveOutCaps;
+ MMRESULT res = waveOutGetDevCaps(WAVE_MAPPER,&waveOutCaps,sizeof(WAVEOUTCAPS));
+#ifdef FORCE_NO_HARDWARE
+ res=MMSYSERR_NOERROR+1;
+#endif
+ if (res != MMSYSERR_NOERROR)
+ iNoHardware = ETrue;
+
+ __HOST_LOCK_OFF;
+
+ // Create the windows waveform audio buffer manager.
+ iWaveformBufMgr=new TWaveformBufMgr(ESoundDirPlayback,!iNoHardware);
+ if (!iWaveformBufMgr)
+ return(KErrNoMemory);
+
+ // Create the driver thread semaphore.
+ iDriverThreadSem = CreateSemaphore(NULL,0,0x7fffffff,NULL);
+ if (!iDriverThreadSem)
+ return(KErrNoMemory);
+
+ // Create the play windows thread.
+ if ((iPlayThread=CreateWin32Thread(EThreadEvent,(LPTHREAD_START_ROUTINE)PlayThreadFunction,(void *)this, FALSE))==NULL)
+ return(KErrNoMemory);
+ SetThreadPriority(iPlayThread,THREAD_PRIORITY_HIGHEST);
+ __ASSERT_ALWAYS(ResumeThread(iPlayThread) != 0xffffffff, PANIC()); // Windows Unexpected Error
+
+ // Wait to be notified of successful thread initialization
+ Emulator::Escape();
+ WaitForSingleObject(iDriverThreadSem,INFINITE);
+ Emulator::Reenter();
+
+ // If the Windows thread started up successfully, indicate this fact so that when shutting down we know
+ // to signal to the thread to exit
+ if (iPlayThreadError == KErrNone)
+ iPlayThreadRunning = ETrue;
+
+ return(iPlayThreadError);
+ }
+
+/**
+Called from the LDD to return the DFC queue to be used by this device.
+This function is always executed in driver thread context.
+@return The DFC queue to use.
+*/
+TDfcQue* DWinsSoundScTxPdd::DfcQ(TInt /*aUnit*/)
+ {
+ return(iPhysicalDevice->iDfcQ);
+ }
+
+/**
+Called from the LDD to return the shared chunk create information to be used by this device.
+This function is always executed in driver thread context.
+@param aChunkCreateInfo A chunk create info. object to be to be filled with the settings
+ required for this device.
+*/
+void DWinsSoundScTxPdd::GetChunkCreateInfo(TChunkCreateInfo& aChunkCreateInfo)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::GetChunkCreateInfo"));
+
+ aChunkCreateInfo.iType=TChunkCreateInfo::ESharedKernelMultiple;
+ aChunkCreateInfo.iMapAttr=0;
+ aChunkCreateInfo.iOwnsMemory=ETrue; // Using RAM pages.
+ aChunkCreateInfo.iDestroyedDfc=NULL; // No chunk destroy DFC.
+ }
+
+/**
+Called from the LDD to return the capabilities of this device.
+This function is always executed in driver thread context.
+@param aCapsBuf A packaged TSoundFormatsSupportedV02 object to be filled with the play
+ capabilities of this device. This descriptor is in kernel memory and can be accessed directly.
+@see TSoundFormatsSupportedV02.
+*/
+void DWinsSoundScTxPdd::Caps(TDes8& aCapsBuf) const
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::Caps"));
+
+ // Copy iCaps back.
+ TPtrC8 ptr((const TUint8*)&iCaps,sizeof(iCaps));
+ aCapsBuf.FillZ(aCapsBuf.MaxLength());
+ aCapsBuf=ptr.Left(Min(ptr.Length(),aCapsBuf.MaxLength()));
+ }
+
+/**
+Called from the LDD to return the maximum transfer length in bytes that this device can support in a single data transfer.
+@return The maximum transfer length in bytes.
+*/
+TInt DWinsSoundScTxPdd::MaxTransferLen() const
+ {
+ return(KWinsMaxAudioTransferLen); // 32K
+ }
+
+/**
+Called from the LDD to power up the sound device.
+This function is always executed in driver thread context.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::PowerUp()
+ {
+ return(KErrNone);
+ }
+
+/**
+Called from the LDD to configure or reconfigure the device using the the configuration supplied.
+This function is always executed in driver thread context.
+@param aConfigBuf A packaged TCurrentSoundFormatV02 object which contains the new configuration settings.
+ This descriptor is in kernel memory and can be accessed directly.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+@see TCurrentSoundFormatV02.
+*/
+TInt DWinsSoundScTxPdd::SetConfig(const TDesC8& aConfigBuf)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::SetConfig"));
+
+ // Cannot change the configuration while the device is open and playing. (LDD should prevent
+ // this anyway but better safe than sorry).
+ if (iPlayDeviceHandle)
+ return(KErrInUse);
+
+ // Save the current settings so we can restore them if there is a problem with the new ones.
+ TCurrentSoundFormatV02 saved=iSoundConfig;
+
+ // Read the new configuration from the LDD.
+ TPtr8 ptr((TUint8*)&iSoundConfig,sizeof(iSoundConfig));
+ Kern::InfoCopy(ptr,aConfigBuf);
+
+ // Open the play device with the new settings to check they are supported. Then close it
+ // again - don't leave it open yet.
+ TInt r = CreatePlayDevice(ESetConfig);
+ if (r==KErrNone)
+ ClosePlayDevice();
+ else
+ iSoundConfig=saved; // Restore the previous settings
+
+ return(r);
+ }
+
+const TInt KdBToLinear[KSoundMaxVolume+1] =
+ {
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
+ 0x0000,0x0000,0x0000,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,
+ 0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0202,0x0202,0x0202,0x0202,0x0202,0x0202,0x0202,0x0202,0x0202,0x0303,
+ 0x0303,0x0303,0x0303,0x0303,0x0303,0x0404,0x0404,0x0404,0x0404,0x0505,0x0505,0x0505,0x0505,0x0606,0x0606,0x0606,
+ 0x0707,0x0707,0x0808,0x0808,0x0909,0x0909,0x0a0a,0x0a0a,0x0b0b,0x0b0b,0x0c0c,0x0d0d,0x0e0e,0x0e0e,0x0f0f,0x1010,
+ 0x1111,0x1212,0x1313,0x1414,0x1616,0x1717,0x1818,0x1a1a,0x1b1b,0x1d1d,0x1e1e,0x2020,0x2222,0x2424,0x2626,0x2929,
+ 0x2b2b,0x2e2e,0x3030,0x3333,0x3636,0x3939,0x3d3d,0x4040,0x4444,0x4848,0x4c4c,0x5151,0x5656,0x5b5b,0x6060,0x6666,
+ 0x6c6c,0x7272,0x7979,0x8080,0x8888,0x9090,0x9898,0xa2a2,0xabab,0xb5b5,0xc0c0,0xcbcb,0xd7d7,0xe4e4,0xf2f2,0xffff,
+ };
+
+/**
+Called from the LDD to set the play volume.
+This function is always executed in driver thread context.
+@param aVolume The play volume to be set - a value in the range 0 to 255. The value 255 equates
+ to the maximum volume and each value below this equates to a 0.5dB step below it.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::SetVolume(TInt aVolume)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::SetVolume"));
+
+ // The documentation for waveOutSetVolume() says: "Volume settings are interpreted logarithmically" but evidence suggests these are really
+ // interpreted linearly. Hence, use a lookup table to convert from dB to linear. At the same time convert from an 8-bit wide range to a
+ // 16-bit wide range.
+ iVolume=KdBToLinear[aVolume];
+
+ // Only update the volume on the output device itself if this is currently open.
+ if (iPlayDeviceHandle)
+ waveOutSetVolume(iPlayDeviceHandle, iVolume | (iVolume << 16));
+ return(KErrNone);
+ }
+
+/**
+Called from the LDD to prepare the audio device for playback.
+This function is always executed in driver thread context.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::StartTransfer()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::StartTransfer"));
+
+ // Convert the enum representing the current sample rate into an integer
+ TInt samplesPerSecond=RateInSamplesPerSecond(iSoundConfig.iRate);
+ if (samplesPerSecond==0)
+ return(KErrNotSupported);
+
+ // Now convert the sample rate into the number of bytes per second and save for later use
+ iBytesPerSecond=samplesPerSecond;
+ if (iSoundConfig.iChannels==2)
+ iBytesPerSecond*=2;
+ if (iSoundConfig.iEncoding==ESoundEncoding16BitPCM)
+ iBytesPerSecond*=2;
+
+ // Open the play device with the current settings.
+ iPendingPlay=0;
+ iCompletedPlayBufHdrMask=0; // Reset the completion status mask
+ TInt r = CreatePlayDevice(EStartTransfer);
+ return(r);
+ }
+
+/**
+Called from the LDD to initiate the playback of a portion of data to the audio device.
+When the transfer is complete, the PDD signals this event using the LDD function PlayCallback().
+This function is always executed in driver thread context.
+@param aTransferID A value assigned by the LDD to allow it to uniquely identify a particular transfer fragment.
+@param aLinAddr The linear address within the shared chunk of the start of the data to be played.
+@param aPhysAddr The physical address within the shared chunk of the start of the data to be played.
+@param aNumBytes The number of bytes to be played.
+@return KErrNone if the transfer has been initiated successfully;
+ KErrNotReady if the device is unable to accept the transfer for the moment;
+ otherwise one of the other system-wide error codes.
+*/
+TInt DWinsSoundScTxPdd::TransferData(TUint aTransferID,TLinAddr aLinAddr,TPhysAddr /*aPhysAddr*/,TInt aNumBytes)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::TransferData(ID:%xH)",aTransferID));
+
+ // Check that we can accept the request
+ if (aNumBytes>KWinsMaxAudioTransferLen)
+ return(KErrArgument);
+ if (iPendingPlay>=iWaveformBufMgr->iNumWaveformBufs) // LDD may issue multiple data transfers per buffer.
+ return(KErrNotReady);
+
+ // Signal the windows thread to initiate the playback of a buffers worth of data to the waveout device.
+ iPendingPlay++;
+ PlayThreadCommand(ESendData,aTransferID,aLinAddr,aNumBytes);
+
+ // Although the windows thread runs at a higher priority, its not safe to assume we will always get pre-empted at this
+ // point while the the higher priority thread processes and completes the request. Instead we need to wait until it
+ // signals back completion of the command.
+ Emulator::Escape();
+ WaitForSingleObject(iDriverThreadSem,INFINITE);
+ Emulator::Reenter();
+
+ return(iPlayThreadError);
+ }
+
+/**
+Called from the LDD to terminate the playback of a data to the device and to release any resources necessary for playback.
+This is called soon after the last pending play request from the client has been completed. Once this function had been
+called, the LDD will not issue any further TransferData() commands without first issueing a StartTransfer() command.
+This function is always executed in driver thread context.
+*/
+void DWinsSoundScTxPdd::StopTransfer()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::StopTransfer"));
+
+ // Signal the windows thread to stop and close the device.
+ iStopSemaphore = CreateSemaphore(NULL, 0, 1, NULL);
+ PlayThreadCommand(EStop);
+
+ // Wait for the windows thread to stop and close the device.
+ if (iStopSemaphore)
+ {
+ Emulator::Escape();
+ WaitForSingleObject(iStopSemaphore, INFINITE);
+ Emulator::Reenter();
+
+ __HOST_LOCK;
+ CloseHandle(iStopSemaphore);
+ iStopSemaphore = NULL;
+ }
+ iPendingPlay=0;
+ iCompletedPlayBufHdrMask=0; // Reset the completion status mask
+
+ // Make sure the DFC is not queued.
+ iDfc.Cancel();
+ }
+
+/**
+Called from the LDD to halt the playback of data to the sound device but not to release any resources necessary for
+playback.
+If possible, any active transfer should be suspended in such a way that it can be resumed later - starting from next
+sample following the one last played.
+This function is always executed in driver thread context.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::PauseTransfer()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::PauseTransfer"));
+
+ // Signal the windows thread to pause playback on the waveout device.
+ PlayThreadCommand(EPause);
+
+ return(KErrNone);
+ }
+
+/**
+Called from the LDD to resume the playback of data to the sound device following a request to halt playback.
+If possible, any transfer which was active when the device was halted should be resumed - starting from next sample
+following the one last played. Once complete, it should be reported using PlayCallback()
+as normal.
+This function is always executed in driver thread context.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::ResumeTransfer()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::ResumeTransfer"));
+
+ // Signal the windows thread to resume playback on the waveout device.
+ PlayThreadCommand(EResume);
+
+ return(KErrNone);
+ }
+
+/**
+Called from the LDD to power down the sound device.
+This function is always executed in driver thread context.
+*/
+void DWinsSoundScTxPdd::PowerDown()
+ {}
+
+/**
+Called from the LDD to handle a custom configuration request.
+@param aFunction A number identifying the request.
+@param aParam A 32-bit value passed to the driver. Its meaning depends on the request.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+*/
+TInt DWinsSoundScTxPdd::CustomConfig(TInt /*aFunction*/,TAny* /*aParam*/)
+ {
+ return(KErrNotSupported);
+ }
+
+/**
+Called from the LDD to find out how many microseconds of data have been played. This is called
+in the context of the DFC thread.
+@param aTimeTransferred A reference to a variable into which to place the number of microseconds of audio.
+@param aStatus The current status of this channel
+@return KErrNone if time is valid or KErrNotSupported.
+*/
+TInt DWinsSoundScTxPdd::TimeTransferred(TInt64& aTimePlayed, TInt /*aStatus*/)
+ {
+ TInt r=KErrGeneral;
+ TInt64 ms=0;
+
+ MMTIME time;
+ time.wType=TIME_BYTES;
+
+ if(iPlayDeviceHandle == 0)
+ {
+ // Have not started playback yet, or have stopped.
+ aTimePlayed = 0;
+ return KErrNone;
+ }
+
+ // If no hardware is present then we need to simulate the amount of time that has passed during
+ // playback. The # of microseconds can be found in the iSimulatedUSecPlayed member, but this is
+ // only updated when the emulation timer triggers. To improve the accuracy of the time returned,
+ // we use the Windows system timer to determine the # of milliseconds that have passed since the
+ // last time the timer triggered
+ if(iNoHardware)
+ {
+ // Determine the # of milliseconds that have passed since the last timer triggered
+ DWORD currentTime = timeGetTime();
+ DWORD timeSinceLastEvent = (currentTime - iLastTimerEventTime);
+
+ // If playback is paused then the Windows system timer will continue, so take this into
+ // account and subtract the # of milliseconds we have been paused for
+ if (iPauseTime)
+ timeSinceLastEvent -= (currentTime - iPauseTime);
+
+ // Clamp the resulting value to the duration of the timer, to prevent the millisecond count
+ // going backwards if Windows is busy and latency becomes an issue
+ if (timeSinceLastEvent > iSimulatedMsecDuration)
+ timeSinceLastEvent = iSimulatedMsecDuration;
+
+ // Now we know the value of the time passed down to a millisecond accuracy
+ aTimePlayed = (iSimulatedUSecPlayed + (timeSinceLastEvent * 1000));
+ return KErrNone;
+ }
+
+ // Get the number of bytes played by the Windows audio system
+ if (waveOutGetPosition(iPlayDeviceHandle,&time,sizeof(time))==MMSYSERR_NOERROR)
+ {
+ // If requesting the number of bytes played is not supported, wType will be
+ // changed to what was actually returned, so check for this and don't continue
+ // if we got anything other than bytes
+ if (time.wType==TIME_BYTES)
+ {
+ // It's all good. Convert the number of bytes played into microseconds and return it
+ ms=((time.u.cb/iBytesPerSecond)*1000);
+ TUint remainder=(time.u.cb%iBytesPerSecond);
+ ms+=((remainder*1000)/iBytesPerSecond);
+ ms*=1000;
+ aTimePlayed=ms;
+ r=KErrNone;
+ }
+ }
+
+ return(r);
+ }
+
+/**
+Constructor for the windows playback waveform audio buffer abstraction.
+*/
+TWaveformAudioBuf::TWaveformAudioBuf()
+ {
+ memclr(&iBufHdr,sizeof(WAVEHDR));
+ iIsPrepared=EFalse;
+ iIsInUse=EFalse;
+ iWaveformBufMgr=NULL;
+ iBufNum=0;
+ }
+
+/**
+Prepare the waveform audio buffer for playback or record.
+@param aBufAddr A pointer to the address of the waveform buffer.
+@param aBufLength The length in bytes of the waveform buffer.
+@param aDeviceHandle The handle to the waveform audio device.
+*/
+void TWaveformAudioBuf::Prepare(char* aBufAddr,TInt aBufLength,TInt aDeviceHandle)
+ {
+ iBufHdr.lpData = aBufAddr;
+ iBufHdr.dwBufferLength = aBufLength;
+ iBufHdr.dwBytesRecorded = 0;
+ iBufHdr.dwUser = iBufNum;
+ if (iWaveformBufMgr->iDirection==ESoundDirPlayback)
+ iBufHdr.dwFlags = WHDR_DONE; // Initialise all to done so we can check for underflow.
+ else
+ iBufHdr.dwFlags = 0;
+ iBufHdr.dwLoops = 0;
+ iBufHdr.lpNext = NULL;
+ iBufHdr.reserved = 0;
+
+ if (iWaveformBufMgr->iIsHardware)
+ {
+ if (iWaveformBufMgr->iDirection==ESoundDirPlayback)
+ DoPrepareOut((HWAVEOUT)aDeviceHandle);
+ else
+ DoPrepareIn((HWAVEIN)aDeviceHandle);
+ }
+ iIsPrepared=ETrue;
+ iIsInUse=EFalse;
+ }
+
+/**
+Cleanup the preparation performed when the waveform audio buffer was prepared for playback or record.
+@param aDeviceHandle The handle to the waveform audio device.
+*/
+void TWaveformAudioBuf::Unprepare(TInt aDeviceHandle)
+ {
+ if (iWaveformBufMgr->iIsHardware && iIsPrepared)
+ {
+ if (iWaveformBufMgr->iDirection==ESoundDirPlayback)
+ DoUnprepareOut((HWAVEOUT)aDeviceHandle);
+ else
+ DoUnprepareIn((HWAVEIN)aDeviceHandle);
+ }
+ iIsPrepared=EFalse;
+ iIsInUse=EFalse;
+ }
+
+/**
+Prepare the waveform audio buffer for playback.
+@param aPlayDeviceHandle The handle to the waveform audio output device.
+*/
+void TWaveformAudioBuf::DoPrepareOut(HWAVEOUT aPlayDeviceHandle)
+ {
+ MMRESULT res = waveOutPrepareHeader(aPlayDeviceHandle,&iBufHdr,sizeof(WAVEHDR));
+ __KTRACE_SND(Kern::Printf(" waveOutPrepareHeader(BufNo:%d Pos:%x Len:%d)-%d",iBufNum,iBufHdr.lpData,iBufHdr.dwBufferLength,res));
+ __ASSERT_ALWAYS(res==MMSYSERR_NOERROR,Kern::Fault("DWinsSoundScTxPddWOPH", res)); //WaveOutPrepareHeader error.
+ }
+
+/**
+Cleanup the preparation performed when the waveform audio buffer was prepared for playback.
+@param aPlayDeviceHandle The handle to the waveform audio output device.
+*/
+void TWaveformAudioBuf::DoUnprepareOut(HWAVEOUT aPlayDeviceHandle)
+ {
+ MMRESULT res = waveOutUnprepareHeader(aPlayDeviceHandle,&iBufHdr,sizeof(WAVEHDR));
+ __KTRACE_SND(Kern::Printf(" waveOutUnprepareHeader(BufNo:%d)-%d",iBufNum,res));
+ __ASSERT_ALWAYS(res==MMSYSERR_NOERROR,Kern::Fault("DWinsSoundScTxPddWOUH",res)); //WaveOutUnprepareHeader error.
+ }
+
+/**
+Constructor for the waveform audio buffer manager.
+*/
+TWaveformBufMgr::TWaveformBufMgr(TSoundDirection aDirection,TBool aIsHardware)
+ : iDirection(aDirection), iIsHardware(aIsHardware)
+ {
+ iWaveformAudioBuf=NULL;
+ iNumWaveformBufs=0;
+ iWaveformBufSize=0;
+ iPendingBufList=NULL;
+ }
+
+/**
+Destructor for the waveform audio buffer manager.
+*/
+TWaveformBufMgr::~TWaveformBufMgr()
+ {
+ if (iWaveformAudioBuf)
+ delete[] iWaveformAudioBuf;
+ if (iPendingBufList)
+ delete iPendingBufList;
+ }
+
+/*
+Re-allocate the number of waveform audio buffers that are available for data transfer according to the
+current shared chunk configuration. Then, for each buffer that exists within the shared chunk, prepare one of the waveform audio
+buffers just created so that it is aligned with the shared chunk buffer.
+@param aBufConfig A buffer configuration object specifying the geometry of the current shared chunk buffer configuration.
+@param aChunkBase The address in the kernel process for the start of the shared chunk.
+@param aDeviceHandle The handle to the waveform audio device.
+@return KErrNone if successful, otherwise one of the other system wide error codes.
+@pre The thread must be in a critical section.
+*/
+TInt TWaveformBufMgr::ReAllocAndUpdate(TSoundSharedChunkBufConfig* aBufConfig,TLinAddr aChunkBase,TInt aDeviceHandle)
+ {
+ __KTRACE_SND(Kern::Printf(">TWaveformBufMgr::ReAllocAndUpdate"));
+
+ // Check if the number of windows waveform audio buffers that are required has changed.
+ TInt required=Max(aBufConfig->iNumBuffers,KMinWaveHdrBufCount);
+ if (iNumWaveformBufs != required)
+ {
+ // The number has changed. First, re-allocate the required number of windows waveform data blocks.
+ if (iWaveformAudioBuf)
+ {
+ delete[] iWaveformAudioBuf;
+ iWaveformAudioBuf=NULL;
+ }
+
+ // If we are emulating an audio device then delete any pending buffer list previously created.
+ if (!iIsHardware && iPendingBufList)
+ {
+ delete iPendingBufList;
+ iPendingBufList=NULL;
+ }
+ iNumWaveformBufs = 0;
+
+ iWaveformAudioBuf=new TWaveformAudioBuf[required];
+ if (!iWaveformAudioBuf)
+ return(KErrNoMemory);
+ for (TInt i=0; i<required ; i++)
+ {
+ iWaveformAudioBuf[i].SetWaveformBufMgr(this);
+ iWaveformAudioBuf[i].SetBufNum(i);
+ }
+
+ // If we are emulating an audio device then allocate a new pending buffer list.
+ if (!iIsHardware)
+ {
+ iPendingBufList=(WAVEHDR**)Kern::AllocZ((required+1)*sizeof(WAVEHDR*));
+ if (!iPendingBufList)
+ return(KErrNoMemory);
+ }
+ iNumWaveformBufs = required;
+ }
+
+ // The most common situation is that request start offsets coincide with the start of one of the
+ // shared chunk buffers. Hence, begin by preparing a windows waveform audio buffer for each shared chunk
+ // buffer - aligned with this start address.
+ TInt* bufOffsetList=&aBufConfig->iBufferOffsetListStart; // The buffer offset list.
+ for (TInt i=0; i<aBufConfig->iNumBuffers ; i++)
+ {
+ char* bufAddr=(char*)(aChunkBase+bufOffsetList[i]);
+ iWaveformAudioBuf[i].Prepare(bufAddr,aBufConfig->iBufferSizeInBytes,aDeviceHandle);
+ }
+ iWaveformBufSize=aBufConfig->iBufferSizeInBytes;
+ return(KErrNone);
+ }
+
+/**
+Acquire an appropriate waveform audio buffer to be used either to send a data block to the waveform output device or
+receive a data block from the waveform input device.
+This function is always executed in windows thread context.
+@param aStartAddress A pointer to the address of the data block to be played/recorded.
+@param aBufLength The length in bytes of the data block to be played/recorded.
+@param aDeviceHandle The handle to the waveform audio device.
+@return A pointer to an appropriate waveform audio buffer to be used to transfer the data block.
+*/
+TWaveformAudioBuf* TWaveformBufMgr::AcquireBuf(char* aStartAddress,TInt aBufLength,TInt aDeviceHandle)
+ {
+ // See if there's a appropriate waveform audio buffer already prepared. We only need to worry about the start address,
+ // the length can be adjusted later if necessary.
+ TInt i;
+ for (i=0; i<iNumWaveformBufs ; i++)
+ {
+ if (iWaveformAudioBuf[i].iIsPrepared && iWaveformAudioBuf[i].iBufHdr.lpData==aStartAddress && !iWaveformAudioBuf[i].iIsInUse)
+ break;
+ }
+ if (i>=iNumWaveformBufs)
+ {
+ // None already prepared which are appropriate so prepare one now. See if there are any not yet prepared.
+ for (i=0; i<iNumWaveformBufs ; i++)
+ {
+ if (!iWaveformAudioBuf[i].iIsPrepared)
+ {
+ iWaveformAudioBuf[i].Prepare(aStartAddress,aBufLength,aDeviceHandle);
+ break;
+ }
+ }
+
+ // All are prepared already so we need to re-prepare one specially.
+ if (i>=iNumWaveformBufs)
+ {
+ for (i=0; i<iNumWaveformBufs ; i++)
+ {
+ if (!iWaveformAudioBuf[i].iIsInUse)
+ {
+ iWaveformAudioBuf[i].Unprepare(aDeviceHandle);
+ iWaveformAudioBuf[i].Prepare(aStartAddress,aBufLength,aDeviceHandle);
+ break;
+ }
+ }
+ __ASSERT_ALWAYS(i>=0,PANIC());
+ }
+ }
+
+ __KTRACE_SND(Kern::Printf("<TWaveformBufMgr:AcquireBuf - BufNo:%d",i));
+ iWaveformAudioBuf[i].iIsInUse=ETrue;
+ return(&iWaveformAudioBuf[i]);
+ }
+
+/**
+The waveform output callback function to handle data block transfer completion.
+This function is always executed in windows thread context.
+@param aHdr A pointer to the header for the waveform audio buffer just transferred.
+*/
+void DWinsSoundScTxPdd::WaveOutProc(WAVEHDR* aHdr)
+ {
+ TInt waveBufId=aHdr->dwUser; // Work out which waveform audio buffer is completing.
+
+ StartOfInterrupt();
+ iCompletedPlayBufHdrMask|=(1<<waveBufId); // Update the completion status mask
+ iDfc.Add(); // Queue PlayDfc().
+ EndOfInterrupt();
+ }
+
+/**
+The DFC used to handle data block transfer completion.
+This function is always executed in driver thread context.
+@param aPtr A pointer to the physical channel object.
+*/
+void DWinsSoundScTxPdd::PlayDfc(TAny* aPtr)
+ {
+ TInt i;
+ DWinsSoundScTxPdd& drv=*(DWinsSoundScTxPdd*)aPtr;
+
+ // More than 1 transfer may have completed so loop until all completions are handled
+ while (drv.iCompletedPlayBufHdrMask)
+ {
+ // Find the buffer ID of the next transfer that has completed
+ for (i=0 ; i<32 && !(drv.iCompletedPlayBufHdrMask&(1<<i)) ; i++) {}
+ __ASSERT_ALWAYS(i<drv.iWaveformBufMgr->iNumWaveformBufs,PANIC());
+ __e32_atomic_and_ord32(&drv.iCompletedPlayBufHdrMask, ~(1u<<i)); // Clear this bit in the mask
+
+ // Update the status of the waveform audio buffer which is completing
+ TWaveformAudioBuf& buf=drv.iWaveformBufMgr->iWaveformAudioBuf[i];
+ buf.iIsInUse=EFalse;
+
+ // Callback the LDD passing the information for the transfer that has completed
+ drv.iPendingPlay--;
+ __KTRACE_SND(Kern::Printf(" Write complete(BufNo:%x Pos:%x Len:%d)",i,buf.iBufHdr.lpData,buf.iBufHdr.dwBufferLength));
+ drv.Ldd()->PlayCallback(buf.iTransferID,KErrNone,buf.iBufHdr.dwBufferLength);
+ }
+ }
+
+/**
+Issue a request from the driver thread to the windows thread to execute a command.
+@param aCommand The identifier of the command to be executed.
+@param aArg0 A first command argument, its meaning depends on the command.
+@param aArg1 A second command argument, its meaning depends on the command.
+@param aArg2 A third command argument, its meaning depends on the command.
+This function is always executed in driver thread context.
+*/
+void DWinsSoundScTxPdd::PlayThreadCommand(TThreadCommand aCommand,TInt aArg0,TInt aArg1,TInt aArg2)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd:PlayThreadCommand"));
+ iPlayCommand = aCommand;
+ iPlayCommandArg0 = aArg0;
+ iPlayCommandArg1 = aArg1;
+ iPlayCommandArg2 = aArg2;
+
+ __HOST_LOCK;
+
+ ReleaseSemaphore(iPlayThreadSem,1,NULL);
+ }
+
+/**
+Pass a value from the windows thread to the driver thread.
+This function is always executed in windows thread context.
+@param aError The value to the passed to the driver thread.
+*/
+void DWinsSoundScTxPdd::PlayThreadNotifyDriver(TInt aError)
+ {
+ iPlayThreadError = aError;
+ BOOL ret = ReleaseSemaphore(iDriverThreadSem,1,NULL);
+ __ASSERT_ALWAYS(ret == TRUE, PANIC()); //Unexpected Windows Error
+ }
+
+#pragma warning(disable : 4702) // unreachable code
+/**
+Open the waveform output device for playback. Use a default device identifier in order to select a device
+capable of meeting the current audio configuration.
+This function can be executed in either driver thread or windows thread context.
+@pre The data member DWinsSoundScTxPdd::iSoundConfig must be setup with the current audio configuration.
+*/
+TInt DWinsSoundScTxPdd::OpenWaveOutDevice()
+ {
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ TUint16 bitsPerSample = 8;
+
+ switch (iSoundConfig.iEncoding)
+ {
+ case ESoundEncoding8BitPCM:
+ break;
+ case ESoundEncoding16BitPCM:
+ bitsPerSample = 16;
+ break;
+ default:
+ return KErrNotSupported;
+ };
+
+ TInt rateInSamplesPerSecond=RateInSamplesPerSecond(iSoundConfig.iRate);
+ format.nChannels = TUint16(iSoundConfig.iChannels);
+ format.nSamplesPerSec = rateInSamplesPerSecond;
+ format.nAvgBytesPerSec = rateInSamplesPerSecond * iSoundConfig.iChannels * bitsPerSample / 8;
+ format.nBlockAlign = TUint16(iSoundConfig.iChannels * bitsPerSample / 8);
+ format.wBitsPerSample = bitsPerSample;
+ format.cbSize = 0;
+
+ MMRESULT res = MMSYSERR_NOERROR;
+
+ __COND_HOST_LOCK;
+ if (iNoHardware)
+ {
+ timeBeginPeriod(KMMTimerRes);
+ iPlayDeviceHandle = (HWAVEOUT)1;
+ }
+ else
+ {
+ res = waveOutOpen(&iPlayDeviceHandle, WAVE_MAPPER, &format, (DWORD)::WaveOutProc, (DWORD)this, CALLBACK_FUNCTION);
+
+ // On some builds of Windows (such as Windows Server 2003), the waveOutGetDevCaps() trick in
+ // DoCreate() won't work, so we have another special check for missing hardware here
+ if ((res == MMSYSERR_NODRIVER) || (res == MMSYSERR_BADDEVICEID))
+ {
+ // Pretend there was no error and switch into hardware emulation mode
+ res = MMSYSERR_NOERROR;
+ iNoHardware = ETrue;
+ iPlayDeviceHandle = (HWAVEOUT)1;
+ iWaveformBufMgr->iIsHardware = EFalse;
+ timeBeginPeriod(KMMTimerRes);
+ }
+ }
+
+ if(iNoHardware)
+ {
+ iSimulatedUSecPlayed = 0;
+ }
+
+ switch (res)
+ {
+ case MMSYSERR_NOERROR: // No error
+ return(KErrNone);
+ case MMSYSERR_ALLOCATED: // Specified resource is already allocated.
+ return(KErrInUse);
+ case WAVERR_BADFORMAT: // Attempted to open with an unsupported waveform-audio format
+ return(KErrNotSupported);
+ case MMSYSERR_NOMEM: // Unable to allocate or lock memory.
+ return(KErrNoMemory);
+ default:
+ return(KErrUnknown);
+ }
+ }
+#pragma warning(default : 4702) // unreachable code
+
+/**
+Open the audio output device.
+This function can be executed in either driver thread or windows thread context.
+@pre The data members DWinsSoundScTxPdd::iSoundConfig and DWinsSoundScTxPdd::iVolume must be setup with the current
+audio configuration.
+*/
+TInt DWinsSoundScTxPdd::CreatePlayDevice(TCreatePlayDeviceMode aMode)
+ {
+ // Check if the waveform output device is already open.
+ if (iPlayDeviceHandle)
+ return(KErrNone);
+
+ WaitForSingleObjectDualThread(iPlayThreadMutex, INFINITE);
+
+ __COND_HOST_LOCK;
+
+ // Open the waveform output device for playback.
+ TInt err = OpenWaveOutDevice();
+ if (err != KErrNone)
+ {
+ ReleaseMutex(iPlayThreadMutex);
+ return(err);
+ }
+
+ __HOST_LOCK_OFF;
+
+ if (aMode==EInit && !iNoHardware)
+ {
+ // Remember the existing volume setting and use this as the default.
+ waveOutGetVolume(iPlayDeviceHandle,&iWinWaveVolume);
+ iVolume = iWinWaveVolume;
+ }
+
+ if (aMode==EStartTransfer)
+ {
+ if (!iNoHardware)
+ {
+ // Set the volume of the waveform output device.
+ waveOutSetVolume(iPlayDeviceHandle, iVolume | (iVolume << 16));
+ }
+
+ // Now, re-allocate a set of the waveform audio blocks in advance of playing any data. Also, prepare one of these
+ // for each buffer within the shared chunk. Need to be in critical section while re-allocating the audio blocks.
+ NKern::ThreadEnterCS();
+ err=iWaveformBufMgr->ReAllocAndUpdate(Ldd()->BufConfig(),Ldd()->ChunkBase(),(TInt)iPlayDeviceHandle);
+ NKern::ThreadLeaveCS();
+ }
+ ReleaseMutex(iPlayThreadMutex);
+
+ return(err);
+ }
+
+/**
+Close down the play device.
+This function can be executed in either driver thread or windows thread context.
+*/
+void DWinsSoundScTxPdd::ClosePlayDevice()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd:ClosePlayDevice"));
+
+ WaitForSingleObjectDualThread(iPlayThreadMutex, INFINITE);
+
+ HWAVEOUT handle = iPlayDeviceHandle;
+
+ __COND_HOST_LOCK;
+
+ if (iNoHardware)
+ timeEndPeriod(KMMTimerRes);
+
+ if (handle)
+ {
+ // Stop playback on the waveout device. (Windows resets the current position to zero and marks all pending
+ // playback buffers as done).
+ if (!iNoHardware)
+ waveOutReset(handle);
+
+ // Un-prepare all the waveform audio buffers.
+ for (TInt i=0 ; i<iWaveformBufMgr->iNumWaveformBufs ; i++)
+ iWaveformBufMgr->iWaveformAudioBuf[i].Unprepare((TInt)handle);
+
+ // Close the waveout device.
+ if (!iNoHardware)
+ {
+ waveOutSetVolume(handle,iWinWaveVolume); // Restore the original volume setting before closing the driver.
+ waveOutClose(handle);
+ }
+
+ iPlayDeviceHandle = NULL;
+ }
+
+ ReleaseMutex(iPlayThreadMutex);
+ }
+
+/**
+The thread function for the play windows thread.
+This function is always executed in windows thread context.
+@pre The data members DWinsSoundScTxPdd::iSoundConfig and DWinsSoundScTxPdd::iVolume must be setup with the current
+audio configuration.
+*/
+void DWinsSoundScTxPdd::PlayThread()
+ {
+ iPlayThreadSem = CreateSemaphore(NULL,0,0x7FFFFFFF,NULL);
+ iPlayTimerEvent = CreateEvent(NULL,TRUE, FALSE, NULL);
+ iPlayThreadMutex = CreateMutex(NULL,FALSE,NULL);
+ __ASSERT_ALWAYS(iPlayThreadSem && iPlayTimerEvent, PANIC()); //no windows memory
+
+ HANDLE objects[2];
+ objects[0] = iPlayThreadSem; // Indicates command from driver thread
+ objects[1] = iPlayTimerEvent; // Indicates timer gone off
+
+ // Open the play device, then close it again. This is so we can return an error early to the client
+ // if there is a problem.
+ TInt ret = CreatePlayDevice(EInit);
+ if (ret != KErrNone)
+ {
+ PlayThreadNotifyDriver(ret);
+ return;
+ }
+ ClosePlayDevice();
+
+ // Signal driver of successful setup
+ PlayThreadNotifyDriver(KErrNone);
+ ResetEvent(iPlayTimerEvent);
+
+ // Wait for the timer to expire or a command.
+ FOREVER
+ {
+ DWORD ret = WaitForMultipleObjectsEx(2,objects,FALSE,INFINITE,TRUE);
+ __KTRACE_SND(Kern::Printf(" PlayThread resumed"));
+ switch (ret)
+ {
+ case WAIT_OBJECT_0: // Command received from the driver thread.
+ if (ProcessPlayCommand(iPlayCommand,iPlayCommandArg0,iPlayCommandArg1,iPlayCommandArg2)==KErrCompletion)
+ return; // ********* Exit thread **************
+ break;
+ case WAIT_OBJECT_0+1:
+ HandlePlayTimerEvent();
+ break;
+ }
+ }
+ }
+
+/**
+Process a request from the driver thread to execute a command.
+This function is always executed in windows thread context.
+@param aCommand The identifier of the command to be executed.
+@param aArg0 A first command argument, its meaning depends on the command.
+@param aArg1 A second command argument, its meaning depends on the command.
+@param aArg2 A third command argument, its meaning depends on the command.
+@return KErrCompletion if the command to exit the windows thread has been received;
+ KErrNone otherwise;
+*/
+TInt DWinsSoundScTxPdd::ProcessPlayCommand(TThreadCommand aCommand,TInt aArg0,TInt aArg1,TInt aArg2)
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd:ProcessPlayCommand(%d)",aCommand));
+ switch(aCommand)
+ {
+ case ESendData: // Initiate the playback of a buffers worth of data to the waveout device.
+ {
+ // Acquire a windows waveform audio buffer for the transfer.
+ char* startAddress=(char*)aArg1;
+ TInt bytesToPlay=aArg2;
+ __ASSERT_ALWAYS(bytesToPlay>0,PANIC());
+ TWaveformAudioBuf* waveformAudioBuf=iWaveformBufMgr->AcquireBuf(startAddress,bytesToPlay,(TInt)iPlayDeviceHandle);
+ waveformAudioBuf->iTransferID=(TUint)aArg0;
+ waveformAudioBuf->iBufHdr.dwBufferLength=bytesToPlay;
+
+ if (!iNoHardware)
+ {
+ // This machine has a waveout device present. Send the buffer to the waveout device.
+ waveformAudioBuf->iBufHdr.dwFlags &= ~WHDR_DONE; // Clear the done flag
+ MMRESULT res = waveOutWrite(iPlayDeviceHandle,&waveformAudioBuf->iBufHdr,sizeof(WAVEHDR));
+ __KTRACE_SND(Kern::Printf(" WaveOutWrite(ID:%x Pos:%x Len:%d)-%d",aArg0,aArg1,aArg2,res));
+ __ASSERT_ALWAYS(res == MMSYSERR_NOERROR,PANIC()); // WaveOutWrite Error
+ }
+ else
+ {
+ // This machine has no audio hardware present so simulate the waveout device using a timer.
+ AddToPendingList(&waveformAudioBuf->iBufHdr,iWaveformBufMgr->iPendingBufList); // Queue the buffer on the pending list
+
+ // Check if the timer needs starting/re-starting
+ if (!iTimerActive)
+ {
+ iLastTimerEventTime = timeGetTime();
+ StartTimer(&waveformAudioBuf->iBufHdr);
+ }
+ }
+
+ // Signal the driver thread that we have started playing the next buffer.
+ PlayThreadNotifyDriver(KErrNone);
+
+ break;
+ }
+
+ case EStop: // Terminate the playing of data to the waveout device.
+ if (iNoHardware)
+ {
+ // This machine has no audio hardware present so simulates the waveout device using a timer.
+ StopTimer(ETrue); // Stop the timer and cancel any buffers pending
+ }
+ ClosePlayDevice(); // Close down the play device.
+
+ // Signal the driver thread that we have completed the command.
+ if (iStopSemaphore)
+ {
+ LONG prev;
+ ReleaseSemaphore(iStopSemaphore,1,&prev);
+ }
+ break;
+
+ case EExit: // Close down the play device and exit the windows thread.
+ {
+ if (!iNoHardware)
+ {
+ // This machine has a waveout device present.
+ if (iPlayDeviceHandle)
+ {
+ waveOutReset(iPlayDeviceHandle); // Stop playback on the waveout device.
+ waveOutSetVolume(iPlayDeviceHandle,iWinWaveVolume); // Restore the original volume setting before closing the driver.
+ waveOutClose(iPlayDeviceHandle); // Close the waveout device.
+ }
+ }
+ else
+ {
+ // This machine has no audio hardware present so simulates the waveout device using a timer.
+ StopTimer(ETrue); // Stop the timer and cancel any buffers pending
+ }
+ // Logically the playback device is now shut so clear the handle.
+ iPlayDeviceHandle = 0;
+
+ // Signal the driver thread that we have completed the command.
+ if (iDeathSemaphore)
+ {
+ LONG prev;
+ ReleaseSemaphore(iDeathSemaphore,1,&prev);
+ }
+ return(KErrCompletion); // ********* Exit thread **************
+ }
+
+ case EPause: // Halt the playback of data to the waveout device.
+ if (!iNoHardware)
+ waveOutPause(iPlayDeviceHandle); // Pause playback on the waveout device. Windows saves current position.
+ else
+ {
+ StopTimer(EFalse); // Just stop the timer. Don't cancel any pending buffers.
+ iPauseTime = timeGetTime();
+ }
+ break;
+
+ case EResume: // Resume the playback of data to the waveout device.
+ if (!iNoHardware)
+ waveOutRestart(iPlayDeviceHandle); // Resume playback on the waveout device.
+ else
+ {
+ // Check if there are more audio buffers waiting to be resumed
+ WAVEHDR* buf=iWaveformBufMgr->iPendingBufList[0];
+ if (buf)
+ {
+ // Before restarting the emulation timer, determine how long we were paused for and
+ // add that time to the time the timer last triggered. This will allow us to continue
+ // as though we had never been paused
+ DWORD currentTime = timeGetTime();
+ iLastTimerEventTime += (currentTime - iPauseTime);
+ iPauseTime = 0;
+ StartTimer(buf); // Re-start the timer
+ }
+ }
+ break;
+ }
+ return(KErrNone);
+ }
+
+/**
+Handle a timer expiry event. This is only used when no audio hardware is present, with a timer expiry corresponding
+to the end of a data block transfer.
+This function is always executed in windows thread context.
+*/
+void DWinsSoundScTxPdd::HandlePlayTimerEvent()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd:HandlePlayTimerEvent"));
+ ResetEvent(iPlayTimerEvent); // Reset the event
+
+ iSimulatedUSecPlayed += (1000 * iSimulatedMsecDuration);
+
+ // Remove the audio buffer just completed from the pending list and save it for the driver thread.
+ WAVEHDR* buf=RemoveFromPendingList(iWaveformBufMgr->iPendingBufList);
+ __ASSERT_ALWAYS(buf != NULL,PANIC());
+ TInt waveBufId=buf->dwUser; // Work out which waveform audio buffer is completing.
+
+ // Check if there are more audio buffers waiting to be played
+ buf=iWaveformBufMgr->iPendingBufList[0];
+ if (buf)
+ {
+ iLastTimerEventTime = timeGetTime();
+ StartTimer(buf); // Re-start the timer
+ }
+ else
+ iTimerActive=EFalse;
+
+ // Notify that another audio buffer has been completed.
+ StartOfInterrupt();
+ iCompletedPlayBufHdrMask|=(1<<waveBufId); // Update the completion status mask
+ iDfc.Add();
+ EndOfInterrupt();
+ return;
+ }
+
+/**
+Initialise the data member DWinsSoundScTxPdd::iCaps with the capabilities of this audio device.
+*/
+void DWinsSoundScTxPdd::SetCaps()
+ {
+ __KTRACE_SND(Kern::Printf(">DWinsSoundScTxPdd::SetCaps"));
+
+ // The data transfer direction for this unit is play.
+ iCaps.iDirection=ESoundDirPlayback;
+
+ // Assume this unit supports mono or stereo.
+ iCaps.iChannels=KSoundMonoChannel|KSoundStereoChannel;
+
+ // Assume this unit supports all sample rates.
+ iCaps.iRates=(KSoundRate7350Hz|KSoundRate8000Hz|KSoundRate8820Hz|KSoundRate9600Hz|KSoundRate11025Hz|
+ KSoundRate12000Hz|KSoundRate14700Hz|KSoundRate16000Hz|KSoundRate22050Hz|KSoundRate24000Hz|
+ KSoundRate29400Hz|KSoundRate32000Hz|KSoundRate44100Hz|KSoundRate48000Hz);
+
+ // Assume this unit supports 8bit and 16bit PCM encoding.
+ iCaps.iEncodings=(KSoundEncoding8BitPCM|KSoundEncoding16BitPCM);
+
+ // This unit only supports interleaved data format
+ iCaps.iDataFormats=KSoundDataFormatInterleaved;
+
+ // The minimum request size that the device can support.
+ iCaps.iRequestMinSize=0; // No restriction
+
+ // The request alignment that this device requires.
+ iCaps.iRequestAlignment=0; // No restriction
+
+ // This unit is not capable of detecting changes in hardware configuration.
+ iCaps.iHwConfigNotificationSupport=EFalse;
+ }
+
+/**
+Start the audio timer.
+The timer is only used when no audio hardware is present on the machine. This is in order to introduce a delay which is
+equivelent to that incurred when transferring audio data over a real audio device.
+@param aBuffer The audio buffer which would have been transferred had a real audio device been present. This contains
+ information on the number of bytes to transfer.
+*/
+void DWinsSoundScTxPdd::StartTimer(WAVEHDR* aBuffer)
+ {
+ // First, need to calculate the duration of the timer in milliseconds.
+ TInt bytesToPlay=aBuffer->dwBufferLength;
+ iSimulatedMsecDuration = bytesToPlay*1000;
+ iSimulatedMsecDuration /= (RateInSamplesPerSecond(iSoundConfig.iRate) * iSoundConfig.iChannels);
+ if (iSoundConfig.iEncoding==ESoundEncoding16BitPCM)
+ iSimulatedMsecDuration /= 2;
+ if (iSoundConfig.iEncoding==ESoundEncoding24BitPCM)
+ iSimulatedMsecDuration /= 3;
+ if (iSimulatedMsecDuration<=0)
+ iSimulatedMsecDuration=1; // Round up to 1ms or timeSetEvent() will return an error.
+
+ // If we have been paused and are now restarting, determine the amount of time that we were paused
+ // and subtract that from the time until the next timer trigger. If this is not done then the time
+ // played or recorded will not be calculated correctly
+ DWORD pauseTime = (timeGetTime() - iLastTimerEventTime);
+ MMRESULT res = timeSetEvent((iSimulatedMsecDuration - pauseTime), KMMTimerRes, (LPTIMECALLBACK)iPlayTimerEvent, 0, TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
+ __ASSERT_ALWAYS(res != NULL,PANIC()); // timeSetEvent error.
+ iTimerID = res; // Save the identifier for the new timer event.
+ iTimerActive=ETrue;
+ }
+
+/**
+Stop the audio timer.
+The timer is only used when no audio hardware is present on the machine. This is in order to introduce a delay which is
+equivelent to that incurred when transferring audio data over a real audio device.
+@param aCancellAll Set to ETrue in order to discard any buffers queued on the pending buffer list. EFalse otherwise.
+*/
+void DWinsSoundScTxPdd::StopTimer(TBool aCancelAll)
+ {
+ if (iTimerActive)
+ {
+ MMRESULT res = timeKillEvent(iTimerID);
+ __ASSERT_ALWAYS(res == TIMERR_NOERROR,PANIC()); // timeKillEvent error
+
+ if (aCancelAll)
+ {
+ WAVEHDR* b;
+ do
+ b=RemoveFromPendingList(iWaveformBufMgr->iPendingBufList);
+ while(b);
+ }
+ }
+ iTimerActive=EFalse;
+ }
+