windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dswave/wave.cpp
2020-09-26 16:20:57 +08:00

938 lines
22 KiB
C++

#include <objbase.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsoundp.h>
//#include "debug.h"
#include "debug.h"
#include "dmusicc.h"
#include "dmusici.h"
#include "dmusicf.h"
#include "validate.h"
#include "riff.h"
#include "dswave.h"
#include "riff.h"
#include <regstr.h>
#include <share.h>
#include "waveutil.h"
#include "dmstrm.h"
// seeks to a 32-bit position in a stream.
HRESULT __inline StreamSeek( LPSTREAM pStream, long lSeekTo, DWORD dwOrigin )
{
LARGE_INTEGER li;
if( lSeekTo < 0 )
{
li.HighPart = -1;
}
else
{
li.HighPart = 0;
}
li.LowPart = lSeekTo;
return pStream->Seek( li, dwOrigin, NULL );
}
// returns the current 32-bit position in a stream.
DWORD __inline StreamTell( LPSTREAM pStream )
{
LARGE_INTEGER li;
ULARGE_INTEGER ul;
#ifdef DBG
HRESULT hr;
#endif
li.HighPart = 0;
li.LowPart = 0;
#ifdef DBG
hr = pStream->Seek( li, STREAM_SEEK_CUR, &ul );
if( FAILED( hr ) )
#else
if( FAILED( pStream->Seek( li, STREAM_SEEK_CUR, &ul ) ) )
#endif
{
return 0;
}
return ul.LowPart;
}
CWave::CWave() : m_dwDecompressedStart(0)
{
V_INAME(CWave::CWave);
InterlockedIncrement(&g_cComponent);
InitializeCriticalSection(&m_CriticalSection);
m_pwfx = NULL;
m_pwfxDst = NULL;
m_fdwFlags = 0;
m_fdwOptions = 0;
m_cSamples = 0L;
m_pStream = NULL;
m_rtReadAheadTime = 0;
m_cRef = 1;
}
CWave::~CWave()
{
V_INAME(CWave::~CWave);
if (NULL != m_pwfx)
{
GlobalFreePtr(m_pwfx);
}
if (NULL != m_pwfxDst)
{
GlobalFreePtr(m_pwfxDst);
}
if (m_pStream) m_pStream->Release();
DeleteCriticalSection(&m_CriticalSection);
InterlockedDecrement(&g_cComponent);
}
STDMETHODIMP CWave::QueryInterface
(
const IID &iid,
void **ppv
)
{
V_INAME(CWave::QueryInterface);
V_REFGUID(iid);
V_PTRPTR_WRITE(ppv);
if (iid == IID_IUnknown || iid == IID_IDirectSoundWave)
{
*ppv = static_cast<IDirectSoundWave*>(this);
}
else if (iid == IID_IPersistStream)
{
*ppv = static_cast<IPersistStream*>(this);
}
else if (iid == IID_IDirectMusicObject)
{
*ppv = static_cast<IDirectMusicObject*>(this);
}
else if (iid == IID_IPrivateWave)
{
*ppv = static_cast<IPrivateWave*>(this);
}
else
{
*ppv = NULL;
Trace(4,"Warning: Request to query unknown interface on Wave Object\n");
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(this)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CWave::AddRef()
{
V_INAME(CWave::AddRef);
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CWave::Release()
{
V_INAME(CWave::Release);
if (!InterlockedDecrement(&m_cRef))
{
delete this;
return 0;
}
return m_cRef;
}
static inline HRESULT MMRESULTToHRESULT(
MMRESULT mmr)
{
switch (mmr)
{
case MMSYSERR_NOERROR:
return S_OK;
case MMSYSERR_ALLOCATED:
return DSERR_ALLOCATED;
case MMSYSERR_NOMEM:
return E_OUTOFMEMORY;
case MMSYSERR_INVALFLAG:
return E_INVALIDARG;
case MMSYSERR_INVALHANDLE:
return E_INVALIDARG;
case MMSYSERR_INVALPARAM:
return E_INVALIDARG;
}
return E_FAIL;
}
STDMETHODIMP CWave::GetFormat
(
LPWAVEFORMATEX pwfx,
DWORD dwSizeAllocated,
LPDWORD pdwSizeWritten
)
{
DWORD cbSize;
LPWAVEFORMATEX pwfxTemp = NULL;
V_INAME(CWave::GetFormat);
if (!pwfx && !pdwSizeWritten)
{
Trace(1, "ERROR: GetFormat (Wave): Must request either the format or the required size\n.");
return E_INVALIDARG;
}
if (!m_pwfx)
{
return DSERR_BADFORMAT;
}
if (WAVE_FORMAT_PCM == m_pwfx->wFormatTag)
{
pwfxTemp = m_pwfx;
}
else
{
pwfxTemp = m_pwfxDst;
if (!pwfxTemp)
{
return DSERR_BADFORMAT;
}
}
// Note: Assuming that the wave object fills the cbSize field even
// on PCM formats...
if (WAVE_FORMAT_PCM == pwfxTemp->wFormatTag)
{
cbSize = sizeof(PCMWAVEFORMAT);
}
else
{
cbSize = sizeof(WAVEFORMATEX) + pwfxTemp->cbSize;
}
if (cbSize > dwSizeAllocated || !pwfx)
{
if (pdwSizeWritten)
{
*pdwSizeWritten = cbSize;
return S_OK; // What to return?
}
else
{
return DSERR_INVALIDPARAM;
}
}
// We don't validate this parameter any earlier on the off chance
// that they're doing a query...
V_BUFPTR_WRITE(pwfx, dwSizeAllocated);
CopyMemory(pwfx, pwfxTemp, cbSize);
// Set the cbSize field in destination for PCM, IF WE HAVE ROOM...
if (WAVE_FORMAT_PCM == pwfxTemp->wFormatTag)
{
if (sizeof(WAVEFORMATEX) <= dwSizeAllocated)
{
pwfx->cbSize = 0;
}
}
// Return the numbers of bytes actually writted
if (pdwSizeWritten)
{
*pdwSizeWritten = cbSize;
}
return S_OK;
}
STDMETHODIMP CWave::CreateSource
(
IDirectSoundSource **ppSource,
LPWAVEFORMATEX pwfx,
DWORD fdwFlags
)
{
HRESULT hr = S_OK;
CWaveViewPort* pVP;
CREATEVIEWPORT cvp;
V_INAME(CWave::CreateSource);
V_PTRPTR_WRITE(ppSource);
V_PWFX_READ(pwfx);
DWORD dwCreateFlags = 0;
if (fdwFlags == DMUS_DOWNLOADINFO_ONESHOTWAVE)
{
dwCreateFlags |= DSOUND_WAVEF_ONESHOT;
}
if (dwCreateFlags & (~DSOUND_WAVEF_CREATEMASK))
{
Trace(1, "ERROR: CreateSource (Wave): Unknown flag.\n");
return (E_INVALIDARG);
}
TraceI(5, "CWave::CreateSource [%d samples]\n", m_cSamples);
pVP = new CWaveViewPort;
if (!pVP)
{
return E_OUTOFMEMORY;
}
cvp.pStream = m_pStream;
cvp.cSamples = m_cSamples;
cvp.dwDecompressedStart = m_dwDecompressedStart;
cvp.cbStream = m_cbStream;
cvp.pwfxSource = m_pwfx;
cvp.pwfxTarget = pwfx;
cvp.fdwOptions = dwCreateFlags;
hr = pVP->Create(&cvp);
if (SUCCEEDED(hr))
{
hr = pVP->QueryInterface(IID_IDirectSoundSource, (void **)ppSource);
}
else
{
TraceI(5, "CWave::CreateSource 00\n");
}
if (SUCCEEDED(hr))
{
// The QI gave us one ref too many
pVP->Release();
}
else
{
TraceI(5, "CWave::CreateSource 01\n");
}
return hr;
}
STDMETHODIMP CWave::GetStreamingParms
(
LPDWORD pdwFlags,
LPREFERENCE_TIME prtReadAhead
)
{
V_INAME(IDirectSoundWave::GetStreamingParms);
V_PTR_WRITE(pdwFlags, DWORD);
V_PTR_WRITE(prtReadAhead, REFERENCE_TIME);
*pdwFlags = 0;
if(!(m_fdwFlags & DSOUND_WAVEF_ONESHOT))
{
*pdwFlags |= DMUS_WAVEF_STREAMING;
}
if(m_fdwFlags & DMUS_WAVEF_NOPREROLL)
{
*pdwFlags |= DMUS_WAVEF_NOPREROLL;
}
*prtReadAhead = m_rtReadAheadTime;
return S_OK;
}
STDMETHODIMP CWave::GetClassID
(
CLSID* pClsId
)
{
V_INAME(CWave::GetClassID);
V_PTR_WRITE(pClsId, CLSID);
*pClsId = CLSID_DirectSoundWave;
return S_OK;
}
STDMETHODIMP CWave::IsDirty()
{
V_INAME(CWave::IsDirty);
return S_FALSE;
}
BOOL CWave::ParseHeader
(
IStream* pIStream,
IRIFFStream* pIRiffStream,
LPMMCKINFO pckMain
)
{
MMCKINFO ck;
DWORD cb = 0;
DWORD dwPos = 0;
MMCKINFO ckINFO;
HRESULT hr;
ck.ckid = 0;
ck.fccType = 0;
V_INAME(CWave::ParseHeader);
BOOL fFormat = FALSE;
BOOL fData = FALSE;
BOOL fHeader = FALSE;
BOOL fReadDecompressionFmt = FALSE;
DWORD dwSamplesFromFact = 0;
while (0 == pIRiffStream->Descend(&ck, pckMain, MMIO_FINDCHUNK))
{
switch (ck.ckid)
{
case mmioFOURCC('w','a','v','u') :
{
cb = 0;
bool bRuntime = false;
hr = pIStream->Read(&bRuntime, sizeof(bool), &cb);
if(FAILED(hr) || cb != sizeof(bool))
{
return FALSE;
}
cb = 0;
bool bCompressed = false;
hr = pIStream->Read(&bCompressed, sizeof(bool), &cb);
if(FAILED(hr) || cb != sizeof(bool))
{
return FALSE;
}
if(bRuntime && bCompressed)
{
// If we have already allocated m_pwfxDst, delete it first
if (NULL != m_pwfxDst)
{
GlobalFreePtr(m_pwfxDst);
}
m_pwfxDst = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, sizeof(WAVEFORMATEX));
if (NULL == m_pwfxDst)
{
return FALSE;
}
cb = 0;
hr = pIStream->Read(m_pwfxDst, sizeof(WAVEFORMATEX), &cb);
if(FAILED(hr) || cb != sizeof(WAVEFORMATEX))
{
GlobalFreePtr(m_pwfxDst);
return FALSE;
}
// Read the actual start for the decompressed data if available
// This is very important for MP3 and WMA codecs which insert silence in the beginning
if(ck.cksize > 2 + sizeof(WAVEFORMATEX))
{
cb = 0;
hr = pIStream->Read(&m_dwDecompressedStart, sizeof(DWORD), &cb);
if(FAILED(hr) || cb != sizeof(DWORD))
{
GlobalFreePtr(m_pwfxDst);
return FALSE;
}
}
fReadDecompressionFmt = TRUE;
}
break;
}
case DMUS_FOURCC_WAVEHEADER_CHUNK:
{
m_fdwFlags = 0;
fHeader = TRUE;
DMUS_IO_WAVE_HEADER iWaveHeader;
memset(&iWaveHeader, 0, sizeof(iWaveHeader));
hr = pIStream->Read(&iWaveHeader, sizeof(iWaveHeader), &cb);
if (iWaveHeader.dwFlags & DMUS_WAVEF_STREAMING)
{
m_fdwFlags &= ~DSOUND_WAVEF_ONESHOT;
m_rtReadAheadTime = iWaveHeader.rtReadAhead;
}
else
{
m_fdwFlags |= DSOUND_WAVEF_ONESHOT;
m_rtReadAheadTime = 0;
}
if (iWaveHeader.dwFlags & DMUS_WAVEF_NOPREROLL)
{
m_fdwFlags |= DMUS_WAVEF_NOPREROLL;
}
else
{
m_fdwFlags &= ~DMUS_WAVEF_NOPREROLL;
}
break;
}
case DMUS_FOURCC_GUID_CHUNK:
hr = pIStream->Read(&m_guid, sizeof(GUID), &cb);
m_fdwOptions |= DMUS_OBJ_OBJECT;
break;
case DMUS_FOURCC_VERSION_CHUNK:
hr = pIStream->Read( &m_vVersion, sizeof(DMUS_VERSION), &cb );
m_fdwOptions |= DMUS_OBJ_VERSION;
break;
case FOURCC_LIST:
switch(ck.fccType)
{
case DMUS_FOURCC_INFO_LIST:
while( pIRiffStream->Descend( &ckINFO, &ck, 0 ) == 0 )
{
switch(ckINFO.ckid)
{
case mmioFOURCC('I','N','A','M'):
{
DWORD cbSize;
cbSize = min(ckINFO.cksize, DMUS_MAX_NAME);
char szName[DMUS_MAX_NAME];
hr = pIStream->Read((BYTE*)szName, cbSize, &cb);
if(SUCCEEDED(hr))
{
MultiByteToWideChar(CP_ACP, 0, szName, -1, m_wszFilename, DMUS_MAX_NAME);
m_fdwOptions |= DMUS_OBJ_NAME;
}
break;
}
}
pIRiffStream->Ascend( &ckINFO, 0 );
}
break;
}
break;
case mmioFOURCC('f','m','t',' '):
{
// If we have already allocated m_pwfx, delete it first
if (NULL != m_pwfx)
{
GlobalFreePtr(m_pwfx);
}
m_pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, max (sizeof(WAVEFORMATEX), ck.cksize));
if (NULL == m_pwfx)
{
return FALSE;
}
hr = pIStream->Read(m_pwfx, ck.cksize, &cb);
if (S_OK != hr)
{
return FALSE;
}
if (m_pwfx && WAVE_FORMAT_PCM != m_pwfx->wFormatTag)
{
if(fReadDecompressionFmt == FALSE)
{
// If we have already allocated m_pwfxDst, delete it first
if (NULL != m_pwfxDst)
{
GlobalFreePtr(m_pwfxDst);
}
m_pwfxDst = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, sizeof(WAVEFORMATEX));
if (NULL == m_pwfxDst)
{
return FALSE;
}
m_pwfxDst->wFormatTag = WAVE_FORMAT_PCM;
MMRESULT mmr = acmFormatSuggest(NULL, m_pwfx, m_pwfxDst, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG);
if(MMSYSERR_NOERROR != mmr)
{
GlobalFreePtr(m_pwfxDst);
m_pwfxDst = NULL;
return FALSE;
}
}
}
fFormat = TRUE;
TraceI(5, "Format [%d:%d%c%02d]\n", m_pwfx->wFormatTag, m_pwfx->nSamplesPerSec/1000, ((2==m_pwfx->nChannels)?'S':'M'), m_pwfx->wBitsPerSample);
break;
}
case mmioFOURCC('f','a','c','t'):
hr = pIStream->Read(&dwSamplesFromFact, sizeof(DWORD), &cb);
TraceI(5, "Stream is %d samples\n", dwSamplesFromFact);
break;
case mmioFOURCC('d','a','t','a'):
TraceI(5, "Data chunk %d bytes\n", ck.cksize);
m_cbStream = ck.cksize;
fData = TRUE;
if (!fHeader) FallbackStreamingBehavior();
// save stream position so we can seek back later
dwPos = StreamTell( pIStream );
break;
default:
break;
}
pIRiffStream->Ascend(&ck, 0);
ck.ckid = 0;
ck.fccType = 0;
}
if (m_pwfx && WAVE_FORMAT_PCM != m_pwfx->wFormatTag && dwSamplesFromFact)
{
m_cSamples = dwSamplesFromFact;
}
if (!fHeader) FallbackStreamingBehavior();
// Seek to beginning of data
if (fData)
{
StreamSeek(pIStream, dwPos, STREAM_SEEK_SET);
}
return fFormat && fData;
}
STDMETHODIMP CWave::Load
(
IStream* pIStream
)
{
IRIFFStream* pIRiffStream = NULL;
HRESULT hr = S_OK;
V_INAME(CWave::Load);
if (NULL == pIStream)
{
Trace(1, "ERROR: Load (Wave): Attempt to load from null stream.\n");
return E_INVALIDARG;
}
EnterCriticalSection( &m_CriticalSection );
if (SUCCEEDED(AllocRIFFStream(pIStream, &pIRiffStream)))
{
MMCKINFO ckMain;
ckMain.fccType = mmioFOURCC('W','A','V','E');
if (0 != pIRiffStream->Descend(&ckMain, NULL, MMIO_FINDRIFF))
{
Trace(1, "ERROR: Load (Wave): Stream does not contain a wave chunk.\n");
hr = E_INVALIDARG;
goto ON_END;
}
// Parses the header information and seeks to the beginning
// of the data in the data chunk.
if (0 == ParseHeader(pIStream, pIRiffStream, &ckMain))
{
Trace(1, "ERROR: Load (Wave): Attempt to read wave's header information failed.\n");
hr = E_INVALIDARG;
goto ON_END;
}
if (0 == m_cSamples)
{
if (m_pwfx && WAVE_FORMAT_PCM == m_pwfx->wFormatTag)
{
m_cSamples = m_cbStream / m_pwfx->nBlockAlign;
}
else // wave format not supported
{
hr = DSERR_BADFORMAT;
goto ON_END;
}
}
}
pIStream->AddRef();
if (m_pStream)
{
m_pStream->Release();
}
m_pStream = pIStream;
ON_END:
if (pIRiffStream) pIRiffStream->Release();
LeaveCriticalSection( &m_CriticalSection );
TraceI(5, "CWave::Load01\n");
return hr;
}
STDMETHODIMP CWave::Save
(
IStream* pIStream,
BOOL fClearDirty
)
{
V_INAME(CWave::Save);
return E_NOTIMPL;
}
STDMETHODIMP CWave::GetSizeMax
(
ULARGE_INTEGER FAR* pcbSize
)
{
V_INAME(CWave::GetSizeMax);
return E_NOTIMPL;
}
STDMETHODIMP CWave::GetDescriptor
(
LPDMUS_OBJECTDESC pDesc
)
{
// Parameter validation...
V_INAME(CWave::GetDescriptor);
V_STRUCTPTR_WRITE(pDesc, DMUS_OBJECTDESC)
ZeroMemory(pDesc, sizeof(DMUS_OBJECTDESC));
pDesc->dwSize = sizeof(DMUS_OBJECTDESC);
pDesc->dwValidData = DMUS_OBJ_CLASS;
pDesc->guidClass = CLSID_DirectSoundWave;
if (NULL != m_pwfx)
{
pDesc->dwValidData |= DMUS_OBJ_LOADED;
}
if (m_fdwOptions & DMUS_OBJ_OBJECT)
{
pDesc->guidObject = m_guid;
pDesc->dwValidData |= DMUS_OBJ_OBJECT;
}
if (m_fdwOptions & DMUS_OBJ_NAME)
{
memcpy( pDesc->wszName, m_wszFilename, sizeof(m_wszFilename) );
pDesc->dwValidData |= DMUS_OBJ_NAME;
}
if (m_fdwOptions & DMUS_OBJ_VERSION)
{
pDesc->vVersion.dwVersionMS = m_vVersion.dwVersionMS;
pDesc->vVersion.dwVersionLS = m_vVersion.dwVersionLS;
pDesc->dwValidData |= DMUS_OBJ_VERSION;
}
return S_OK;
}
STDMETHODIMP CWave::SetDescriptor
(
LPDMUS_OBJECTDESC pDesc
)
{
HRESULT hr = E_INVALIDARG;
DWORD dw = 0;
// Parameter validation...
V_INAME(CWave::SetDescriptor);
V_PTR_READ(pDesc, DMUS_OBJECTDESC)
if (pDesc->dwSize >= sizeof(DMUS_OBJECTDESC))
{
if(pDesc->dwValidData & DMUS_OBJ_CLASS)
{
dw |= DMUS_OBJ_CLASS;
}
if(pDesc->dwValidData & DMUS_OBJ_LOADED)
{
dw |= DMUS_OBJ_LOADED;
}
if (pDesc->dwValidData & DMUS_OBJ_OBJECT)
{
m_guid = pDesc->guidObject;
dw |= DMUS_OBJ_OBJECT;
m_fdwOptions |= DMUS_OBJ_OBJECT;
}
if (pDesc->dwValidData & DMUS_OBJ_NAME)
{
memcpy( m_wszFilename, pDesc->wszName, sizeof(WCHAR)*DMUS_MAX_NAME );
dw |= DMUS_OBJ_NAME;
m_fdwOptions |= DMUS_OBJ_NAME;
}
if (pDesc->dwValidData & DMUS_OBJ_VERSION)
{
m_vVersion.dwVersionMS = pDesc->vVersion.dwVersionMS;
m_vVersion.dwVersionLS = pDesc->vVersion.dwVersionLS;
dw |= DMUS_OBJ_VERSION;
m_fdwOptions |= DMUS_OBJ_VERSION;
}
if (pDesc->dwValidData & (~dw))
{
Trace(2, "WARNING: SetDescriptor (Wave): Descriptor contains fields that were not set.\n");
hr = S_FALSE;
pDesc->dwValidData = dw;
}
else
{
hr = S_OK;
}
}
return hr;
}
STDMETHODIMP CWave::ParseDescriptor
(
LPSTREAM pStream,
LPDMUS_OBJECTDESC pDesc
)
{
V_INAME(CWave::ParseDescriptor);
V_PTR_READ(pDesc, DMUS_OBJECTDESC)
CRiffParser Parser(pStream);
RIFFIO ckMain;
RIFFIO ckNext;
RIFFIO ckINFO;
HRESULT hr = S_OK;
Parser.EnterList(&ckMain);
if (Parser.NextChunk(&hr) && (ckMain.fccType == mmioFOURCC('W','A','V','E')))
{
Parser.EnterList(&ckNext);
while(Parser.NextChunk(&hr))
{
switch(ckNext.ckid)
{
case DMUS_FOURCC_GUID_CHUNK:
hr = Parser.Read( &pDesc->guidObject, sizeof(GUID) );
if( SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_OBJECT;
}
break;
case DMUS_FOURCC_VERSION_CHUNK:
hr = Parser.Read( &pDesc->vVersion, sizeof(DMUS_VERSION) );
if( SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_VERSION;
}
break;
case FOURCC_LIST:
switch(ckNext.fccType)
{
case DMUS_FOURCC_INFO_LIST:
Parser.EnterList(&ckINFO);
while (Parser.NextChunk(&hr))
{
switch( ckINFO.ckid )
{
case mmioFOURCC('I','N','A','M'):
{
DWORD cbSize;
cbSize = min(ckINFO.cksize, DMUS_MAX_NAME);
char szName[DMUS_MAX_NAME];
hr = Parser.Read(&szName, sizeof(szName));
if(SUCCEEDED(hr))
{
MultiByteToWideChar(CP_ACP, 0, szName, -1, pDesc->wszName, DMUS_MAX_NAME);
pDesc->dwValidData |= DMUS_OBJ_NAME;
}
break;
}
default:
break;
}
}
Parser.LeaveList();
break;
}
break;
default:
break;
}
}
Parser.LeaveList();
}
else
{
Trace(2, "WARNING: ParseDescriptor (Wave): The stream does not contain a Wave chunk.\n");
hr = DMUS_E_CHUNKNOTFOUND;
}
return hr;
}
STDMETHODIMP CWave::GetLength(REFERENCE_TIME *prtLength)
{
HRESULT hr = S_OK;
if (0 == m_cSamples)
{
if (m_pwfx && WAVE_FORMAT_PCM == m_pwfx->wFormatTag)
{
m_cSamples = m_cbStream / m_pwfx->nBlockAlign;
}
}
if (m_cSamples && m_pwfx && m_pwfx->nSamplesPerSec)
{
if(m_dwDecompressedStart > 0)
{
assert(m_dwDecompressedStart < m_cSamples);
*prtLength = 1000 * (REFERENCE_TIME)(m_cSamples - m_dwDecompressedStart) / m_pwfx->nSamplesPerSec;
}
else
{
*prtLength = 1000 * (REFERENCE_TIME)m_cSamples / m_pwfx->nSamplesPerSec;
}
}
else
{
Trace(2, "WARNING: Couldn't get a length for a Wave.\n");
hr = DMUS_E_BADWAVE;
}
return hr;
}