00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "StdAfx.h"
00020 #include "StreamSwitcher.h"
00021
00022 #include "Shlwapi.h"
00023 #include <atlpath.h>
00024 #include <mmreg.h>
00025 #include <ks.h>
00026 #include <ksmedia.h>
00027 #include "AudioSwitcher.h"
00028 #include "Audio.h"
00029 #include "..\..\..\DSUtil\DSUtil.h"
00030
00031 #include <initguid.h>
00032 #include "..\..\..\..\include\Ogg\OggDS.h"
00033
00034 #define BLOCKSTREAM
00035
00036
00037
00038
00039
00040 CStreamSwitcherPassThru::CStreamSwitcherPassThru(LPUNKNOWN pUnk, HRESULT* phr, CStreamSwitcherFilter* pFilter)
00041 : CMediaPosition(NAME("CStreamSwitcherPassThru"), pUnk)
00042 , m_pFilter(pFilter)
00043 {
00044 }
00045
00046 STDMETHODIMP CStreamSwitcherPassThru::NonDelegatingQueryInterface(REFIID riid, void** ppv)
00047 {
00048 CheckPointer(ppv, E_POINTER);
00049 *ppv = NULL;
00050
00051 return
00052 QI(IMediaSeeking)
00053 CMediaPosition::NonDelegatingQueryInterface(riid, ppv);
00054 }
00055
00056 template<class T>
00057 HRESULT GetPeer(CStreamSwitcherFilter* pFilter, T** ppT)
00058 {
00059 *ppT = NULL;
00060
00061 CBasePin* pPin = pFilter->GetInputPin();
00062 if(!pPin) return E_NOTIMPL;
00063
00064 CComPtr<IPin> pConnected;
00065 if(FAILED(pPin->ConnectedTo(&pConnected)))
00066 return E_NOTIMPL;
00067
00068 if(CComQIPtr<T> pT = pConnected)
00069 {
00070 *ppT = pT.Detach();
00071 return S_OK;
00072 }
00073
00074 return E_NOTIMPL;
00075 }
00076
00077 #define CallPeerSeeking(call) \
00078 CComPtr<IMediaSeeking> pMS; \
00079 if(FAILED(GetPeer(m_pFilter, &pMS))) return E_NOTIMPL; \
00080 return pMS->##call; \
00081
00082 #define CallPeer(call) \
00083 CComPtr<IMediaPosition> pMP; \
00084 if(FAILED(GetPeer(m_pFilter, &pMP))) return E_NOTIMPL; \
00085 return pMP->##call; \
00086
00087 #define CallPeerSeekingAll(call) \
00088 HRESULT hr = E_NOTIMPL; \
00089 POSITION pos = m_pFilter->m_pInputs.GetHeadPosition(); \
00090 while(pos) \
00091 { \
00092 CBasePin* pPin = m_pFilter->m_pInputs.GetNext(pos); \
00093 CComPtr<IPin> pConnected; \
00094 if(FAILED(pPin->ConnectedTo(&pConnected))) \
00095 continue; \
00096 if(CComQIPtr<IMediaSeeking> pMS = pConnected) \
00097 { \
00098 HRESULT hr2 = pMS->call; \
00099 if(pPin == m_pFilter->GetInputPin()) \
00100 hr = hr2; \
00101 } \
00102 } \
00103 return hr; \
00104
00105 #define CallPeerAll(call) \
00106 HRESULT hr = E_NOTIMPL; \
00107 POSITION pos = m_pFilter->m_pInputs.GetHeadPosition(); \
00108 while(pos) \
00109 { \
00110 CBasePin* pPin = m_pFilter->m_pInputs.GetNext(pos); \
00111 CComPtr<IPin> pConnected; \
00112 if(FAILED(pPin->ConnectedTo(&pConnected))) \
00113 continue; \
00114 if(CComQIPtr<IMediaPosition> pMP = pConnected) \
00115 { \
00116 HRESULT hr2 = pMP->call; \
00117 if(pPin == m_pFilter->GetInputPin()) \
00118 hr = hr2; \
00119 } \
00120 } \
00121 return hr; \
00122
00123
00124
00125
00126 STDMETHODIMP CStreamSwitcherPassThru::GetCapabilities(DWORD* pCaps)
00127 {CallPeerSeeking(GetCapabilities(pCaps));}
00128 STDMETHODIMP CStreamSwitcherPassThru::CheckCapabilities(DWORD* pCaps)
00129 {CallPeerSeeking(CheckCapabilities(pCaps));}
00130 STDMETHODIMP CStreamSwitcherPassThru::IsFormatSupported(const GUID* pFormat)
00131 {CallPeerSeeking(IsFormatSupported(pFormat));}
00132 STDMETHODIMP CStreamSwitcherPassThru::QueryPreferredFormat(GUID* pFormat)
00133 {CallPeerSeeking(QueryPreferredFormat(pFormat));}
00134 STDMETHODIMP CStreamSwitcherPassThru::SetTimeFormat(const GUID* pFormat)
00135 {CallPeerSeeking(SetTimeFormat(pFormat));}
00136 STDMETHODIMP CStreamSwitcherPassThru::GetTimeFormat(GUID* pFormat)
00137 {CallPeerSeeking(GetTimeFormat(pFormat));}
00138 STDMETHODIMP CStreamSwitcherPassThru::IsUsingTimeFormat(const GUID* pFormat)
00139 {CallPeerSeeking(IsUsingTimeFormat(pFormat));}
00140 STDMETHODIMP CStreamSwitcherPassThru::ConvertTimeFormat(LONGLONG* pTarget, const GUID* pTargetFormat, LONGLONG Source, const GUID* pSourceFormat)
00141 {CallPeerSeeking(ConvertTimeFormat(pTarget, pTargetFormat, Source, pSourceFormat));}
00142 STDMETHODIMP CStreamSwitcherPassThru::SetPositions(LONGLONG* pCurrent, DWORD CurrentFlags, LONGLONG* pStop, DWORD StopFlags)
00143 {CallPeerSeekingAll(SetPositions(pCurrent, CurrentFlags, pStop, StopFlags));}
00144 STDMETHODIMP CStreamSwitcherPassThru::GetPositions(LONGLONG* pCurrent, LONGLONG* pStop)
00145 {CallPeerSeeking(GetPositions(pCurrent, pStop));}
00146 STDMETHODIMP CStreamSwitcherPassThru::GetCurrentPosition(LONGLONG* pCurrent)
00147 {CallPeerSeeking(GetCurrentPosition(pCurrent));}
00148 STDMETHODIMP CStreamSwitcherPassThru::GetStopPosition(LONGLONG* pStop)
00149 {CallPeerSeeking(GetStopPosition(pStop));}
00150 STDMETHODIMP CStreamSwitcherPassThru::GetDuration(LONGLONG* pDuration)
00151 {CallPeerSeeking(GetDuration(pDuration));}
00152 STDMETHODIMP CStreamSwitcherPassThru::GetPreroll(LONGLONG* pllPreroll)
00153 {CallPeerSeeking(GetPreroll(pllPreroll));}
00154 STDMETHODIMP CStreamSwitcherPassThru::GetAvailable(LONGLONG* pEarliest, LONGLONG* pLatest)
00155 {CallPeerSeeking(GetAvailable(pEarliest, pLatest));}
00156 STDMETHODIMP CStreamSwitcherPassThru::GetRate(double* pdRate)
00157 {CallPeerSeeking(GetRate(pdRate));}
00158 STDMETHODIMP CStreamSwitcherPassThru::SetRate(double dRate)
00159 {if(0.0 == dRate) return E_INVALIDARG;
00160 CallPeerSeekingAll(SetRate(dRate));}
00161
00162
00163
00164 STDMETHODIMP CStreamSwitcherPassThru::get_Duration(REFTIME* plength)
00165 {CallPeer(get_Duration(plength));}
00166 STDMETHODIMP CStreamSwitcherPassThru::get_CurrentPosition(REFTIME* pllTime)
00167 {CallPeer(get_CurrentPosition(pllTime));}
00168 STDMETHODIMP CStreamSwitcherPassThru::put_CurrentPosition(REFTIME llTime)
00169 {CallPeerAll(put_CurrentPosition(llTime));}
00170 STDMETHODIMP CStreamSwitcherPassThru::get_StopTime(REFTIME* pllTime)
00171 {CallPeer(get_StopTime(pllTime));}
00172 STDMETHODIMP CStreamSwitcherPassThru::put_StopTime(REFTIME llTime)
00173 {CallPeerAll(put_StopTime(llTime));}
00174 STDMETHODIMP CStreamSwitcherPassThru::get_PrerollTime(REFTIME * pllTime)
00175 {CallPeer(get_PrerollTime(pllTime));}
00176 STDMETHODIMP CStreamSwitcherPassThru::put_PrerollTime(REFTIME llTime)
00177 {CallPeerAll(put_PrerollTime(llTime));}
00178 STDMETHODIMP CStreamSwitcherPassThru::get_Rate(double* pdRate)
00179 {CallPeer(get_Rate(pdRate));}
00180 STDMETHODIMP CStreamSwitcherPassThru::put_Rate(double dRate)
00181 {if(0.0 == dRate) return E_INVALIDARG;
00182 CallPeerAll(put_Rate(dRate));}
00183 STDMETHODIMP CStreamSwitcherPassThru::CanSeekForward(LONG* pCanSeekForward)
00184 {CallPeer(CanSeekForward(pCanSeekForward));}
00185 STDMETHODIMP CStreamSwitcherPassThru::CanSeekBackward(LONG* pCanSeekBackward)
00186 {CallPeer(CanSeekBackward(pCanSeekBackward));}
00187
00188
00189
00190
00191
00192 CStreamSwitcherAllocator::CStreamSwitcherAllocator(CStreamSwitcherInputPin* pPin, HRESULT* phr)
00193 : CMemAllocator(NAME("CStreamSwitcherAllocator"), NULL, phr)
00194 , m_pPin(pPin)
00195 , m_fMediaTypeChanged(false)
00196 {
00197 ASSERT(phr);
00198 ASSERT(pPin);
00199 }
00200
00201 #ifdef DEBUG
00202 CStreamSwitcherAllocator::~CStreamSwitcherAllocator()
00203 {
00204 ASSERT(m_bCommitted == FALSE);
00205 }
00206 #endif
00207
00208 STDMETHODIMP_(ULONG) CStreamSwitcherAllocator::NonDelegatingAddRef()
00209 {
00210 return m_pPin->m_pFilter->AddRef();
00211 }
00212
00213 STDMETHODIMP_(ULONG) CStreamSwitcherAllocator::NonDelegatingRelease()
00214 {
00215 return m_pPin->m_pFilter->Release();
00216 }
00217
00218 STDMETHODIMP CStreamSwitcherAllocator::GetBuffer(
00219 IMediaSample** ppBuffer,
00220 REFERENCE_TIME* pStartTime, REFERENCE_TIME* pEndTime,
00221 DWORD dwFlags)
00222 {
00223 HRESULT hr = VFW_E_NOT_COMMITTED;
00224
00225 if(!m_bCommitted)
00226 return hr;
00227
00228
00229
00230
00231
00232 if(m_fMediaTypeChanged)
00233 {
00234 if(!m_pPin || !m_pPin->m_pFilter)
00235 return hr;
00236
00237 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pPin->m_pFilter)->GetOutputPin();
00238 if(!pOut || !pOut->CurrentAllocator())
00239 return hr;
00240
00241 ALLOCATOR_PROPERTIES Properties, Actual;
00242 if(FAILED(pOut->CurrentAllocator()->GetProperties(&Actual)))
00243 return hr;
00244 if(FAILED(GetProperties(&Properties)))
00245 return hr;
00246
00247 if(!m_bCommitted || Properties.cbBuffer < Actual.cbBuffer)
00248 {
00249 Properties.cbBuffer = Actual.cbBuffer;
00250 if(FAILED(Decommit())) return hr;
00251 if(FAILED(SetProperties(&Properties, &Actual))) return hr;
00252 if(FAILED(Commit())) return hr;
00253 ASSERT(Actual.cbBuffer >= Properties.cbBuffer);
00254 if(Actual.cbBuffer < Properties.cbBuffer) return hr;
00255 }
00256 }
00257
00258 hr = CMemAllocator::GetBuffer(ppBuffer, pStartTime, pEndTime, dwFlags);
00259
00260 if(m_fMediaTypeChanged && SUCCEEDED(hr))
00261 {
00262 (*ppBuffer)->SetMediaType(&m_mt);
00263 m_fMediaTypeChanged = false;
00264 }
00265
00266 return hr;
00267 }
00268
00269 void CStreamSwitcherAllocator::NotifyMediaType(const CMediaType& mt)
00270 {
00271 CopyMediaType(&m_mt, &mt);
00272 m_fMediaTypeChanged = true;
00273 }
00274
00275
00276
00277
00278
00279
00280 CStreamSwitcherInputPin::CStreamSwitcherInputPin(CStreamSwitcherFilter* pFilter, HRESULT* phr, LPCWSTR pName)
00281 : CBaseInputPin(NAME("CStreamSwitcherInputPin"), pFilter, &pFilter->m_csState, phr, pName)
00282 , m_Allocator(this, phr)
00283 , m_bSampleSkipped(FALSE)
00284 , m_bQualityChanged(FALSE)
00285 , m_bUsingOwnAllocator(FALSE)
00286 , m_evBlock(TRUE)
00287 , m_fCanBlock(false)
00288 , m_hNotifyEvent(NULL)
00289 {
00290 m_bCanReconnectWhenActive = TRUE;
00291 }
00292
00293 [uuid("138130AF-A79B-45D5-B4AA-87697457BA87")]
00294 class NeroAudioDecoder {};
00295
00296 STDMETHODIMP CStreamSwitcherInputPin::NonDelegatingQueryInterface(REFIID riid, void** ppv)
00297 {
00298 return
00299 QI(IStreamSwitcherInputPin)
00300 IsConnected() && GetCLSID(GetFilterFromPin(GetConnected())) == __uuidof(NeroAudioDecoder) && QI(IPinConnection)
00301 __super::NonDelegatingQueryInterface(riid, ppv);
00302 }
00303
00304
00305
00306 STDMETHODIMP CStreamSwitcherInputPin::DynamicQueryAccept(const AM_MEDIA_TYPE* pmt)
00307 {
00308 return QueryAccept(pmt);
00309 }
00310
00311 STDMETHODIMP CStreamSwitcherInputPin::NotifyEndOfStream(HANDLE hNotifyEvent)
00312 {
00313 if(m_hNotifyEvent) SetEvent(m_hNotifyEvent);
00314 m_hNotifyEvent = hNotifyEvent;
00315 return S_OK;
00316 }
00317
00318 STDMETHODIMP CStreamSwitcherInputPin::IsEndPin()
00319 {
00320 return S_OK;
00321 }
00322
00323 STDMETHODIMP CStreamSwitcherInputPin::DynamicDisconnect()
00324 {
00325 CAutoLock cAutoLock(&m_csReceive);
00326 Disconnect();
00327 return S_OK;
00328 }
00329
00330
00331
00332 STDMETHODIMP_(bool) CStreamSwitcherInputPin::IsActive()
00333 {
00334
00335 return(this == ((CStreamSwitcherFilter*)m_pFilter)->GetInputPin());
00336 }
00337
00338
00339
00340 HRESULT CStreamSwitcherInputPin::QueryAcceptDownstream(const AM_MEDIA_TYPE* pmt)
00341 {
00342 HRESULT hr = S_OK;
00343
00344 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00345
00346 if(pOut && pOut->IsConnected())
00347 {
00348 if(CComPtr<IPinConnection> pPC = pOut->CurrentPinConnection())
00349 {
00350 hr = pPC->DynamicQueryAccept(pmt);
00351 if(hr == S_OK) return S_OK;
00352 }
00353
00354 hr = pOut->GetConnected()->QueryAccept(pmt);
00355 }
00356
00357 return hr;
00358 }
00359
00360 void CStreamSwitcherInputPin::Block(bool fBlock)
00361 {
00362 if(fBlock) m_evBlock.Reset();
00363 else m_evBlock.Set();
00364 }
00365
00366 HRESULT CStreamSwitcherInputPin::InitializeOutputSample(IMediaSample* pInSample, IMediaSample** ppOutSample)
00367 {
00368 if(!pInSample || !ppOutSample)
00369 return E_POINTER;
00370
00371 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00372 ASSERT(pOut->GetConnected());
00373
00374 CComPtr<IMediaSample> pOutSample;
00375
00376 DWORD dwFlags = m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0;
00377
00378 if(!(m_SampleProps.dwSampleFlags & AM_SAMPLE_SPLICEPOINT))
00379 dwFlags |= AM_GBF_NOTASYNCPOINT;
00380
00381 HRESULT hr = pOut->GetDeliveryBuffer(&pOutSample
00382 , m_SampleProps.dwSampleFlags & AM_SAMPLE_TIMEVALID ? &m_SampleProps.tStart : NULL
00383 , m_SampleProps.dwSampleFlags & AM_SAMPLE_STOPVALID ? &m_SampleProps.tStop : NULL
00384 , dwFlags);
00385
00386 if(FAILED(hr))
00387 return hr;
00388
00389 if(!pOutSample)
00390 return E_FAIL;
00391
00392 if(CComQIPtr<IMediaSample2> pOutSample2 = pOutSample)
00393 {
00394 AM_SAMPLE2_PROPERTIES OutProps;
00395 EXECUTE_ASSERT(SUCCEEDED(pOutSample2->GetProperties(FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, tStart), (PBYTE)&OutProps)));
00396 OutProps.dwTypeSpecificFlags = m_SampleProps.dwTypeSpecificFlags;
00397 OutProps.dwSampleFlags =
00398 (OutProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED) |
00399 (m_SampleProps.dwSampleFlags & ~AM_SAMPLE_TYPECHANGED);
00400
00401 OutProps.tStart = m_SampleProps.tStart;
00402 OutProps.tStop = m_SampleProps.tStop;
00403 OutProps.cbData = FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId);
00404
00405 hr = pOutSample2->SetProperties(FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId), (PBYTE)&OutProps);
00406 if(m_SampleProps.dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY)
00407 m_bSampleSkipped = FALSE;
00408 }
00409 else
00410 {
00411 if(m_SampleProps.dwSampleFlags & AM_SAMPLE_TIMEVALID)
00412 pOutSample->SetTime(&m_SampleProps.tStart, &m_SampleProps.tStop);
00413
00414 if(m_SampleProps.dwSampleFlags & AM_SAMPLE_SPLICEPOINT)
00415 pOutSample->SetSyncPoint(TRUE);
00416
00417 if(m_SampleProps.dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY)
00418 {
00419 pOutSample->SetDiscontinuity(TRUE);
00420 m_bSampleSkipped = FALSE;
00421 }
00422
00423 LONGLONG MediaStart, MediaEnd;
00424 if(pInSample->GetMediaTime(&MediaStart, &MediaEnd) == NOERROR)
00425 pOutSample->SetMediaTime(&MediaStart, &MediaEnd);
00426 }
00427
00428 *ppOutSample = pOutSample.Detach();
00429
00430 return S_OK;
00431 }
00432
00433
00434
00435 HRESULT CStreamSwitcherInputPin::CheckMediaType(const CMediaType* pmt)
00436 {
00437 return ((CStreamSwitcherFilter*)m_pFilter)->CheckMediaType(pmt);
00438 }
00439
00440
00441
00442 HRESULT CStreamSwitcherInputPin::CheckConnect(IPin* pPin)
00443 {
00444 return (IPin*)((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin() == pPin
00445 ? E_FAIL
00446 : __super::CheckConnect(pPin);
00447 }
00448
00449 HRESULT CStreamSwitcherInputPin::CompleteConnect(IPin* pReceivePin)
00450 {
00451 HRESULT hr = __super::CompleteConnect(pReceivePin);
00452 if(FAILED(hr)) return hr;
00453
00454 ((CStreamSwitcherFilter*)m_pFilter)->CompleteConnect(PINDIR_INPUT, this, pReceivePin);
00455
00456 m_fCanBlock = false;
00457 bool fForkedSomewhere = false;
00458
00459 CStringW fileName;
00460 CStringW pinName;
00461
00462 IPin* pPin = (IPin*)this;
00463 IBaseFilter* pBF = (IBaseFilter*)m_pFilter;
00464
00465 while((pPin = GetUpStreamPin(pBF, pPin)) && (pBF = GetFilterFromPin(pPin)))
00466 {
00467 if(IsSplitter(pBF))
00468 {
00469 pinName = GetPinName(pPin);
00470 }
00471
00472 CLSID clsid = GetCLSID(pBF);
00473 if(clsid == CLSID_AviSplitter || clsid == CLSID_OggSplitter)
00474 m_fCanBlock = true;
00475
00476 int nIn, nOut, nInC, nOutC;
00477 CountPins(pBF, nIn, nOut, nInC, nOutC);
00478 fForkedSomewhere = fForkedSomewhere || nIn > 1 || nOut > 1;
00479
00480 if(CComQIPtr<IFileSourceFilter> pFSF = pBF)
00481 {
00482 WCHAR* pszName = NULL;
00483 AM_MEDIA_TYPE mt;
00484 if(SUCCEEDED(pFSF->GetCurFile(&pszName, &mt)) && pszName)
00485 {
00486 fileName = pszName;
00487 CoTaskMemFree(pszName);
00488
00489 fileName.Replace('\\', '/');
00490 CStringW fn = fileName.Mid(fileName.ReverseFind('/')+1);
00491 if(!fn.IsEmpty()) fileName = fn;
00492
00493 if(!pinName.IsEmpty()) fileName += L" / " + pinName;
00494
00495 WCHAR* pName = new WCHAR[fileName.GetLength()+1];
00496 if(pName)
00497 {
00498 wcscpy(pName, fileName);
00499 if(m_pName) delete [] m_pName;
00500 m_pName = pName;
00501 }
00502 }
00503
00504 break;
00505 }
00506
00507 pPin = GetFirstPin(pBF);
00508 }
00509
00510 if(!fForkedSomewhere)
00511 m_fCanBlock = true;
00512
00513 m_hNotifyEvent = NULL;
00514
00515 return S_OK;
00516 }
00517
00518 HRESULT CStreamSwitcherInputPin::Active()
00519 {
00520 Block(!IsActive());
00521
00522 return __super::Active();
00523 }
00524
00525 HRESULT CStreamSwitcherInputPin::Inactive()
00526 {
00527 Block(false);
00528
00529 return __super::Inactive();
00530 }
00531
00532
00533
00534 STDMETHODIMP CStreamSwitcherInputPin::QueryAccept(const AM_MEDIA_TYPE* pmt)
00535 {
00536 HRESULT hr = __super::QueryAccept(pmt);
00537 if(S_OK != hr) return hr;
00538
00539 return QueryAcceptDownstream(pmt);
00540 }
00541
00542 STDMETHODIMP CStreamSwitcherInputPin::ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt)
00543 {
00544
00545
00546
00547 HRESULT hr;
00548 if(S_OK != (hr = QueryAcceptDownstream(pmt)))
00549 return VFW_E_TYPE_NOT_ACCEPTED;
00550
00551 if(m_Connected)
00552 m_Connected->Release(), m_Connected = NULL;
00553
00554 return SUCCEEDED(__super::ReceiveConnection(pConnector, pmt)) ? S_OK : E_FAIL;
00555 }
00556
00557 STDMETHODIMP CStreamSwitcherInputPin::GetAllocator(IMemAllocator** ppAllocator)
00558 {
00559 CheckPointer(ppAllocator, E_POINTER);
00560
00561 if(m_pAllocator == NULL)
00562 {
00563 (m_pAllocator = &m_Allocator)->AddRef();
00564 }
00565
00566 (*ppAllocator = m_pAllocator)->AddRef();
00567
00568 return NOERROR;
00569 }
00570
00571 STDMETHODIMP CStreamSwitcherInputPin::NotifyAllocator(IMemAllocator* pAllocator, BOOL bReadOnly)
00572 {
00573 HRESULT hr = __super::NotifyAllocator(pAllocator, bReadOnly);
00574 if(FAILED(hr)) return hr;
00575
00576 m_bUsingOwnAllocator = (pAllocator == (IMemAllocator*)&m_Allocator);
00577
00578 return S_OK;
00579 }
00580
00581 STDMETHODIMP CStreamSwitcherInputPin::BeginFlush()
00582 {
00583 CAutoLock cAutoLock(&((CStreamSwitcherFilter*)m_pFilter)->m_csState);
00584
00585 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00586 if(!IsConnected() || !pOut || !pOut->IsConnected())
00587 return VFW_E_NOT_CONNECTED;
00588
00589 HRESULT hr = __super::BeginFlush();
00590 if(FAILED(hr))
00591 return hr;
00592
00593 return IsActive() ? pOut->DeliverBeginFlush() : Block(false), S_OK;
00594 }
00595
00596 STDMETHODIMP CStreamSwitcherInputPin::EndFlush()
00597 {
00598 CAutoLock cAutoLock(&((CStreamSwitcherFilter*)m_pFilter)->m_csState);
00599
00600 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00601 if(!IsConnected() || !pOut || !pOut->IsConnected())
00602 return VFW_E_NOT_CONNECTED;
00603
00604 HRESULT hr = __super::EndFlush();
00605 if(FAILED(hr))
00606 return hr;
00607
00608 return IsActive() ? pOut->DeliverEndFlush() : Block(true), S_OK;
00609 }
00610
00611 STDMETHODIMP CStreamSwitcherInputPin::EndOfStream()
00612 {
00613 CAutoLock cAutoLock(&m_csReceive);
00614
00615 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00616 if(!IsConnected() || !pOut || !pOut->IsConnected())
00617 return VFW_E_NOT_CONNECTED;
00618
00619 if(m_hNotifyEvent)
00620 {
00621 SetEvent(m_hNotifyEvent), m_hNotifyEvent = NULL;
00622 return S_OK;
00623 }
00624
00625 return IsActive() ? pOut->DeliverEndOfStream() : S_OK;
00626 }
00627
00628
00629
00630 STDMETHODIMP CStreamSwitcherInputPin::Receive(IMediaSample* pSample)
00631 {
00632 AM_MEDIA_TYPE* pmt = NULL;
00633 if(SUCCEEDED(pSample->GetMediaType(&pmt)) && pmt)
00634 {
00635 const CMediaType mt(*pmt);
00636 DeleteMediaType(pmt), pmt = NULL;
00637 SetMediaType(&mt);
00638 }
00639
00640
00641
00642
00643
00644
00645 #ifdef BLOCKSTREAM
00646 if(m_fCanBlock)
00647 m_evBlock.Wait();
00648 #endif
00649
00650 if(!IsActive())
00651 {
00652 #ifdef BLOCKSTREAM
00653 if(m_fCanBlock)
00654 return S_FALSE;
00655 #endif
00656
00657 TRACE(_T("&^%$#@\n"));
00658
00659 return E_FAIL;
00660 }
00661
00662 CAutoLock cAutoLock(&m_csReceive);
00663
00664 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00665 ASSERT(pOut->GetConnected());
00666
00667 HRESULT hr = __super::Receive(pSample);
00668 if(S_OK != hr) return hr;
00669
00670 if(m_SampleProps.dwStreamId != AM_STREAM_MEDIA)
00671 {
00672 return pOut->Deliver(pSample);
00673 }
00674
00675
00676
00677 ALLOCATOR_PROPERTIES props, actual;
00678 hr = m_pAllocator->GetProperties(&props);
00679 hr = pOut->CurrentAllocator()->GetProperties(&actual);
00680
00681 REFERENCE_TIME rtStart = 0, rtStop = 0;
00682 if(S_OK == pSample->GetTime(&rtStart, &rtStop))
00683 {
00684
00685 }
00686
00687 long cbBuffer = pSample->GetActualDataLength();
00688
00689 CMediaType mtOut = m_mt;
00690 mtOut = ((CStreamSwitcherFilter*)m_pFilter)->CreateNewOutputMediaType(mtOut, cbBuffer);
00691
00692 bool fTypeChanged = false;
00693
00694 if(mtOut != pOut->CurrentMediaType() || cbBuffer > actual.cbBuffer)
00695 {
00696 fTypeChanged = true;
00697
00698 m_SampleProps.dwSampleFlags |= AM_SAMPLE_TYPECHANGED;
00699
00700
00701
00702
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712
00713 if(props.cBuffers < 8 && mtOut.majortype == MEDIATYPE_Audio)
00714 props.cBuffers = 8;
00715
00716 props.cbBuffer = cbBuffer;
00717
00718 if(actual.cbAlign != props.cbAlign
00719 || actual.cbPrefix != props.cbPrefix
00720 || actual.cBuffers < props.cBuffers
00721 || actual.cbBuffer < props.cbBuffer)
00722 {
00723 hr = pOut->DeliverBeginFlush();
00724 hr = pOut->DeliverEndFlush();
00725 hr = pOut->CurrentAllocator()->Decommit();
00726 hr = pOut->CurrentAllocator()->SetProperties(&props, &actual);
00727 hr = pOut->CurrentAllocator()->Commit();
00728 }
00729 }
00730
00731 CComPtr<IMediaSample> pOutSample;
00732 if(FAILED(InitializeOutputSample(pSample, &pOutSample)))
00733 return E_FAIL;
00734
00735 pmt = NULL;
00736 if(SUCCEEDED(pOutSample->GetMediaType(&pmt)) && pmt)
00737 {
00738 const CMediaType mt(*pmt);
00739 DeleteMediaType(pmt), pmt = NULL;
00740
00741 ASSERT(0);
00742 }
00743
00744 if(fTypeChanged)
00745 {
00746 pOut->SetMediaType(&mtOut);
00747 ((CStreamSwitcherFilter*)m_pFilter)->OnNewOutputMediaType(m_mt, mtOut);
00748 pOutSample->SetMediaType(&mtOut);
00749 }
00750
00751
00752
00753 hr = ((CStreamSwitcherFilter*)m_pFilter)->Transform(pSample, pOutSample);
00754
00755
00756
00757 if(S_OK == hr)
00758 {
00759 hr = pOut->Deliver(pOutSample);
00760 m_bSampleSkipped = FALSE;
00761
00762
00763
00764
00765
00766
00767 }
00768 else if(S_FALSE == hr)
00769 {
00770 hr = S_OK;
00771 pOutSample = NULL;
00772 m_bSampleSkipped = TRUE;
00773
00774 if(!m_bQualityChanged)
00775 {
00776 m_pFilter->NotifyEvent(EC_QUALITY_CHANGE, 0, 0);
00777 m_bQualityChanged = TRUE;
00778 }
00779 }
00780
00781 return hr;
00782 }
00783
00784 STDMETHODIMP CStreamSwitcherInputPin::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
00785 {
00786 if(!IsConnected())
00787 return S_OK;
00788
00789 CAutoLock cAutoLock(&m_csReceive);
00790
00791 CStreamSwitcherOutputPin* pOut = ((CStreamSwitcherFilter*)m_pFilter)->GetOutputPin();
00792 if(!pOut || !pOut->IsConnected())
00793 return VFW_E_NOT_CONNECTED;
00794
00795 HRESULT hr = pOut->DeliverNewSegment(tStart, tStop, dRate);
00796
00797 return hr;
00798 }
00799
00800
00801
00802
00803
00804
00805 CStreamSwitcherOutputPin::CStreamSwitcherOutputPin(CStreamSwitcherFilter* pFilter, HRESULT* phr)
00806 : CBaseOutputPin(NAME("CStreamSwitcherOutputPin"), pFilter, &pFilter->m_csState, phr, L"Out")
00807 {
00808
00809 }
00810
00811 STDMETHODIMP CStreamSwitcherOutputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv)
00812 {
00813 CheckPointer(ppv,E_POINTER);
00814 ValidateReadWritePtr(ppv, sizeof(PVOID));
00815 *ppv = NULL;
00816
00817 if(riid == IID_IMediaPosition || riid == IID_IMediaSeeking)
00818 {
00819 if(m_pStreamSwitcherPassThru == NULL)
00820 {
00821 HRESULT hr = S_OK;
00822 m_pStreamSwitcherPassThru = (IUnknown*)(INonDelegatingUnknown*)
00823 new CStreamSwitcherPassThru(GetOwner(), &hr, (CStreamSwitcherFilter*)m_pFilter);
00824
00825 if(!m_pStreamSwitcherPassThru) return E_OUTOFMEMORY;
00826 if(FAILED(hr)) return hr;
00827 }
00828
00829 return m_pStreamSwitcherPassThru->QueryInterface(riid, ppv);
00830 }
00831
00832
00833
00834
00835
00836
00837 return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
00838 }
00839
00840 HRESULT CStreamSwitcherOutputPin::QueryAcceptUpstream(const AM_MEDIA_TYPE* pmt)
00841 {
00842 HRESULT hr = S_FALSE;
00843
00844 CStreamSwitcherInputPin* pIn = ((CStreamSwitcherFilter*)m_pFilter)->GetInputPin();
00845
00846 if(pIn && pIn->IsConnected() && (pIn->IsUsingOwnAllocator() || pIn->CurrentMediaType() == *pmt))
00847 {
00848 if(CComQIPtr<IPin> pPinTo = pIn->GetConnected())
00849 {
00850 if(S_OK != (hr = pPinTo->QueryAccept(pmt)))
00851 return VFW_E_TYPE_NOT_ACCEPTED;
00852 }
00853 else
00854 {
00855 return E_FAIL;
00856 }
00857 }
00858
00859 return hr;
00860 }
00861
00862
00863
00864 HRESULT CStreamSwitcherOutputPin::DecideBufferSize(IMemAllocator* pAllocator, ALLOCATOR_PROPERTIES* pProperties)
00865 {
00866 CStreamSwitcherInputPin* pIn = ((CStreamSwitcherFilter*)m_pFilter)->GetInputPin();
00867 if(!pIn || !pIn->IsConnected()) return E_UNEXPECTED;
00868
00869 CComPtr<IMemAllocator> pAllocatorIn;
00870 pIn->GetAllocator(&pAllocatorIn);
00871 if(!pAllocatorIn) return E_UNEXPECTED;
00872
00873 HRESULT hr;
00874 if(FAILED(hr = pAllocatorIn->GetProperties(pProperties)))
00875 return hr;
00876
00877 if(pProperties->cBuffers < 8 && pIn->CurrentMediaType().majortype == MEDIATYPE_Audio)
00878 pProperties->cBuffers = 8;
00879
00880 ALLOCATOR_PROPERTIES Actual;
00881 if(FAILED(hr = pAllocator->SetProperties(pProperties, &Actual)))
00882 return hr;
00883
00884 return(pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer
00885 ? E_FAIL
00886 : NOERROR);
00887 }
00888
00889
00890
00891 [uuid("AEFA5024-215A-4FC7-97A4-1043C86FD0B8")]
00892 class MatrixMixer {};
00893
00894 HRESULT CStreamSwitcherOutputPin::CheckConnect(IPin* pPin)
00895 {
00896 CComPtr<IBaseFilter> pBF = GetFilterFromPin(pPin);
00897
00898 return
00899 IsAudioWaveRenderer(pBF) || GetCLSID(pBF) == __uuidof(MatrixMixer)
00900 ? __super::CheckConnect(pPin)
00901 : E_FAIL;
00902
00903
00904
00905 }
00906
00907 HRESULT CStreamSwitcherOutputPin::BreakConnect()
00908 {
00909 m_pPinConnection = NULL;
00910 return __super::BreakConnect();
00911 }
00912
00913 HRESULT CStreamSwitcherOutputPin::CompleteConnect(IPin* pReceivePin)
00914 {
00915 m_pPinConnection = CComQIPtr<IPinConnection>(pReceivePin);
00916 return __super::CompleteConnect(pReceivePin);
00917 }
00918
00919 HRESULT CStreamSwitcherOutputPin::CheckMediaType(const CMediaType* pmt)
00920 {
00921 return ((CStreamSwitcherFilter*)m_pFilter)->CheckMediaType(pmt);
00922 }
00923
00924 HRESULT CStreamSwitcherOutputPin::GetMediaType(int iPosition, CMediaType* pmt)
00925 {
00926 CStreamSwitcherInputPin* pIn = ((CStreamSwitcherFilter*)m_pFilter)->GetInputPin();
00927 if(!pIn || !pIn->IsConnected()) return E_UNEXPECTED;
00928
00929 CComPtr<IEnumMediaTypes> pEM;
00930 if(FAILED(pIn->GetConnected()->EnumMediaTypes(&pEM)))
00931 return VFW_S_NO_MORE_ITEMS;
00932
00933 if(iPosition > 0 && FAILED(pEM->Skip(iPosition)))
00934 return VFW_S_NO_MORE_ITEMS;
00935
00936 AM_MEDIA_TYPE* tmp = NULL;
00937 if(S_OK != pEM->Next(1, &tmp, NULL) || !tmp)
00938 return VFW_S_NO_MORE_ITEMS;
00939
00940 CopyMediaType(pmt, tmp);
00941 DeleteMediaType(tmp);
00942
00943
00944
00945
00946
00947
00948 return S_OK;
00949 }
00950
00951
00952
00953 STDMETHODIMP CStreamSwitcherOutputPin::QueryAccept(const AM_MEDIA_TYPE* pmt)
00954 {
00955 HRESULT hr = __super::QueryAccept(pmt);
00956 if(S_OK != hr) return hr;
00957
00958 return QueryAcceptUpstream(pmt);
00959 }
00960
00961
00962
00963 STDMETHODIMP CStreamSwitcherOutputPin::Notify(IBaseFilter* pSender, Quality q)
00964 {
00965 CStreamSwitcherInputPin* pIn = ((CStreamSwitcherFilter*)m_pFilter)->GetInputPin();
00966 if(!pIn || !pIn->IsConnected()) return VFW_E_NOT_CONNECTED;
00967 return pIn->PassNotify(q);
00968 }
00969
00970
00971
00972 STDMETHODIMP CStreamSwitcherOutputPin::Render(IPin* ppinOut, IGraphBuilder* pGraph)
00973 {
00974 CComPtr<IBaseFilter> pBF;
00975 pBF.CoCreateInstance(CLSID_DSoundRender);
00976 if(!pBF || FAILED(pGraph->AddFilter(pBF, L"Default DirectSound Device")))
00977 {
00978 return E_FAIL;
00979 }
00980
00981 if(FAILED(pGraph->ConnectDirect(ppinOut, GetFirstDisconnectedPin(pBF, PINDIR_INPUT), NULL)))
00982 {
00983 pGraph->RemoveFilter(pBF);
00984 return E_FAIL;
00985 }
00986
00987 return S_OK;
00988 }
00989
00990 STDMETHODIMP CStreamSwitcherOutputPin::Backout(IPin* ppinOut, IGraphBuilder* pGraph)
00991 {
00992 return S_OK;
00993 }
00994
00995
00996
00997
00998
00999 CStreamSwitcherFilter::CStreamSwitcherFilter(LPUNKNOWN lpunk, HRESULT* phr, const CLSID& clsid)
01000 : CBaseFilter(NAME("CStreamSwitcherFilter"), lpunk, &m_csState, clsid)
01001 {
01002 if(phr) *phr = S_OK;
01003
01004 HRESULT hr = S_OK;
01005
01006 do
01007 {
01008 CAutoPtr<CStreamSwitcherInputPin> pInput;
01009 CAutoPtr<CStreamSwitcherOutputPin> pOutput;
01010
01011 hr = S_OK;
01012 pInput.Attach(new CStreamSwitcherInputPin(this, &hr, L"Channel 1"));
01013 if(!pInput || FAILED(hr)) break;
01014
01015 hr = S_OK;
01016 pOutput.Attach(new CStreamSwitcherOutputPin(this, &hr));
01017 if(!pOutput || FAILED(hr)) break;
01018
01019 CAutoLock cAutoLock(&m_csPins);
01020
01021 m_pInputs.AddHead(m_pInput = pInput.Detach());
01022 m_pOutput = pOutput.Detach();
01023
01024 return;
01025 }
01026 while(false);
01027
01028 if(phr) *phr = E_FAIL;
01029 }
01030
01031 CStreamSwitcherFilter::~CStreamSwitcherFilter()
01032 {
01033 CAutoLock cAutoLock(&m_csPins);
01034
01035 POSITION pos = m_pInputs.GetHeadPosition();
01036 while(pos) delete m_pInputs.GetNext(pos);
01037 m_pInputs.RemoveAll();
01038 m_pInput = NULL;
01039
01040 delete m_pOutput;
01041 m_pOutput = NULL;
01042 }
01043
01044 STDMETHODIMP CStreamSwitcherFilter::NonDelegatingQueryInterface(REFIID riid, void** ppv)
01045 {
01046 return
01047 QI(IAMStreamSelect)
01048 __super::NonDelegatingQueryInterface(riid, ppv);
01049 }
01050
01051
01052
01053 int CStreamSwitcherFilter::GetPinCount()
01054 {
01055 CAutoLock cAutoLock(&m_csPins);
01056
01057 return(1 + (int)m_pInputs.GetCount());
01058 }
01059
01060 CBasePin* CStreamSwitcherFilter::GetPin(int n)
01061 {
01062 CAutoLock cAutoLock(&m_csPins);
01063
01064 if(n < 0 || n >= GetPinCount()) return NULL;
01065 else if(n == 0) return m_pOutput;
01066 else return m_pInputs.GetAt(m_pInputs.FindIndex(n-1));
01067 }
01068
01069 int CStreamSwitcherFilter::GetConnectedInputPinCount()
01070 {
01071 CAutoLock cAutoLock(&m_csPins);
01072
01073 int nConnected = 0;
01074
01075 POSITION pos = m_pInputs.GetHeadPosition();
01076 while(pos)
01077 {
01078 if(m_pInputs.GetNext(pos)->IsConnected())
01079 nConnected++;
01080 }
01081
01082 return(nConnected);
01083 }
01084
01085 CStreamSwitcherInputPin* CStreamSwitcherFilter::GetConnectedInputPin(int n)
01086 {
01087 if(n >= 0)
01088 {
01089 POSITION pos = m_pInputs.GetHeadPosition();
01090 while(pos)
01091 {
01092 CStreamSwitcherInputPin* pPin = m_pInputs.GetNext(pos);
01093 if(pPin->IsConnected())
01094 {
01095 if(n == 0) return(pPin);
01096 n--;
01097 }
01098 }
01099 }
01100
01101 return NULL;
01102 }
01103
01104 CStreamSwitcherInputPin* CStreamSwitcherFilter::GetInputPin()
01105 {
01106 return m_pInput;
01107 }
01108
01109 CStreamSwitcherOutputPin* CStreamSwitcherFilter::GetOutputPin()
01110 {
01111 return m_pOutput;
01112 }
01113
01114
01115
01116 HRESULT CStreamSwitcherFilter::CompleteConnect(PIN_DIRECTION dir, CBasePin* pPin, IPin* pReceivePin)
01117 {
01118 if(dir == PINDIR_INPUT)
01119 {
01120 CAutoLock cAutoLock(&m_csPins);
01121
01122 int nConnected = GetConnectedInputPinCount();
01123
01124 if(nConnected == 1)
01125 {
01126 m_pInput = (CStreamSwitcherInputPin*)pPin;
01127 }
01128
01129 if(nConnected == m_pInputs.GetCount())
01130 {
01131 CStringW name;
01132 name.Format(L"Channel %d", ++m_PinVersion);
01133
01134 HRESULT hr = S_OK;
01135 CStreamSwitcherInputPin* pPin = new CStreamSwitcherInputPin(this, &hr, name);
01136 if(!pPin || FAILED(hr)) return E_FAIL;
01137 m_pInputs.AddTail(pPin);
01138 }
01139 }
01140
01141 return S_OK;
01142 }
01143
01144
01145
01146 void CStreamSwitcherFilter::SelectInput(CStreamSwitcherInputPin* pInput)
01147 {
01148
01149 m_pInput = NULL;
01150
01151
01152 POSITION pos = m_pInputs.GetHeadPosition();
01153 while(pos)
01154 {
01155 CStreamSwitcherInputPin* pPin = m_pInputs.GetNext(pos);
01156 pPin->Block(false);
01157
01158 pPin->Block(true);
01159 }
01160
01161
01162 if(m_pOutput)
01163 {
01164 m_pOutput->DeliverBeginFlush();
01165 m_pOutput->DeliverEndFlush();
01166 }
01167
01168 if(!pInput) return;
01169
01170
01171 m_pInput = pInput;
01172
01173
01174 m_pInput->Block(false);
01175 }
01176
01177
01178
01179 HRESULT CStreamSwitcherFilter::Transform(IMediaSample* pIn, IMediaSample* pOut)
01180 {
01181 BYTE* pDataIn = NULL;
01182 BYTE* pDataOut = NULL;
01183
01184 HRESULT hr;
01185 if(FAILED(hr = pIn->GetPointer(&pDataIn))) return hr;
01186 if(FAILED(hr = pOut->GetPointer(&pDataOut))) return hr;
01187
01188 long len = pIn->GetActualDataLength();
01189 long size = pOut->GetSize();
01190
01191 if(!pDataIn || !pDataOut ) return S_FALSE;
01192
01193 memcpy(pDataOut, pDataIn, min(len, size));
01194 pOut->SetActualDataLength(min(len, size));
01195
01196 return S_OK;
01197 }
01198
01199 CMediaType CStreamSwitcherFilter::CreateNewOutputMediaType(CMediaType mt, long& cbBuffer)
01200 {
01201 return(mt);
01202 }
01203
01204
01205
01206 STDMETHODIMP CStreamSwitcherFilter::Count(DWORD* pcStreams)
01207 {
01208 if(!pcStreams) return E_POINTER;
01209
01210 CAutoLock cAutoLock(&m_csPins);
01211
01212 *pcStreams = GetConnectedInputPinCount();
01213
01214 return S_OK;
01215 }
01216
01217 STDMETHODIMP CStreamSwitcherFilter::Info(long lIndex, AM_MEDIA_TYPE** ppmt, DWORD* pdwFlags, LCID* plcid, DWORD* pdwGroup, WCHAR** ppszName, IUnknown** ppObject, IUnknown** ppUnk)
01218 {
01219 CAutoLock cAutoLock(&m_csPins);
01220
01221 CBasePin* pPin = GetConnectedInputPin(lIndex);
01222 if(!pPin) return E_INVALIDARG;
01223
01224 if(ppmt)
01225 *ppmt = CreateMediaType(&m_pOutput->CurrentMediaType());
01226
01227 if(pdwFlags)
01228 *pdwFlags = (m_pInput == pPin) ? AMSTREAMSELECTINFO_EXCLUSIVE : 0;
01229
01230 if(plcid)
01231 *plcid = 0;
01232
01233 if(pdwGroup)
01234 *pdwGroup = 0;
01235
01236 if(ppszName && (*ppszName = (WCHAR*)CoTaskMemAlloc((wcslen(pPin->Name())+1)*sizeof(WCHAR))))
01237 wcscpy(*ppszName, pPin->Name());
01238
01239 if(ppObject)
01240 *ppObject = NULL;
01241
01242 if(ppUnk)
01243 *ppUnk = NULL;
01244
01245 return S_OK;
01246 }
01247
01248 STDMETHODIMP CStreamSwitcherFilter::Enable(long lIndex, DWORD dwFlags)
01249 {
01250 if(dwFlags != AMSTREAMSELECTENABLE_ENABLE)
01251 return E_NOTIMPL;
01252
01253 PauseGraph;
01254
01255 CStreamSwitcherInputPin* pNewInput = GetConnectedInputPin(lIndex);
01256 if(!pNewInput) return E_INVALIDARG;
01257
01258 SelectInput(pNewInput);
01259
01260 ResumeGraph;
01261
01262 return S_OK;
01263 }
01264