Sweden-Number/dlls/quartz/audren.c

849 lines
18 KiB
C

/*
* Audio Renderer (CLSID_AudioRender)
*
* FIXME
* - implements IRefereneceClock.
* - implements seeking.
*
* hidenori@a2.ctktv.ne.jp
*/
#include "config.h"
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "mmsystem.h"
#include "winerror.h"
#include "strmif.h"
#include "control.h"
#include "vfwmsgs.h"
#include "uuids.h"
#include "evcode.h"
#include "debugtools.h"
DEFAULT_DEBUG_CHANNEL(quartz);
#include "quartz_private.h"
#include "audren.h"
static const WCHAR QUARTZ_AudioRender_Name[] =
{ 'A','u','d','i','o',' ','R','e','n','d','e','r',0 };
static const WCHAR QUARTZ_AudioRenderPin_Name[] =
{ 'I','n',0 };
/***************************************************************************
*
* CAudioRendererImpl waveOut methods (internal)
*
*/
static
HRESULT QUARTZ_HRESULT_From_MMRESULT( MMRESULT mr )
{
HRESULT hr = E_FAIL;
switch ( mr )
{
case MMSYSERR_NOERROR:
hr = S_OK;
break;
case MMSYSERR_NOMEM:
hr = E_OUTOFMEMORY;
break;
}
return hr;
}
void CAudioRendererImpl_waveOutEventCallback(
HWAVEOUT hwo, UINT uMsg,
DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 )
{
CAudioRendererImpl* This = (CAudioRendererImpl*)dwInstance;
if ( uMsg == WOM_DONE )
SetEvent( This->m_hEventRender );
}
static
void CAudioRendererImpl_waveOutReset(
CAudioRendererImpl* This )
{
if ( !This->m_fWaveOutInit )
return;
waveOutReset( This->m_hWaveOut );
SetEvent( This->m_hEventRender );
}
static
void CAudioRendererImpl_waveOutUninit(
CAudioRendererImpl* This )
{
DWORD i;
TRACE("(%p)\n",This);
if ( !This->m_fWaveOutInit )
return;
waveOutReset( This->m_hWaveOut );
SetEvent( This->m_hEventRender );
for ( i = 0; i < WINE_QUARTZ_WAVEOUT_COUNT; i++ )
{
if ( This->m_hdr[i].dwFlags & WHDR_PREPARED )
{
waveOutUnprepareHeader(
This->m_hWaveOut,
&This->m_hdr[i], sizeof(WAVEHDR) );
This->m_hdr[i].dwFlags = 0;
}
if ( This->m_hdr[i].lpData != NULL )
{
QUARTZ_FreeMem( This->m_hdr[i].lpData );
This->m_hdr[i].lpData = NULL;
}
}
waveOutClose( This->m_hWaveOut );
This->m_hWaveOut = (HWAVEOUT)NULL;
if ( This->m_hEventRender != (HANDLE)NULL )
{
CloseHandle( This->m_hEventRender );
This->m_hEventRender = (HANDLE)NULL;
}
This->m_fWaveOutInit = FALSE;
}
static
HRESULT CAudioRendererImpl_waveOutInit(
CAudioRendererImpl* This, WAVEFORMATEX* pwfx )
{
MMRESULT mr;
HRESULT hr;
DWORD i;
DWORD dwBlockSize;
if ( This->m_fWaveOutInit )
return E_UNEXPECTED;
if ( pwfx == NULL )
return E_POINTER;
if ( pwfx->nBlockAlign == 0 )
return E_INVALIDARG;
This->m_hEventRender = (HANDLE)NULL;
This->m_hWaveOut = (HWAVEOUT)NULL;
This->m_dwBlockSize = 0;
This->m_phdrCur = NULL;
ZeroMemory( &This->m_hdr, sizeof(This->m_hdr) );
mr = waveOutOpen(
&This->m_hWaveOut, WAVE_MAPPER, pwfx,
(DWORD)CAudioRendererImpl_waveOutEventCallback, (DWORD)This,
CALLBACK_FUNCTION );
hr = QUARTZ_HRESULT_From_MMRESULT( mr );
if ( FAILED(hr) )
return hr;
This->m_fWaveOutInit = TRUE;
This->m_hEventRender = CreateEventA(
NULL, TRUE, TRUE, NULL );
if ( This->m_hEventRender == (HANDLE)NULL )
{
hr = E_OUTOFMEMORY;
goto err;
}
dwBlockSize = pwfx->nAvgBytesPerSec / pwfx->nBlockAlign;
if ( dwBlockSize == 0 )
dwBlockSize = 1;
dwBlockSize *= pwfx->nBlockAlign;
This->m_dwBlockSize = dwBlockSize;
for ( i = 0; i < WINE_QUARTZ_WAVEOUT_COUNT; i++ )
{
This->m_hdr[i].lpData = (CHAR*)QUARTZ_AllocMem( dwBlockSize );
if ( This->m_hdr[i].lpData == NULL )
{
hr = E_OUTOFMEMORY;
goto err;
}
mr = waveOutPrepareHeader(
This->m_hWaveOut,
&This->m_hdr[i], sizeof(WAVEHDR) );
hr = QUARTZ_HRESULT_From_MMRESULT( mr );
if ( FAILED(hr) )
goto err;
This->m_hdr[i].dwFlags |= WHDR_DONE;
This->m_hdr[i].dwBufferLength = dwBlockSize;
This->m_hdr[i].dwUser = i;
}
return S_OK;
err:
CAudioRendererImpl_waveOutUninit(This);
return hr;
}
static
WAVEHDR* CAudioRendererImpl_waveOutGetBuffer(
CAudioRendererImpl* This )
{
DWORD i;
if ( !This->m_fWaveOutInit )
return NULL;
if ( This->m_phdrCur != NULL )
return This->m_phdrCur;
for ( i = 0; i < WINE_QUARTZ_WAVEOUT_COUNT; i++ )
{
if ( This->m_hdr[i].dwFlags & WHDR_DONE )
{
This->m_phdrCur = &(This->m_hdr[i]);
This->m_phdrCur->dwFlags &= ~WHDR_DONE;
This->m_phdrCur->dwBufferLength = 0;
return This->m_phdrCur;
}
}
return NULL;
}
static
HRESULT CAudioRendererImpl_waveOutWriteData(
CAudioRendererImpl* This,
const BYTE* pData, DWORD cbData, DWORD* pcbWritten )
{
DWORD cbAvail;
*pcbWritten = 0;
if ( !This->m_fWaveOutInit )
return E_UNEXPECTED;
if ( cbData == 0 )
return S_OK;
if ( CAudioRendererImpl_waveOutGetBuffer(This) == NULL )
return S_FALSE;
cbAvail = This->m_dwBlockSize - This->m_phdrCur->dwBufferLength;
if ( cbAvail > cbData )
cbAvail = cbData;
memcpy( This->m_phdrCur->lpData, pData, cbAvail );
pData += cbAvail;
cbData -= cbAvail;
This->m_phdrCur->dwBufferLength += cbAvail;
*pcbWritten = cbAvail;
return S_OK;
}
static
HRESULT CAudioRendererImpl_waveOutFlush(
CAudioRendererImpl* This )
{
MMRESULT mr;
HRESULT hr;
if ( !This->m_fWaveOutInit )
return E_UNEXPECTED;
if ( This->m_phdrCur == NULL )
return E_UNEXPECTED;
if ( This->m_phdrCur->dwBufferLength == 0 )
return S_OK;
mr = waveOutWrite(
This->m_hWaveOut,
This->m_phdrCur, sizeof(WAVEHDR) );
hr = QUARTZ_HRESULT_From_MMRESULT( mr );
if ( FAILED(hr) )
return hr;
This->m_phdrCur = NULL;
return S_OK;
}
static
HRESULT CAudioRendererImpl_waveOutGetVolume(
CAudioRendererImpl* This,
DWORD* pdwLeft, DWORD* pdwRight )
{
MMRESULT mr;
HRESULT hr;
DWORD dwVol;
if ( !This->m_fWaveOutInit )
return E_UNEXPECTED;
mr = waveOutGetVolume(
This->m_hWaveOut, &dwVol );
hr = QUARTZ_HRESULT_From_MMRESULT( mr );
if ( FAILED(hr) )
return hr;
*pdwLeft = LOWORD(dwVol);
*pdwRight = HIWORD(dwVol);
return NOERROR;
}
static
HRESULT CAudioRendererImpl_waveOutSetVolume(
CAudioRendererImpl* This,
DWORD dwLeft, DWORD dwRight )
{
MMRESULT mr;
DWORD dwVol;
if ( !This->m_fWaveOutInit )
return E_UNEXPECTED;
dwVol = dwLeft | (dwRight<<16);
mr = waveOutSetVolume(
This->m_hWaveOut, dwVol );
return QUARTZ_HRESULT_From_MMRESULT( mr );
}
/***************************************************************************
*
* CAudioRendererImpl methods
*
*/
static HRESULT CAudioRendererImpl_OnActive( CBaseFilterImpl* pImpl )
{
CAudioRendererImpl_THIS(pImpl,basefilter);
WAVEFORMATEX* pwfx;
FIXME( "(%p)\n", This );
if ( This->pPin->pin.pmtConn == NULL )
return NOERROR;
pwfx = (WAVEFORMATEX*)This->pPin->pin.pmtConn->pbFormat;
if ( pwfx == NULL )
return E_FAIL;
This->m_fInFlush = FALSE;
return CAudioRendererImpl_waveOutInit(This,pwfx);
}
static HRESULT CAudioRendererImpl_OnInactive( CBaseFilterImpl* pImpl )
{
CAudioRendererImpl_THIS(pImpl,basefilter);
FIXME( "(%p)\n", This );
CAudioRendererImpl_waveOutUninit(This);
This->m_fInFlush = FALSE;
TRACE("returned\n" );
return NOERROR;
}
static const CBaseFilterHandlers filterhandlers =
{
CAudioRendererImpl_OnActive, /* pOnActive */
CAudioRendererImpl_OnInactive, /* pOnInactive */
NULL, /* pOnStop */
};
/***************************************************************************
*
* CAudioRendererPinImpl methods
*
*/
static HRESULT CAudioRendererPinImpl_CheckMediaType( CPinBaseImpl* pImpl, const AM_MEDIA_TYPE* pmt )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
const WAVEFORMATEX* pwfx;
TRACE("(%p,%p)\n",This,pmt);
if ( !IsEqualGUID( &pmt->majortype, &MEDIATYPE_Audio ) )
return E_FAIL;
if ( !IsEqualGUID( &pmt->subtype, &MEDIASUBTYPE_PCM ) )
return E_FAIL;
if ( !IsEqualGUID( &pmt->formattype, &FORMAT_WaveFormatEx ) )
return E_FAIL;
if ( pmt->cbFormat < (sizeof(WAVEFORMATEX)-sizeof(WORD)) )
return E_FAIL;
pwfx = (const WAVEFORMATEX*)pmt->pbFormat;
if ( pwfx == NULL )
return E_FAIL;
if ( pwfx->wFormatTag != 1 )
return E_FAIL;
return NOERROR;
}
static HRESULT CAudioRendererPinImpl_Receive( CPinBaseImpl* pImpl, IMediaSample* pSample )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
HRESULT hr;
BYTE* pData = NULL;
DWORD dwDataLength;
DWORD dwWritten;
FIXME( "(%p,%p)\n",This,pSample );
if ( !This->pRender->m_fWaveOutInit )
return E_UNEXPECTED;
if ( This->pRender->m_fInFlush )
return S_FALSE;
if ( pSample == NULL )
return E_POINTER;
hr = IMediaSample_GetPointer( pSample, &pData );
if ( FAILED(hr) )
return hr;
dwDataLength = (DWORD)IMediaSample_GetActualDataLength( pSample );
while ( 1 )
{
TRACE("trying to write %lu bytes\n",dwDataLength);
ResetEvent( This->pRender->m_hEventRender );
hr = CAudioRendererImpl_waveOutWriteData(
This->pRender,pData,dwDataLength,&dwWritten);
if ( FAILED(hr) )
break;
if ( hr == S_FALSE )
{
WaitForSingleObject( This->pRender->m_hEventRender, INFINITE );
continue;
}
pData += dwWritten;
dwDataLength -= dwWritten;
hr = CAudioRendererImpl_waveOutFlush(This->pRender);
if ( FAILED(hr) )
break;
if ( dwDataLength == 0 )
break;
}
return hr;
}
static HRESULT CAudioRendererPinImpl_ReceiveCanBlock( CPinBaseImpl* pImpl )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
TRACE( "(%p)\n", This );
/* might block. */
return S_OK;
}
static HRESULT CAudioRendererPinImpl_EndOfStream( CPinBaseImpl* pImpl )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
FIXME( "(%p)\n", This );
This->pRender->m_fInFlush = FALSE;
/* FIXME - don't notify twice until stopped or seeked. */
return CBaseFilterImpl_MediaEventNotify(
&This->pRender->basefilter, EC_COMPLETE,
(LONG_PTR)S_OK, (LONG_PTR)(IBaseFilter*)(This->pRender) );
}
static HRESULT CAudioRendererPinImpl_BeginFlush( CPinBaseImpl* pImpl )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
FIXME( "(%p)\n", This );
This->pRender->m_fInFlush = TRUE;
CAudioRendererImpl_waveOutReset(This->pRender);
return NOERROR;
}
static HRESULT CAudioRendererPinImpl_EndFlush( CPinBaseImpl* pImpl )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
FIXME( "(%p)\n", This );
This->pRender->m_fInFlush = FALSE;
return NOERROR;
}
static HRESULT CAudioRendererPinImpl_NewSegment( CPinBaseImpl* pImpl, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double rate )
{
CAudioRendererPinImpl_THIS(pImpl,pin);
FIXME( "(%p)\n", This );
This->pRender->m_fInFlush = FALSE;
return NOERROR;
}
static const CBasePinHandlers pinhandlers =
{
NULL, /* pOnPreConnect */
NULL, /* pOnPostConnect */
NULL, /* pOnDisconnect */
CAudioRendererPinImpl_CheckMediaType, /* pCheckMediaType */
NULL, /* pQualityNotify */
CAudioRendererPinImpl_Receive, /* pReceive */
CAudioRendererPinImpl_ReceiveCanBlock, /* pReceiveCanBlock */
CAudioRendererPinImpl_EndOfStream, /* pEndOfStream */
CAudioRendererPinImpl_BeginFlush, /* pBeginFlush */
CAudioRendererPinImpl_EndFlush, /* pEndFlush */
CAudioRendererPinImpl_NewSegment, /* pNewSegment */
};
/***************************************************************************
*
* new/delete CAudioRendererImpl
*
*/
/* can I use offsetof safely? - FIXME? */
static QUARTZ_IFEntry FilterIFEntries[] =
{
{ &IID_IPersist, offsetof(CAudioRendererImpl,basefilter)-offsetof(CAudioRendererImpl,unk) },
{ &IID_IMediaFilter, offsetof(CAudioRendererImpl,basefilter)-offsetof(CAudioRendererImpl,unk) },
{ &IID_IBaseFilter, offsetof(CAudioRendererImpl,basefilter)-offsetof(CAudioRendererImpl,unk) },
{ &IID_IBasicAudio, offsetof(CAudioRendererImpl,basaud)-offsetof(CAudioRendererImpl,unk) },
};
static void QUARTZ_DestroyAudioRenderer(IUnknown* punk)
{
CAudioRendererImpl_THIS(punk,unk);
TRACE( "(%p)\n", This );
if ( This->pPin != NULL )
{
IUnknown_Release(This->pPin->unk.punkControl);
This->pPin = NULL;
}
CAudioRendererImpl_UninitIBasicAudio(This);
CBaseFilterImpl_UninitIBaseFilter(&This->basefilter);
}
HRESULT QUARTZ_CreateAudioRenderer(IUnknown* punkOuter,void** ppobj)
{
CAudioRendererImpl* This = NULL;
HRESULT hr;
TRACE("(%p,%p)\n",punkOuter,ppobj);
This = (CAudioRendererImpl*)
QUARTZ_AllocObj( sizeof(CAudioRendererImpl) );
if ( This == NULL )
return E_OUTOFMEMORY;
This->pPin = NULL;
This->m_fInFlush = FALSE;
This->m_fWaveOutInit = FALSE;
This->m_hEventRender = (HANDLE)NULL;
QUARTZ_IUnkInit( &This->unk, punkOuter );
hr = CBaseFilterImpl_InitIBaseFilter(
&This->basefilter,
This->unk.punkControl,
&CLSID_AudioRender,
QUARTZ_AudioRender_Name,
&filterhandlers );
if ( SUCCEEDED(hr) )
{
hr = CAudioRendererImpl_InitIBasicAudio(This);
if ( FAILED(hr) )
{
CBaseFilterImpl_UninitIBaseFilter(&This->basefilter);
}
}
if ( FAILED(hr) )
{
QUARTZ_FreeObj(This);
return hr;
}
This->unk.pEntries = FilterIFEntries;
This->unk.dwEntries = sizeof(FilterIFEntries)/sizeof(FilterIFEntries[0]);
This->unk.pOnFinalRelease = QUARTZ_DestroyAudioRenderer;
hr = QUARTZ_CreateAudioRendererPin(
This,
&This->basefilter.csFilter,
&This->pPin );
if ( SUCCEEDED(hr) )
hr = QUARTZ_CompList_AddComp(
This->basefilter.pInPins,
(IUnknown*)&This->pPin->pin,
NULL, 0 );
if ( FAILED(hr) )
{
IUnknown_Release( This->unk.punkControl );
return hr;
}
*ppobj = (void*)&(This->unk);
return S_OK;
}
/***************************************************************************
*
* new/delete CAudioRendererPinImpl
*
*/
/* can I use offsetof safely? - FIXME? */
static QUARTZ_IFEntry PinIFEntries[] =
{
{ &IID_IPin, offsetof(CAudioRendererPinImpl,pin)-offsetof(CAudioRendererPinImpl,unk) },
{ &IID_IMemInputPin, offsetof(CAudioRendererPinImpl,meminput)-offsetof(CAudioRendererPinImpl,unk) },
};
static void QUARTZ_DestroyAudioRendererPin(IUnknown* punk)
{
CAudioRendererPinImpl_THIS(punk,unk);
TRACE( "(%p)\n", This );
CPinBaseImpl_UninitIPin( &This->pin );
CMemInputPinBaseImpl_UninitIMemInputPin( &This->meminput );
}
HRESULT QUARTZ_CreateAudioRendererPin(
CAudioRendererImpl* pFilter,
CRITICAL_SECTION* pcsPin,
CAudioRendererPinImpl** ppPin)
{
CAudioRendererPinImpl* This = NULL;
HRESULT hr;
TRACE("(%p,%p,%p)\n",pFilter,pcsPin,ppPin);
This = (CAudioRendererPinImpl*)
QUARTZ_AllocObj( sizeof(CAudioRendererPinImpl) );
if ( This == NULL )
return E_OUTOFMEMORY;
QUARTZ_IUnkInit( &This->unk, NULL );
This->pRender = pFilter;
hr = CPinBaseImpl_InitIPin(
&This->pin,
This->unk.punkControl,
pcsPin,
&pFilter->basefilter,
QUARTZ_AudioRenderPin_Name,
FALSE,
&pinhandlers );
if ( SUCCEEDED(hr) )
{
hr = CMemInputPinBaseImpl_InitIMemInputPin(
&This->meminput,
This->unk.punkControl,
&This->pin );
if ( FAILED(hr) )
{
CPinBaseImpl_UninitIPin( &This->pin );
}
}
if ( FAILED(hr) )
{
QUARTZ_FreeObj(This);
return hr;
}
This->unk.pEntries = PinIFEntries;
This->unk.dwEntries = sizeof(PinIFEntries)/sizeof(PinIFEntries[0]);
This->unk.pOnFinalRelease = QUARTZ_DestroyAudioRendererPin;
*ppPin = This;
TRACE("returned successfully.\n");
return S_OK;
}
/***************************************************************************
*
* CAudioRendererImpl::IBasicAudio
*
*/
static HRESULT WINAPI
IBasicAudio_fnQueryInterface(IBasicAudio* iface,REFIID riid,void** ppobj)
{
CAudioRendererImpl_THIS(iface,basaud);
TRACE("(%p)->()\n",This);
return IUnknown_QueryInterface(This->unk.punkControl,riid,ppobj);
}
static ULONG WINAPI
IBasicAudio_fnAddRef(IBasicAudio* iface)
{
CAudioRendererImpl_THIS(iface,basaud);
TRACE("(%p)->()\n",This);
return IUnknown_AddRef(This->unk.punkControl);
}
static ULONG WINAPI
IBasicAudio_fnRelease(IBasicAudio* iface)
{
CAudioRendererImpl_THIS(iface,basaud);
TRACE("(%p)->()\n",This);
return IUnknown_Release(This->unk.punkControl);
}
static HRESULT WINAPI
IBasicAudio_fnGetTypeInfoCount(IBasicAudio* iface,UINT* pcTypeInfo)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnGetTypeInfo(IBasicAudio* iface,UINT iTypeInfo, LCID lcid, ITypeInfo** ppobj)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnGetIDsOfNames(IBasicAudio* iface,REFIID riid, LPOLESTR* ppwszName, UINT cNames, LCID lcid, DISPID* pDispId)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnInvoke(IBasicAudio* iface,DISPID DispId, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarRes, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnput_Volume(IBasicAudio* iface,long lVol)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnget_Volume(IBasicAudio* iface,long* plVol)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnput_Balance(IBasicAudio* iface,long lBalance)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static HRESULT WINAPI
IBasicAudio_fnget_Balance(IBasicAudio* iface,long* plBalance)
{
CAudioRendererImpl_THIS(iface,basaud);
FIXME("(%p)->()\n",This);
return E_NOTIMPL;
}
static ICOM_VTABLE(IBasicAudio) ibasicaudio =
{
ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
/* IUnknown fields */
IBasicAudio_fnQueryInterface,
IBasicAudio_fnAddRef,
IBasicAudio_fnRelease,
/* IDispatch fields */
IBasicAudio_fnGetTypeInfoCount,
IBasicAudio_fnGetTypeInfo,
IBasicAudio_fnGetIDsOfNames,
IBasicAudio_fnInvoke,
/* IBasicAudio fields */
IBasicAudio_fnput_Volume,
IBasicAudio_fnget_Volume,
IBasicAudio_fnput_Balance,
IBasicAudio_fnget_Balance,
};
HRESULT CAudioRendererImpl_InitIBasicAudio( CAudioRendererImpl* This )
{
TRACE("(%p)\n",This);
ICOM_VTBL(&This->basaud) = &ibasicaudio;
return NOERROR;
}
void CAudioRendererImpl_UninitIBasicAudio( CAudioRendererImpl* This )
{
TRACE("(%p)\n",This);
}