Files
2025-05-21 21:20:08 +03:00

1009 lines
23 KiB
C++

// ASRStream.cpp : Implementation of CASRStream
#include "stdafx.h"
#include "STCustomStream.h"
#include "ASRStream.h"
#include "Spddkhlp.h"
const WAIT_TIME_OUT = 20000; // 20 seconds
/////////////////////////////////////////////////////////////////////////////
// CASRStream
////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::FinalConstruct()
{
HRESULT hr = S_OK;
//Initialize data members
m_ulBufferSize = 0;
m_ulActualRead = 0;
m_pnDataBuffer = NULL;
m_ulLeftOver = 0;
m_ulLeftOverPos = 0;
m_nNumberOfSamples = 0;
m_pSampleReadyEvents = NULL;
m_ppStreamSamples = NULL;
m_pnLeftOverBuffer = NULL;
m_bFlag = FALSE;
m_bPurgeFlag = FALSE;
return hr;
}
////////////////////////////////////////////////////////////////////////
void CASRStream::FinalRelease()
{
//Release objects
CleanUp();
m_cpSRMediaStream.Release();
}
////////////////////////////////////////////////////////////////////////
// Initialize the SR render stream and store the wave format
//
STDMETHODIMP CASRStream::InitSRRenderStream( IUnknown *pRenderTerminal)
{
HRESULT hr = S_OK;
CComQIPtr<ITTerminal> cpTerminal(pRenderTerminal);
//Release the stream, if it exists
if ( m_cpSRMediaStream )
{
m_cpSRMediaStream.Release();
}
//Query the IMediaStream interface from the terminal
if ( cpTerminal )
{
hr = cpTerminal->QueryInterface(IID_IMediaStream, (void**)&m_cpSRMediaStream);
}
else
{
hr = E_INVALIDARG;
cpTerminal.Release();
return hr;
}
//Get the ITAMMediaFormat and then retrieve the format
ITAMMediaFormat *pITFormat;
hr = cpTerminal->QueryInterface(IID_ITAMMediaFormat, (void **)&pITFormat);
if (FAILED(hr))
{
cpTerminal.Release();
return hr;
}
AM_MEDIA_TYPE *pmt;
if(SUCCEEDED(hr))
{
hr = pITFormat->get_MediaFormat(&pmt);
}
pITFormat->Release();
//Store the wave format
if(SUCCEEDED(hr))
{
hr = m_StreamFormat.AssignFormat (SPDFID_WaveFormatEx, ( WAVEFORMATEX * )pmt->pbFormat );
::CoTaskMemFree(pmt);
}
cpTerminal.Release();
return hr;
}
////////////////////////////////////////////////////////////////////////
// Release the internal objects
//
STDMETHODIMP CASRStream::CleanUp()
{
HRESULT hr = S_OK;
m_hCritSec.Lock();
//Release objects
if ( m_pSampleReadyEvents)
{
ReleaseEvents(m_pSampleReadyEvents, m_nNumberOfSamples);
m_pSampleReadyEvents = NULL;
}
if ( m_ppStreamSamples )
{
ReleaseSamples(m_ppStreamSamples, m_nNumberOfSamples);
m_ppStreamSamples = NULL;
}
if ( m_pnDataBuffer )
{
delete[] m_pnDataBuffer;
m_pnDataBuffer = NULL;
}
if ( m_pnLeftOverBuffer )
{
delete[] m_pnLeftOverBuffer;
m_pnLeftOverBuffer = NULL;
}
m_hCritSec.Unlock();
return hr;
}
////////////////////////////////////////////////////////////////////////
// Set the purge stream flag to TRUE so that SR engine will get the less
// audio data than it wants. The SR engine will stop calling Read(), because
// the SR engine thinks it reaches the end of the stream.
//
STDMETHODIMP CASRStream::PurgeStream()
{
HRESULT hr = S_OK;
m_hCritSec.Lock();
m_bPurgeFlag = TRUE;
m_bFlag = FALSE;
m_hCritSec.Unlock();
return hr;
}
////////////////////////////////////////////////////////////////////////
// Set the purge stream flag to FALSE. This allows the SR engine to
// to get the data in the next Read() calls.
// Reset the other variables.
//
STDMETHODIMP CASRStream::RestartStream()
{
HRESULT hr = S_OK;
m_hCritSec.Lock();
hr = CleanUp();
m_ulBufferSize = 0;
m_ulActualRead = 0;
m_ulLeftOver = 0;
m_ulLeftOverPos = 0;
m_nNumberOfSamples = 0;
m_bFlag = FALSE;
m_bPurgeFlag = FALSE;
m_hCritSec.Unlock();
return hr;
}
////////////////////////////////////////////////////////////////////////
// read the terminal's allocator properties to return the number of samples
// the terminal provides
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::GetNumberOfSamplesOnStream(IMediaStream *pTerminalMediaStream,
OUT DWORD *pnNumberOfSamples)
{
HRESULT hr = S_OK;
//
// don't return garbage
//
*pnNumberOfSamples = 0;
//
// get allocator properties
//
ITAllocatorProperties *pAllocProperites = NULL;
hr = pTerminalMediaStream->QueryInterface(IID_ITAllocatorProperties,
(void **)&pAllocProperites);
if (FAILED(hr))
{
return hr;
}
//
// we want to know the number of samples we will be getting
//
ALLOCATOR_PROPERTIES AllocProperties;
hr = pAllocProperites->GetAllocatorProperties(&AllocProperties);
pAllocProperites->Release();
pAllocProperites = NULL;
if (FAILED(hr))
{
return hr;
}
*pnNumberOfSamples = AllocProperties.cBuffers;
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
// close the handles passed into the function and release the array of handles
//
///////////////////////////////////////////////////////////////////////////////
void CASRStream::ReleaseEvents(OUT HANDLE *pEvents, // array of events to be freed
DWORD nNumberOfEvents // number of events the array
)
{
//
// close all the handles in the array
//
for (DWORD i = 0; i < nNumberOfEvents; i++)
{
CloseHandle(pEvents[i]);
pEvents[i] = NULL;
}
//
// free the array itself
//
FreeMemory(pEvents);
pEvents = NULL;
}
//////////////////////////////////////////////////////////////////////////////
// allocate the array of events of size nNumberOfSamples
//
// return pointer to the allocated and initialized array if success
// or NULL if failed
//
///////////////////////////////////////////////////////////////////////////////
HANDLE* CASRStream::AllocateEvents(DWORD nNumberOfEvents)
{
//
// pointer to an array of event handles
//
HANDLE *pSampleReadyEvents = NULL;
pSampleReadyEvents =
(HANDLE*)AllocateMemory(sizeof(HANDLE) * nNumberOfEvents);
if (NULL == pSampleReadyEvents)
{
//Failed to allocate sample ready events.
return NULL;
}
//
// create an event for every allocated handle
//
for (DWORD i = 0; i < nNumberOfEvents; i++)
{
pSampleReadyEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == pSampleReadyEvents[i])
{
//
// close handles we have created already
//
for (DWORD j = 0; j< i; j++)
{
CloseHandle(pSampleReadyEvents[j]);
pSampleReadyEvents[j] = NULL;
}
FreeMemory(pSampleReadyEvents);
pSampleReadyEvents = NULL;
return NULL;
}
} // creating events for each sample
//
// succeded creating events. return the pointer to the array
//
return pSampleReadyEvents;
}
//////////////////////////////////////////////////////////////////////////////
// aborts and releases every sample in the array of samples of size
// nNumberOfSamples and deallocates the array itself
//
// ppStreamSamples becomes invalid when the function returns
//
///////////////////////////////////////////////////////////////////////////////
void CASRStream::ReleaseSamples(IStreamSample **ppStreamSamples,
DWORD nNumberOfSamples)
{
for (DWORD i = 0; i < nNumberOfSamples; i++)
{
ppStreamSamples[i]->CompletionStatus(COMPSTAT_WAIT |
COMPSTAT_ABORT,
INFINITE);
//
// regardless of the error code, release the sample
//
ppStreamSamples[i]->Release();
ppStreamSamples[i] = NULL;
}
FreeMemory(ppStreamSamples);
ppStreamSamples = NULL;
}
//////////////////////////////////////////////////////////////////////////////
// allocate the array of nNumberOfSamples samples, and initialize each sample
// pointer in the array with samples from the supplied stream.
//
// return pointer to the allocated and initialized array if success
// or NULL if failed
//
///////////////////////////////////////////////////////////////////////////////
IStreamSample ** CASRStream::AllocateStreamSamples(IMediaStream *pMediaStream,
DWORD nNumberOfSamples)
{
//
// allocate stream sample array
//
IStreamSample **ppStreamSamples = (IStreamSample **)
AllocateMemory( sizeof(IStreamSample*) * nNumberOfSamples );
if (NULL == ppStreamSamples)
{
return NULL;
}
//
// allocate samples from the stream and put them into the array
//
for (DWORD i = 0; i < nNumberOfSamples; i++)
{
HRESULT hr = pMediaStream->AllocateSample(0, &ppStreamSamples[i]);
if (FAILED(hr))
{
for (DWORD j = 0; j < i; j++)
{
ppStreamSamples[j]->Release();
ppStreamSamples[j] = NULL;
}
FreeMemory(ppStreamSamples);
ppStreamSamples = NULL;
return NULL;
} // failed AllocateSample()
} // allocating samples on the stream
//
// succeeded allocating samples
//
return ppStreamSamples;
}
//////////////////////////////////////////////////////////////////////////////
// call Update() on every sample of the array of stream samples to associate it
// with an event from the array of events. The events will be signaled when the
// corresponding sample has data and is ready to render to SAPI
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::AssociateEventsWithSamples(HANDLE *pSampleReadyEvents,
IStreamSample **ppStreamSamples,
DWORD nNumberOfSamples)
{
for (DWORD i = 0; i < nNumberOfSamples; i++)
{
//
// the event passed to Update will be signaled when the sample is
// filled with data
//
HRESULT hr =
ppStreamSamples[i]->Update(0, pSampleReadyEvents[i], NULL, 0);
if (FAILED(hr))
{
//
// abort the samples we have Update()'d
//
for (DWORD j = 0; j < i; j++)
{
//
// no need to check the return code here -- best effort attempt
// if failed -- too bad
//
ppStreamSamples[j]->CompletionStatus(COMPSTAT_WAIT |
COMPSTAT_ABORT,
INFINITE);
}
return hr;
} // Update() failed
} // Update()'ing all samples
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// This function writes the data portion of the sample into the SAPI
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::ReadStreamSample(IStreamSample *pStreamSample, ULONG start)
{
//
// get the sample's IMemoryData interface so we can get to the
// sample's data
//
IMemoryData *pSampleMemoryData = NULL;
HRESULT hr = pStreamSample->QueryInterface(IID_IMemoryData,
(void **)&pSampleMemoryData);
if (FAILED(hr))
{
return hr;
}
//
// get to the sample's data buffer
//
DWORD nBufferSize = 0;
BYTE *pnDataBuffer = NULL;
DWORD nActualDataSize = 0;
hr = pSampleMemoryData->GetInfo(&nBufferSize,
&pnDataBuffer,
&nActualDataSize);
if ( SUCCEEDED ( hr ) )
{
m_ulActualRead = (ULONG) nActualDataSize;
if ( nActualDataSize + start <= m_ulBufferSize )
{
//If SR engine wants more data then the media stream contains, copy it directly
//to the current buffer
memcpy ( (BYTE *)&m_pnDataBuffer[start], (BYTE *)pnDataBuffer, nActualDataSize * sizeof(BYTE));
}
else
{ // we have left over data
memcpy ( (BYTE *)&m_pnDataBuffer[start], (BYTE *)pnDataBuffer, (m_ulBufferSize - start - m_ulLeftOver ) * sizeof(BYTE));
ULONG ulLeftOver = nActualDataSize + start + m_ulLeftOver - m_ulBufferSize;
if ( m_pnLeftOverBuffer )
{
delete[] m_pnLeftOverBuffer;
m_pnLeftOverBuffer = NULL;
}
m_pnLeftOverBuffer = new BYTE[ ulLeftOver ];
if ( m_pnLeftOverBuffer == NULL )
{
hr = E_OUTOFMEMORY;
}
else
{
memcpy ( (BYTE *)m_pnLeftOverBuffer, (BYTE *)&pnDataBuffer[m_ulBufferSize - start - m_ulLeftOver], ulLeftOver * sizeof(BYTE));
}
}
}
pSampleMemoryData->Release();
pSampleMemoryData = NULL;
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// given the return code fom WaitForMultipleObjects, this function determines
// which sample was signal and returns S_OK and the id of the signaled sample
// or E_FAIL if WaitForMultipleEvents returned an error
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::GetSampleID(DWORD nWaitCode, // code from WaitForMultiple...
DWORD nNumberOfSamples, // the total number of samples
DWORD *pnSampleID) // the calculated id of the
// signaled sample
{
//
// event abandoned?
//
if ( (nWaitCode >= WAIT_ABANDONED_0) &&
(nWaitCode < WAIT_ABANDONED_0 + nNumberOfSamples) )
{
return E_FAIL;
}
//
// any other error?
//
if ( (WAIT_OBJECT_0 + nNumberOfSamples <= nWaitCode) )
{
return E_FAIL;
}
//
// which sample was signaled?
//
*pnSampleID = nWaitCode - WAIT_OBJECT_0;
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// extract samples from the terminal's media stream and render it to SR engine
//
// returns when the call is disconnected (call disconnect causes media streaming
// terminal to abort the samples
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CASRStream::RenderAudioStream(ULONG start)
{
HRESULT hr = S_OK;
DWORD nSampleID = 0;
if ( !m_bFlag )
{
//turn off the flag. Only initialize once for each connection
m_bFlag = TRUE;
// get the number of stream samples we will be using
hr = GetNumberOfSamplesOnStream(m_cpSRMediaStream, &m_nNumberOfSamples);
if (FAILED(hr))
{
return hr;
}
//
// the number of samples directly corresponds the number of events we will
// be waiting on later. WaitForMultipleObjects has a limit of
// MAXIMUM_WAIT_OBJECTS events.
//
if (m_nNumberOfSamples > MAXIMUM_WAIT_OBJECTS)
{
return E_FAIL;
}
//
// allocate events that will be signaled when each sample is ready to be
// read
//
m_pSampleReadyEvents = AllocateEvents(m_nNumberOfSamples);
if (NULL == m_pSampleReadyEvents)
{
return E_OUTOFMEMORY;
}
//
// allocate array of stream samples
//
m_ppStreamSamples = AllocateStreamSamples(m_cpSRMediaStream,
m_nNumberOfSamples);
if (NULL == m_ppStreamSamples)
{
//
// release events we have allocated
//
ReleaseEvents(m_pSampleReadyEvents, m_nNumberOfSamples);
m_pSampleReadyEvents = NULL;
return E_FAIL;
}
//
// we have the samples, we have the events.
// associate events with samples so events get signaled when the
// corresponding samples are ready to be read
//
hr = AssociateEventsWithSamples(m_pSampleReadyEvents,
m_ppStreamSamples,
m_nNumberOfSamples);
if (FAILED(hr))
{
//
// release events and samples we have allocated
//
ReleaseEvents(m_pSampleReadyEvents, m_nNumberOfSamples);
m_pSampleReadyEvents = NULL;
ReleaseSamples(m_ppStreamSamples, m_nNumberOfSamples);
m_ppStreamSamples = NULL;
return E_FAIL;
}
}
if ( SUCCEEDED ( hr ) )
{
//
// wait for the events associated with the samples
// when a samples has data, the corresponding event will be
// signaled
//
DWORD nWaitCode = WaitForMultipleObjects(m_nNumberOfSamples,
m_pSampleReadyEvents,
FALSE,
WAIT_TIME_OUT);
//
// get the id of the sample that was signaled. fail if Wait returned
// error
//
hr = GetSampleID(nWaitCode, m_nNumberOfSamples, &nSampleID);
}
//
// we filtered out all invalid error codes. so nSampleID has no
// choice but be a valid sample index.
//
_ASSERTE(nSampleID < m_nNumberOfSamples);
if ( SUCCEEDED ( hr ) )
{
//
// make sure the sample is ready to be read
//
hr = m_ppStreamSamples[nSampleID]->CompletionStatus(COMPSTAT_WAIT, 0);
}
//
// we have the sample that was signaled and which is now ready to be
// read. Record the sample.
//
if ( SUCCEEDED ( hr ) )
{
hr = ReadStreamSample(m_ppStreamSamples[nSampleID], start);
}
//
// we are done with this sample. return it to the source stream
// to be refilled with data
//
if ( SUCCEEDED ( hr ) )
{
hr = m_ppStreamSamples[nSampleID]->Update(0, m_pSampleReadyEvents[nSampleID],NULL,0);
}
if (FAILED(hr))
{
//
// release events and samples we have allocated
//
ReleaseEvents(m_pSampleReadyEvents, m_nNumberOfSamples);
m_pSampleReadyEvents = NULL;
ReleaseSamples(m_ppStreamSamples, m_nNumberOfSamples);
m_ppStreamSamples = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// use win32 heap api to allocate memory on the application's heap
// and zero the allocated memory
//
///////////////////////////////////////////////////////////////////////////////
void *CASRStream::AllocateMemory(SIZE_T nMemorySize)
{
//
// use HeapAlloc to allocate and clear memory
//
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nMemorySize);
}
//////////////////////////////////////////////////////////////////////////////
//
// use win32 heap api to free memory previously allocated on the application's
// heap
//
///////////////////////////////////////////////////////////////////////////////
void CASRStream::FreeMemory(void *pMemory)
{
//
// get size of the allocated memory
//
DWORD nMemorySize = HeapSize(GetProcessHeap(), 0, pMemory);
//
// fill memory with 0xdd's before freeing, so it is easier to debug
// failures caused by using pointer to deallocated memory
//
if (NULL != pMemory)
{
FillMemory(pMemory, nMemorySize, 0xdd);
}
//
// use HeapFree to free memory. use return code to log the result, but
// do not return it to the caller
//
BOOL bFreeSuccess = HeapFree(GetProcessHeap(), 0, pMemory);
if (FALSE == bFreeSuccess)
{
//
// if this assertion fires, it is likely there is a problem with the
// memory we are trying to deallocate. Was it allocated using heapalloc
// and on the same heap? Is this a valid pointer?
//
_ASSERTE(FALSE);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// SR engine calls the Read to get the audio through SAPI
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CASRStream::Read(void * pv, ULONG cb, ULONG *pcbRead)
{
m_hCritSec.Lock();
ULONG start;
LONG len = cb;
BYTE * pnData = (BYTE *)pv;
HRESULT hr = S_OK;
if (m_bPurgeFlag)
{
//the app wants to stop rendering the stream
m_bPurgeFlag = FALSE;
ReleaseEvents(m_pSampleReadyEvents, m_nNumberOfSamples);
m_pSampleReadyEvents = NULL;
ReleaseSamples(m_ppStreamSamples, m_nNumberOfSamples);
m_ppStreamSamples = NULL;
*pcbRead = 0;
m_hCritSec.Unlock();
return hr;
}
//allocate the buffer
if ( m_pnDataBuffer == NULL )
{
m_ulBufferSize = cb;
m_pnDataBuffer = new BYTE [ m_ulBufferSize ];
if ( m_pnDataBuffer == NULL )
{
m_hCritSec.Unlock();
return E_OUTOFMEMORY;
}
}
else if ( m_ulBufferSize != cb)
{
//allocate a new buffer
delete []m_pnDataBuffer;
m_ulBufferSize = cb;
m_pnDataBuffer = new BYTE [ m_ulBufferSize ];
if ( m_pnDataBuffer == NULL )
{
m_hCritSec.Unlock();
return E_OUTOFMEMORY;
}
}
//copy the data left from the previous Read().
if ( m_pnLeftOverBuffer && m_ulLeftOver > 0 )
{
memcpy ( pnData, (BYTE *)&m_pnLeftOverBuffer[m_ulLeftOverPos], min(cb, m_ulLeftOver) * sizeof(BYTE) );
}
//Read the data from MST
start = 0;
len = cb - m_ulLeftOver;
while ( SUCCEEDED ( hr ) && len > 0 )
{
hr = RenderAudioStream( start );
start += m_ulActualRead;
len = cb - m_ulLeftOver - start;
}
if ( SUCCEEDED ( hr ) )
{
// tell SR engine, we have cb bytes available.
*pcbRead = cb;
// Copy the rest of data outside and set the postion and number of bytes left in
// the left over data buffer
long lRead = *pcbRead - m_ulLeftOver;
if ( lRead > 0 )
{
m_ulLeftOverPos = 0;
memcpy ( &pnData[m_ulLeftOver], (BYTE *)m_pnDataBuffer, (*pcbRead - m_ulLeftOver) * sizeof(BYTE) );
}
else
{
m_ulLeftOverPos += cb;
}
if ( len < 0 )
{
m_ulLeftOver = -len;
}
else
{
m_ulLeftOverPos = 0;
m_ulLeftOver = 0;
}
}
m_hCritSec.Unlock();
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// This method is called by SAPI
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CASRStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition)
{
// We only accept queries for the current stream position
if (STREAM_SEEK_CUR != dwOrigin || dlibMove.QuadPart)
{
return E_INVALIDARG;
}
// Validate the OUT parameter
if (SPIsBadWritePtr(plibNewPosition, sizeof(ULARGE_INTEGER)))
{
return E_POINTER;
}
m_hCritSec.Lock();
plibNewPosition->QuadPart = (LONG)dlibMove.LowPart;
m_hCritSec.Unlock();
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
// This method is called by SAPI
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CASRStream::GetFormat(GUID * pFmtId, WAVEFORMATEX ** ppCoMemWaveFormatEx)
{
m_hCritSec.Lock();
HRESULT hr = S_OK;
hr = m_StreamFormat.ParamValidateCopyTo( pFmtId, ppCoMemWaveFormatEx );
m_hCritSec.Unlock();
return hr;
}