Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

859 lines
23 KiB

//
// Audio playback function impl.
//
//
#include "private.h"
#include "sapilayr.h"
#include "playback.h"
#include "immxutil.h"
#include "propstor.h"
#include "hwxink.h"
//
// ctor/dtor
//
CSapiPlayBack::CSapiPlayBack(CSapiIMX *psi)
{
m_psi = psi;
m_pIC = NULL;
m_cRef = 1;
}
CSapiPlayBack::~CSapiPlayBack()
{
SafeRelease(m_pIC);
}
//
// IUnknown
//
STDMETHODIMP CSapiPlayBack::QueryInterface(REFGUID riid, LPVOID *ppvObj)
{
Assert(ppvObj);
*ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_ITfFnPlayBack))
{
*ppvObj = SAFECAST(this, CSapiPlayBack *);
}
if (*ppvObj)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CSapiPlayBack::AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CSapiPlayBack::Release(void)
{
long cr;
cr = InterlockedDecrement(&m_cRef);
Assert(cr >= 0);
if (cr == 0)
{
delete this;
}
return cr;
}
//
// ITfFunction
//
STDMETHODIMP CSapiPlayBack::GetDisplayName(BSTR *pbstrName)
{
HRESULT hr = E_INVALIDARG;
if (pbstrName)
{
*pbstrName = SysAllocString(L"PlayBack Voice");
if (!*pbstrName)
hr = E_OUTOFMEMORY;
else
hr = S_OK;
}
return hr;
}
STDMETHODIMP CSapiPlayBack::IsEnabled(BOOL *pfEnable)
{
*pfEnable = TRUE;
return S_OK;
}
//
// CSapiPlayBack::FindSoundRange
//
// synopsis - finds matching range with sound data for the given
// text range
// callar is responsible for releasing the returned range object
//
HRESULT
CSapiPlayBack::FindSoundRange(TfEditCookie ec, ITfRange *pRange, ITfProperty **ppProp, ITfRange **ppPropRange, ITfRange **ppSndRange)
{
ITfProperty *pProp = NULL;
ITfRange *pPropRange = NULL;
Assert(pRange);
*ppProp = NULL;
HRESULT hr = m_pIC->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &pProp);
if (SUCCEEDED(hr) && pProp)
{
ITfRange *pRangeSize0;
pRange->Clone(&pRangeSize0);
pRangeSize0->Collapse(ec, TF_ANCHOR_START); // findrange would fail if this failed
hr = pProp->FindRange(ec, pRangeSize0, &pPropRange, TF_ANCHOR_START);
pRangeSize0->Release();
*ppProp = pProp;
pProp->AddRef();
}
if (SUCCEEDED(hr) && pPropRange)
{
ITfRange *pRangeForSound;
hr = pPropRange->Clone(&pRangeForSound);
if (ppSndRange && SUCCEEDED(hr))
{
hr = pRangeForSound->Clone(ppSndRange);
pRangeForSound->Release();
}
if (ppPropRange && pPropRange && SUCCEEDED(hr))
{
hr = pPropRange->Clone(ppPropRange);
}
SafeRelease(pPropRange);
}
else
{
if (ppPropRange)
*ppPropRange = NULL;
if (ppSndRange)
*ppSndRange = NULL;
}
SafeRelease(pProp);
return hr;
}
// ITfFnPlayBack
//
//
STDAPI CSapiPlayBack::QueryRange(ITfRange *pRange, ITfRange **ppNewRange, BOOL *pfPlayable)
{
//
// always ok because of TTS.
//
if (ppNewRange)
{
pRange->Clone(ppNewRange);
}
*pfPlayable = TRUE;
return S_OK;
}
// ITfFnPlayBack
//
// play the audio stream attached to the range
// TODO: use TTS if:
// 1) the given range has less text
// then the stream prop
// 2) the audio property is not found
//
STDAPI CSapiPlayBack::Play(ITfRange *pRange)
{
HRESULT hr = E_OUTOFMEMORY;
if ( !m_psi )
return E_FAIL;
SafeRelease(m_pIC);
m_psi->GetFocusIC(&m_pIC);
if (m_pIC)
{
CPlayBackEditSession *pes;
if (pes = new CPlayBackEditSession(this, m_pIC))
{
pes->_SetEditSessionData(ESCB_PLAYBK_PLAYSND, NULL, 0);
pes->_SetRange(pRange);
m_pIC->RequestEditSession(m_psi->_GetId(), pes, TF_ES_READ /*| TF_ES_SYNC */, &hr);
pes->Release();
}
}
return hr;
}
HRESULT CSapiPlayBack::_PlaySound(TfEditCookie ec, ITfRange *pRange)
{
HRESULT hr = S_OK;
LONG l;
ITfRange *pRangeForSound = NULL;
ITfRange *pRangeCurrent = NULL;
BOOL fEmpty;
ULONG ulStart, ulcElem;
// playback one phrase for no selection
pRange->IsEmpty(ec, &fEmpty);
if (fEmpty)
{
ITfProperty *pProp = NULL;
hr = FindSoundRange(ec, pRange, &pProp, NULL, &pRangeForSound);
if (SUCCEEDED(hr) && pRangeForSound)
{
pRange->ShiftStartToRange(ec, pRangeForSound, TF_ANCHOR_START);
pRange->ShiftEndToRange(ec, pRangeForSound, TF_ANCHOR_END);
pRangeForSound->Release();
}
SafeReleaseClear(pProp);
}
// setting up a range object on our own
hr = pRange->Clone(&pRangeCurrent);
while(SUCCEEDED(hr) && pRangeCurrent->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
{
ITfProperty *pProp = NULL;
// get the first dictated range
CDictRange *pDictRange = new CDictRange( );
if ( pDictRange )
{
hr = pDictRange->Initialize(ec, m_pIC, pRangeCurrent);
if ( SUCCEEDED(hr) && pDictRange->IsDictRangeFound( ))
{
// Found a dictated range.
pRangeForSound = pDictRange->GetDictRange( );
ulStart = pDictRange->GetStartElem( );
ulcElem = pDictRange->GetNumElem( );
pProp = pDictRange->GetProp( );
// if start anchor of pRangeForSound is larger than start anchor of pRangeCurrent,
// we need to send the text between these two anchors to spVoice first.
hr = pRangeCurrent->CompareStart(ec, pRangeForSound, TF_ANCHOR_START, &l);
if ( SUCCEEDED(hr) && l < 0 )
{
CComPtr<ITfRange> cpRangeText;
hr = pRangeCurrent->Clone(&cpRangeText);
if ( SUCCEEDED(hr) )
{
hr = cpRangeText->ShiftEndToRange(ec, pRangeForSound, TF_ANCHOR_START);
}
if ( SUCCEEDED(hr) )
hr = PlayTextData(ec, cpRangeText);
}
// Then play the audio data.
if (SUCCEEDED(hr) )
{
hr = PlayAudioData(ec, pRangeForSound, pProp, ulStart, ulcElem);
}
}
else
{
// There is no dictated phrase in this range.
// just speak all the rest text at once.
SafeRelease(pRangeForSound);
hr = pRangeCurrent->Clone(&pRangeForSound);
if ( SUCCEEDED(hr) )
hr = PlayTextData(ec, pRangeCurrent);
}
SafeReleaseClear(pProp);
// next range
pRangeCurrent->ShiftStartToRange(ec, pRangeForSound, TF_ANCHOR_END);
pRangeForSound->Release();
delete pDictRange;
}
else
hr = E_OUTOFMEMORY;
} // end of while
SafeRelease(pRangeCurrent);
return hr;
}
//
// CSapiPlayBack::PlayTextData
//
// Playing the text by default voice
//
// This is for Non-Dictated text. pRangeText contains all the Non-Dictated text
const GUID GUID_TS_SERVICE_DATAOBJECT={0x6086fbb5, 0xe225, 0x46ce, {0xa7, 0x70, 0xc1, 0xbb, 0xd3, 0xe0, 0x5d, 0x7b}};
const IID IID_ILineInfo = {0x9C1C5AD5,0xF22F,0x4DE4,{0xB4,0x53,0xA2,0xCC,0x48,0x2E,0x7C,0x33}};
HRESULT CSapiPlayBack::GetInkObjectText(TfEditCookie ec, ITfRange *pRange, BSTR *pbstrWord,UINT *pcchWord)
{
HRESULT hr = S_OK;
CComPtr<IDataObject> cpDataObject;
CComPtr<ILineInfo> cpLineInfo;
if ( !pRange || !pbstrWord || !pcchWord )
return E_FAIL;
*pbstrWord = NULL;
*pcchWord = 0;
hr = pRange->GetEmbedded(ec,
GUID_TS_SERVICE_DATAOBJECT,
IID_IDataObject,
(IUnknown **)&cpDataObject);
if ( hr == S_OK )
{
hr = cpDataObject->QueryInterface(IID_ILineInfo, (void **)&cpLineInfo);
}
if ( hr == S_OK && cpLineInfo)
{
hr = cpLineInfo->TopCandidates(0, pbstrWord, pcchWord, 0, 0);
}
else
{
// it doesn't support ILineInfoi or IDataObject.
// But it is not an error, the code should not terminate here.
hr = S_OK;
}
return hr;
}
HRESULT CSapiPlayBack::PlayTextData(TfEditCookie ec, ITfRange *pRangeText)
{
HRESULT hr = S_OK;
CComPtr<ITfRange> cpRangeCloned;
BOOL fEmpty = TRUE;
CSpDynamicString dstrText;
CSpTask *psp;
WCHAR sz[128];
ULONG iIndex = 0;
ULONG ucch;
if ( m_psi == NULL ) return E_FAIL;
if ( !pRangeText ) return E_INVALIDARG;
hr = pRangeText->Clone(&cpRangeCloned);
// Get the text from the pRangeCloned
while(S_OK == hr && (S_OK == cpRangeCloned->IsEmpty(ec, &fEmpty)) && !fEmpty)
{
WCHAR szEach[2];
BOOL fHitInkObject = FALSE;
BSTR bstr = NULL;
fHitInkObject = FALSE;
hr = cpRangeCloned->GetText(ec, TF_TF_MOVESTART, szEach, ARRAYSIZE(szEach)-1, &ucch);
if (S_OK == hr && ucch > 0)
{
szEach[ucch] = L'\0';
if ( szEach[0] == TF_CHAR_EMBEDDED )
{
// This is an embedded object.
// Check to see if it is Ink Object. currently we support only Inkobject TTSed
CComPtr<ITfRange> cpRangeTmp;
// Shift the start anchor back by 1 char.
hr = cpRangeCloned->Clone(&cpRangeTmp);
if ( hr == S_OK )
{
LONG cch;
hr = cpRangeTmp->ShiftStart(ec, -1, &cch, 0 );
}
if ( hr == S_OK )
hr = GetInkObjectText(ec, cpRangeTmp, &bstr,(UINT *)&ucch);
if ( hr == S_OK && ucch > 0 && bstr)
fHitInkObject = TRUE;
}
if ( fHitInkObject)
{
// Fill the previous text to dstrText.
if ( iIndex > 0 )
{
sz[iIndex] = L'\0';
dstrText.Append(sz);
iIndex = 0;
}
// Fill this Ink Object text
dstrText.Append(bstr);
SysFreeString(bstr);
}
else
{
if ( iIndex >= ARRAYSIZE(sz)-1 )
{
sz[ARRAYSIZE(sz)-1] = L'\0';
dstrText.Append(sz);
iIndex=0;
}
sz[iIndex] = szEach[0];
iIndex ++;
}
}
else
{
// hr is not S_OK or ucch is zero.
// we just want to exit here.
TraceMsg(TF_GENERAL, "PlayTextData: ucch=%d", ucch);
break;
}
}
// Fill the last run of text.
if ( iIndex > 0 )
{
sz[iIndex] = L'\0';
dstrText.Append(sz);
iIndex = 0;
}
// Play the text through TTS service.
if ((hr == S_OK) && dstrText)
{
hr = m_psi->GetSpeechTask(&psp);
if (hr == S_OK)
{
hr = psp->_SpeakText((WCHAR *)dstrText);
psp->Release();
}
}
return hr;
}
//
// CSapiPlayBack::PlayAudioData
//
// Playing the sound by Aduio Data
//
// This is for Dictated text. pRangeAudio keeps the dictated text range.
HRESULT CSapiPlayBack::PlayAudioData(TfEditCookie ec, ITfRange *pRangeAudio, ITfProperty *pProp, ULONG ulStart, ULONG ulcElem )
{
HRESULT hr = S_OK;
CSpTask *psp;
VARIANT var;
if ( m_psi == NULL ) return E_FAIL;
if ( !pRangeAudio || !pProp ) return E_INVALIDARG;
hr = pProp->GetValue(ec, pRangeAudio, &var);
if (S_OK == hr)
{
Assert(var.vt == VT_UNKNOWN);
IUnknown *punk = var.punkVal;
if (punk)
{
// get the wrapper object
CRecoResultWrap *pWrap;
hr = punk->QueryInterface(IID_PRIV_RESULTWRAP, (void **)&pWrap);
if (S_OK == hr)
{
// hr = pWrap->_SpeakAudio(0,0); // better calculate the length accurately
CComPtr<ISpRecoResult> cpResult;
hr = pWrap->GetResult(&cpResult);
if (S_OK == hr)
{
CComPtr<ISpStreamFormat> cpStream;
if ((ulStart == 0) && (ulcElem == 0))
{
// We don't set the start element and number of elems.
// it must be for the whole recoWrap.
ulStart = pWrap->GetStart();
ulcElem = pWrap->GetNumElements();
}
hr = cpResult->GetAudio(ulStart, ulcElem, &cpStream);
if ( S_OK == hr )
{
hr = m_psi->GetSpeechTask(&psp);
if (SUCCEEDED(hr))
{
hr = psp->_SpeakAudio(cpStream);
psp->Release();
}
}
}
pWrap->Release();
}
punk->Release();
}
}
return hr;
}
//
// CSapiPlayBack::GetDataID
//
// This method is incomplete for now, until we figure out
// the usage of dataid
//
HRESULT CSapiPlayBack::GetDataID(BSTR bstrCandXml, int nId, GUID *pguidData)
{
// 1) parse the list and find RANGEDATA
IXMLDOMNodeList *pNList = NULL;
IXMLDOMElement *pElm = NULL;
IXMLDOMNode *pNode;
VARIANT_BOOL bSuccessful;
HRESULT hr = EnsureIXMLDoc();
if (SUCCEEDED(hr))
{
hr = m_pIXMLDoc->loadXML(bstrCandXml, &bSuccessful);
}
// get <RANGEDATA> element
if (SUCCEEDED(hr) && bSuccessful)
{
BSTR bstrRange = SysAllocString(L"RANGEDATA");
if (bstrRange)
{
hr = m_pIXMLDoc->getElementsByTagName(bstrRange, &pNList);
SysFreeString(bstrRange);
}
else
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr) && pNList)
{
if (pNList->nextNode(&pNode) == S_OK)
hr = pNode->QueryInterface(IID_IXMLDOMElement,(void **)&pElm);
pNList->Release();
}
// then <MICROSOFTSPEECH> ...
if (SUCCEEDED(hr) && pElm)
{
BSTR bstrSpchPriv = SysAllocString(L"MICROSOFTSPEECH");
if (bstrSpchPriv)
{
hr = pElm->getElementsByTagName(bstrSpchPriv, &pNList);
SysFreeString(bstrSpchPriv);
}
else
hr = E_OUTOFMEMORY;
pElm->Release();
}
if (SUCCEEDED(hr) && pNList)
{
if (pNList->nextNode(&pNode) == S_OK)
hr = pNode->QueryInterface(IID_IXMLDOMElement,(void **)&pElm);
pNList->Release();
}
// <DATAID>
// right now assuming the speech element
// is put at the level of <rangedata>
// ignoring nId here
if (SUCCEEDED(hr) && pElm)
{
BSTR bstrDataId = SysAllocString(L"DATAID");
if (bstrDataId)
{
hr = pElm->getElementsByTagName(bstrDataId, &pNList);
SysFreeString(bstrDataId);
}
else
hr = E_OUTOFMEMORY;
pElm->Release();
}
// impl later...
// so, here we'll get the real dataid and be done
if (SUCCEEDED(hr) && pNList)
{
pNList->Release();
}
return hr;
}
HRESULT CSapiPlayBack::_PlaySoundSelection(TfEditCookie ec, ITfContext *pic)
{
ITfRange *pSelection;
HRESULT hr = E_FAIL;
if (GetSelectionSimple(ec, pic, &pSelection) == S_OK)
{
hr = _PlaySound(ec, pSelection);
pSelection->Release();
}
return hr;
}
HRESULT CSapiPlayBack::EnsureIXMLDoc(void)
{
HRESULT hr = S_OK;
IXMLDOMDocument *pIXMLDocument;
if (!m_pIXMLDoc)
{
if (SUCCEEDED(hr = CoCreateInstance(CLSID_DOMDocument,
NULL,
CLSCTX_INPROC_SERVER,
IID_IXMLDOMDocument,
(void **) &pIXMLDocument)))
{
m_pIXMLDoc = pIXMLDocument;
}
}
return hr;
}
//
// Implementation for Class CDictRange
//
//
//
// ctor/dtor
//
CDictRange::CDictRange( ) : CBestPropRange( )
{
m_fFoundDictRange = FALSE;
m_pProp = NULL;
m_pDictatRange = NULL;
m_ulStart = 0;
m_ulcElem = 0;
}
CDictRange::~CDictRange( )
{
SafeRelease(m_pProp);
SafeRelease(m_pDictatRange);
}
ITfProperty *CDictRange::GetProp( )
{
if ( m_pProp )
m_pProp->AddRef( );
return m_pProp;
}
ITfRange *CDictRange::GetDictRange( )
{
if ( m_pDictatRange )
m_pDictatRange->AddRef( );
return m_pDictatRange;
}
HRESULT CDictRange::_GetOverlapRange(TfEditCookie ec, ITfRange *pRange1, ITfRange *pRange2, ITfRange **ppOverlapRange)
{
HRESULT hr = E_FAIL;
LONG l1=0;
LONG l2=0;
CComPtr<ITfRange> cpRangeOverlap;
if ( !pRange1 || !pRange2 || !ppOverlapRange )
return E_INVALIDARG;
*ppOverlapRange = NULL;
// Get the overlapping part of pRange and cpPropRange, and then calculate the
// best matched propRange.
hr = pRange1->Clone( &cpRangeOverlap );
if ( SUCCEEDED(hr) )
hr = pRange1->CompareStart(ec, pRange2, TF_ANCHOR_START, &l1);
if ( SUCCEEDED(hr) && l1 < 0 )
{
// Start anchor of pRange1 is before the start anchor of the
// pRange2.
hr = cpRangeOverlap->ShiftStartToRange(ec, pRange2, TF_ANCHOR_START);
}
if ( SUCCEEDED(hr) )
hr = cpRangeOverlap->CompareEnd(ec, pRange2, TF_ANCHOR_END, &l2);
if ( SUCCEEDED(hr) && l2 > 0)
{
// End anchor of cpRangeOverlap is after the end anchor of the pRange2.
hr = cpRangeOverlap->ShiftEndToRange(ec, pRange2, TF_ANCHOR_END);
}
if ( SUCCEEDED(hr) )
hr = cpRangeOverlap->Clone(ppOverlapRange);
return hr;
}
//
// CDictRange::Initialize
//
// synopsis - Get the necessary input parameters, (ec, pic, and pRange),
// and then search the first dictated range inside the given range.
// if it finds the first dictated range, update the related
// data memebers.
//
// if it doesn't find any dictated range, mark as not found.
//
HRESULT CDictRange::Initialize(TfEditCookie ec, ITfContext *pic, ITfRange *pRange)
{
CComPtr<ITfRange> cpPropRange = NULL;
HRESULT hr;
m_fFoundDictRange = FALSE;
m_pProp = NULL;
m_pDictatRange = NULL;
m_ulStart = 0;
m_ulcElem = 0;
if ( !pic || !pRange ) return E_INVALIDARG;
hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &m_pProp);
if ( SUCCEEDED(hr) && m_pProp)
{
CComPtr<ITfRange> cpRangeCurrent;
LONG cch;
hr = pRange->Clone(&cpRangeCurrent);
if ( SUCCEEDED(hr) )
{
hr = cpRangeCurrent->Collapse(ec, TF_ANCHOR_START);
}
while ( SUCCEEDED(hr) && !m_fFoundDictRange )
{
cpPropRange.Release( );
hr = m_pProp->FindRange(ec, cpRangeCurrent, &cpPropRange, TF_ANCHOR_START);
if ( SUCCEEDED(hr) && cpPropRange )
{
// Found the first Dictated phrase.
CComPtr<ITfRange> cpRangeOverlap;
// Get the overlapping part of pRange and cpPropRange, and then calculate the
// best matched propRange.
hr = _GetOverlapRange(ec, pRange, cpPropRange, &cpRangeOverlap);
if ( SUCCEEDED(hr) )
{
// Calculate the best matched propRange and ulStart, ulcElem.
cpPropRange.Release( );
hr = _ComputeBestFitPropRange(ec, m_pProp, cpRangeOverlap, &cpPropRange, &m_ulStart, &m_ulcElem);
}
if (SUCCEEDED(hr) && (m_ulcElem > 0))
{
m_fFoundDictRange = TRUE;
}
}
if ( SUCCEEDED(hr) && !m_fFoundDictRange)
{
// cpRangeCurrent shift forward one character. and try again.
hr = cpRangeCurrent->ShiftStart(ec, 1, &cch, NULL);
if ( SUCCEEDED(hr) && (cch==0))
{
// Hit a region or the end of doc.
// check to see if there is more region.
BOOL fNoRegion = TRUE;
hr = cpRangeCurrent->ShiftStartRegion(ec, TF_SD_FORWARD, &fNoRegion);
if ( fNoRegion )
{
TraceMsg(TF_GENERAL, "Reach to end of doc");
break;
}
else
TraceMsg(TF_GENERAL, "Shift over to another region!");
}
if ( SUCCEEDED(hr) )
{
// check to see if cpRangeCurrent is beyond of the pRange.
hr = pRange->CompareEnd(ec, cpRangeCurrent, TF_ANCHOR_END, &cch);
if ( SUCCEEDED(hr) && cch <= 0 )
{
// cpRangeCurrent Now is beyond of specified range, exit while statement.
TraceMsg(TF_GENERAL, "reach to the end of original range");
break;
}
}
}
}
}
if (SUCCEEDED(hr) && cpPropRange && m_fFoundDictRange)
{
hr = cpPropRange->Clone(&m_pDictatRange);
}
return hr;
}