/*++ Copyright (c) 1997 Microsoft Corporation Module Name: confvid.cpp Abstract: This module contains implementation of the video send and receive stream implementations. Author: Mu Han (muhan) 15-September-1999 --*/ #include "stdafx.h" #include "common.h" #include // for IRTPRPHFilter #include // for IRTPSPHFilter #include // AMRTP media types #include // rtp guids #include // for the h26X encoder filter #include #include // demux guid #include // for video CLSIDs ///////////////////////////////////////////////////////////////////////////// // // CStreamVideoRecv // ///////////////////////////////////////////////////////////////////////////// CStreamVideoRecv::CStreamVideoRecv() : CIPConfMSPStream(), m_pIRTPDemux(NULL) { m_szName = L"VideoRecv"; } HRESULT CStreamVideoRecv::Init( IN HANDLE hAddress, IN CMSPCallBase * pMSPCall, IN IMediaEvent * pIGraphBuilder, IN DWORD dwMediaType, IN TERMINAL_DIRECTION Direction ) /*++ Routine Description: Init our substream array and then call the base class' Init. Arguments: hAddress - a handle to the address, used in identify terminals. pMSPCall - the call object that owns the stream. pIGraphBuilder - the filter graph object. dwMediaType - the mediatype of this stream. Direction - the direction of this stream. Return Value: S_OK, E_OUTOFMEMORY --*/ { LOG((MSP_TRACE, "CSubStreamVideoRecvVideoSend::Init - enter")); // initialize the stream array so that the array is not NULL. if (!m_SubStreams.Grow()) { LOG((MSP_TRACE, "CSubStreamVideoRecvVideoSend::Init - return out of memory")); return E_OUTOFMEMORY; } return CIPConfMSPStream::Init( hAddress, pMSPCall, pIGraphBuilder,dwMediaType, Direction ); } HRESULT CStreamVideoRecv::ShutDown() /*++ Routine Description: Shut down the stream. Arguments: Return Value: S_OK --*/ { CLock lock(m_lock); // Release the memory for the local participant info items. for (int j = 0; j < RTCP_SDES_LAST - 1; j ++) { if (m_InfoItems[j]) { free(m_InfoItems[j]); m_InfoItems[j] = NULL; } } // Release the refcount on the call object. if (m_pMSPCall) { m_pMSPCall->MSPCallRelease(); m_pMSPCall = NULL; } // if there are branches and configured, we need to disconnect // the terminals and remove the branches. if (m_Branches.GetSize() > 0) { // Stop the graph before disconnecting the terminals. HRESULT hr = CMSPStream::StopStream(); if (FAILED(hr)) { LOG((MSP_ERROR, "stream %ws %p failed to stop, %x", m_szName, this, hr)); return hr; } for (int i = 0; i < m_Branches.GetSize(); i ++) { RemoveOneBranch(&m_Branches[i]); } m_Branches.RemoveAll(); } // free the extra filter reference. if (m_pEdgeFilter) { m_pEdgeFilter->Release(); m_pEdgeFilter = NULL; } if (m_pIRTPDemux) { m_pIRTPDemux->Release(); m_pIRTPDemux = NULL; } if (m_pRTPFilter) { m_pRTPFilter->Release(); m_pRTPFilter = NULL; } // release all the substream objects. for (int i = 0; i < m_SubStreams.GetSize(); i ++) { m_SubStreams[i]->Release(); } m_SubStreams.RemoveAll(); // release all the terminals. for (i = 0; i < m_Terminals.GetSize(); i ++ ) { m_Terminals[i]->Release(); } m_Terminals.RemoveAll(); // release all the participants. for (i = 0; i < m_Participants.GetSize(); i ++) { m_Participants[i]->Release(); } m_Participants.RemoveAll(); LOG((MSP_TRACE, "CStreamVideoRecv::Shutdown - exit S_OK")); return S_OK; } HRESULT CStreamVideoRecv::InternalCreateSubStream( OUT ITSubStream ** ppSubStream ) /*++ Routine Description: This method creat a substream object and add it into out list. Arguments: ppSubStream - the memory location that will store the returned SubStream. Return Value: S_OK E_OUTOFMEMORY E_NOINTERFACE --*/ { CComObject * pCOMSubStream; HRESULT hr = CComObject::CreateInstance(&pCOMSubStream); if (NULL == pCOMSubStream) { LOG((MSP_ERROR, "could not create video recv sub stream:%x", hr)); return hr; } ITSubStream* pSubStream; // get the interface pointer. hr = pCOMSubStream->_InternalQueryInterface( IID_ITSubStream, (void **)&pSubStream ); if (FAILED(hr)) { LOG((MSP_ERROR, "Create VideoRecv Substream QueryInterface failed: %x", hr)); delete pCOMSubStream; return hr; } // Initialize the object. hr = pCOMSubStream->Init(this); if (FAILED(hr)) { LOG((MSP_ERROR, "CreateMSPSubStream:call init failed: %x", hr)); pSubStream->Release(); return hr; } // Add the SubStream into our list of SubStreams. This takes a refcount. if (!m_SubStreams.Add(pSubStream)) { pSubStream->Release(); LOG((MSP_ERROR, "out of memory in adding a SubStream.")); return E_OUTOFMEMORY; } // AddRef the interface pointer and return it. pSubStream->AddRef(); *ppSubStream = pSubStream; return S_OK; } // ITSubStreamControl methods, called by the app. STDMETHODIMP CStreamVideoRecv::CreateSubStream( IN OUT ITSubStream ** ppSubStream ) /*++ Routine Description: This method creates a new substream on this video receive stream. Since the substreams are created based on the participants, this function returns only TAPI_E_NOTSUPPORTED. Arguments: ppSubStream - the memory location that will store the returned SubStream. Return Value: TAPI_E_NOTSUPPORTED --*/ { return TAPI_E_NOTSUPPORTED; } STDMETHODIMP CStreamVideoRecv::RemoveSubStream( IN ITSubStream * pSubStream ) /*++ Routine Description: This method remove substream on this video receive stream. Since the substreams are created based on the participants, this function returns only TAPI_E_NOTSUPPORTED. Arguments: pSubStream - the SubStream to be removed. Return Value: TAPI_E_NOTSUPPORTED --*/ { return TAPI_E_NOTSUPPORTED; } STDMETHODIMP CStreamVideoRecv::EnumerateSubStreams( OUT IEnumSubStream ** ppEnumSubStream ) /*++ Routine Description: This method returns an enumerator of the substreams. Arguments: ppEnumSubStream - the memory location to store the returned pointer. Return Value: S_OK E_POINTER E_UNEXPECTED E_OUTOFMEMORY --*/ { LOG((MSP_TRACE, "EnumerateSubStreams entered. ppEnumSubStream:%x", ppEnumSubStream)); // // Check parameters. // if (IsBadWritePtr(ppEnumSubStream, sizeof(VOID *))) { LOG((MSP_ERROR, "CMSPCallBase::EnumerateSubStreams - " "bad pointer argument - exit E_POINTER")); return E_POINTER; } // // First see if this call has been shut down. // acquire the lock before accessing the SubStream object list. // CLock lock(m_lock); if (m_SubStreams.GetData() == NULL) { LOG((MSP_ERROR, "CMSPCallBase::EnumerateSubStreams - " "call appears to have been shut down - exit E_UNEXPECTED")); // This call has been shut down. return E_UNEXPECTED; } // // Create an enumerator object. // typedef _CopyInterface CCopy; typedef CSafeComEnum CEnumerator; HRESULT hr; CComObject *pEnum = NULL; hr = CComObject::CreateInstance(&pEnum); if (pEnum == NULL) { LOG((MSP_ERROR, "CMSPCallBase::EnumerateSubStreams - " "Could not create enumerator object, %x", hr)); return hr; } // // query for the IID_IEnumSubStream i/f // IEnumSubStream * pEnumSubStream; hr = pEnum->_InternalQueryInterface(IID_IEnumSubStream, (void**)&pEnumSubStream); if (FAILED(hr)) { LOG((MSP_ERROR, "CMSPCallBase::EnumerateSubStreams - " "query enum interface failed, %x", hr)); delete pEnum; return hr; } // // Init the enumerator object. The CSafeComEnum can handle zero-sized array. // hr = pEnum->Init( m_SubStreams.GetData(), // the begin itor m_SubStreams.GetData() + m_SubStreams.GetSize(), // the end itor, NULL, // IUnknown AtlFlagCopy // copy the data. ); if (FAILED(hr)) { LOG((MSP_ERROR, "CMSPCallBase::EnumerateSubStreams - " "init enumerator object failed, %x", hr)); pEnumSubStream->Release(); return hr; } LOG((MSP_TRACE, "CMSPCallBase::EnumerateSubStreams - exit S_OK")); *ppEnumSubStream = pEnumSubStream; return hr; } STDMETHODIMP CStreamVideoRecv::get_SubStreams( OUT VARIANT * pVariant ) /*++ Routine Description: This method returns a collection of the substreams. Arguments: pVariant - a variant structure. Return Value: S_OK E_POINTER E_UNEXPECTED E_OUTOFMEMORY --*/ { LOG((MSP_TRACE, "CStreamVideoRecv::get_SubStreams - enter")); // // Check parameters. // if ( IsBadWritePtr(pVariant, sizeof(VARIANT) ) ) { LOG((MSP_ERROR, "CStreamVideoRecv::get_SubStreams - " "bad pointer argument - exit E_POINTER")); return E_POINTER; } // // See if this call has been shut down. Acquire the lock before accessing // the SubStream object list. // CLock lock(m_lock); if (m_SubStreams.GetData() == NULL) { LOG((MSP_ERROR, "CStreamVideoRecv::get_SubStreams - " "call appears to have been shut down - exit E_UNEXPECTED")); // This call has been shut down. return E_UNEXPECTED; } // // create the collection object - see mspcoll.h // typedef CTapiIfCollection< ITSubStream * > SubStreamCollection; CComObject * pCollection; HRESULT hr = CComObject::CreateInstance( &pCollection ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CStreamVideoRecv::get_SubStreams - " "can't create collection - exit 0x%08x", hr)); return hr; } // // get the Collection's IDispatch interface // IDispatch * pDispatch; hr = pCollection->_InternalQueryInterface(IID_IDispatch, (void **) &pDispatch ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CStreamVideoRecv::get_SubStreams - " "QI for IDispatch on collection failed - exit 0x%08x", hr)); delete pCollection; return hr; } // // Init the collection using an iterator -- pointers to the beginning and // the ending element plus one. // hr = pCollection->Initialize( m_SubStreams.GetSize(), m_SubStreams.GetData(), m_SubStreams.GetData() + m_SubStreams.GetSize() ); if (FAILED(hr)) { LOG((MSP_ERROR, "CStreamVideoRecv::get_SubStreams - " "Initialize on collection failed - exit 0x%08x", hr)); pDispatch->Release(); return hr; } // // put the IDispatch interface pointer into the variant // VariantInit(pVariant); pVariant->vt = VT_DISPATCH; pVariant->pdispVal = pDispatch; LOG((MSP_TRACE, "CStreamVideoRecv::get_SubStreams - exit S_OK")); return S_OK; } HRESULT CStreamVideoRecv::Configure( IN STREAMSETTINGS &StreamSettings ) /*++ Routine Description: Configure the settings of this stream. Arguments: StreamSettings - The setting structure got from the SDP blob. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoRecv configure entered.")); CLock lock(m_lock); _ASSERTE(m_fIsConfigured == FALSE); switch (StreamSettings.dwPayloadType) { case PAYLOAD_H261: m_pClsidCodecFilter = &CLSID_H261_DECODE_FILTER; m_pRPHInputMinorType = &MEDIASUBTYPE_RTP_Payload_H261; m_pClsidPHFilter = &CLSID_INTEL_RPHH26X; break; case PAYLOAD_H263: m_pClsidCodecFilter = &CLSID_H263_DECODE_FILTER; m_pRPHInputMinorType = &MEDIASUBTYPE_RTP_Payload_H263; m_pClsidPHFilter = &CLSID_INTEL_RPHH26X; break; default: LOG((MSP_ERROR, "unknow payload type, %x", StreamSettings.dwPayloadType)); return E_FAIL; } m_Settings = StreamSettings; m_fIsConfigured = TRUE; return InternalConfigure(); } HRESULT CStreamVideoRecv::CheckTerminalTypeAndDirection( IN ITTerminal * pTerminal ) /*++ Routine Description: Check to see if the terminal is allowed on this stream. Only video render terminal is allowed. Arguments: pTerminal - the terminal. Return value: S_OK TAPI_E_INVALIDTERMINAL */ { LOG((MSP_TRACE, "VideoRecv.CheckTerminalTypeAndDirection")); // check the media type of this terminal. long lMediaType; HRESULT hr = pTerminal->get_MediaType(&lMediaType); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal media type. %x", hr)); return TAPI_E_INVALIDTERMINAL; } if ((DWORD)lMediaType != m_dwMediaType) { return TAPI_E_INVALIDTERMINAL; } // check the direction of this terminal. TERMINAL_DIRECTION Direction; hr = pTerminal->get_Direction(&Direction); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal direction. %x", hr)); return TAPI_E_INVALIDTERMINAL; } if (Direction != m_Direction) { return TAPI_E_INVALIDTERMINAL; } return S_OK; } HRESULT CStreamVideoRecv::SubStreamSelectTerminal( IN ITSubStream * pITSubStream, IN ITTerminal * pITTerminal ) /*++ Routine Description: handle terminals being selected on the sub streams. It gives the terminal to one free branch and then sets up a mapping between the branch and the substream, so that the participant in the substream is displayed on the terminal selected. Arguments: pITSubStream - the Substream that got a terminal selected. pITTerminal - the terminal object. Return Value: S_OK --*/ { LOG((MSP_TRACE, "VideoRecv SubStreamSelectTerminal")); HRESULT hr; CLock lock(m_lock); // Call the base class's select terminal first. The terminal will be put // into the terminal pool and a branch of filters will be created for it. hr = CIPConfMSPStream::SelectTerminal(pITTerminal); if (FAILED(hr)) { return hr; } // Find out which branch got the terminal. int i; for (i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pITTerminal == pITTerminal) { break; } } _ASSERTE(i < m_Branches.GetSize()); if (i >= m_Branches.GetSize()) { return E_UNEXPECTED; } // Find out the participant on the SubStream. ITParticipant *pITParticipant = NULL; DWORD dwSSRC; if (((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pITParticipant ) == FALSE) { return E_UNEXPECTED; } pITParticipant->Release(); if (m_pIRTPDemux == NULL) { LOG((MSP_ERROR, "no demux filter")); return E_UNEXPECTED; } // map the pin to this SSRC only. hr = m_pIRTPDemux->MapSSRCToPin(dwSSRC, m_Branches[i].pIPin); if (FAILED(hr)) { LOG((MSP_ERROR, "map SSRC %x to pin %p returned %x", dwSSRC, m_Branches[i].pIPin, hr)); return hr; } _ASSERTE(m_Branches[i].pITSubStream == NULL); pITSubStream->AddRef(); m_Branches[i].pITSubStream = pITSubStream; m_Branches[i].dwSSRC = dwSSRC; return hr; } HRESULT CStreamVideoRecv::ConfigureRTPFilter( IN IBaseFilter * pIBaseFilter ) /*++ Routine Description: Configure the source RTP filter. Including set the address, port, TTL, QOS, thread priority, clockrate, etc. Arguments: pIBaseFilter - The source RTP Filter. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoRecv ConfigureRTPFilter")); HRESULT hr; // Get the IRTPStream interface pointer on the filter. CComQIPtr pIRTPStream(pIBaseFilter); if (pIRTPStream == NULL) { LOG((MSP_ERROR, "get RTP Stream interface")); return E_NOINTERFACE; } LOG((MSP_INFO, "set remote Address:%x, port:%d", m_Settings.dwIPRemote, m_Settings.wRTPPortRemote)); // Set the address and port used in the filter. if (FAILED(hr = pIRTPStream->SetAddress( htons(m_Settings.wRTPPortRemote), // local port to listen on. 0, // remote port. htonl(m_Settings.dwIPRemote) // remote address. ))) { LOG((MSP_ERROR, "set remote Address, hr:%x", hr)); return hr; } // Set the TTL used in the filter. if (FAILED(hr = pIRTPStream->SetMulticastScope(m_Settings.dwTTL))) { LOG((MSP_ERROR, "set TTL. %x", hr)); return hr; } if (m_Settings.dwIPLocal != INADDR_ANY) { // set the local interface that the socket should bind to LOG((MSP_INFO, "set locol Address:%x", m_Settings.dwIPLocal)); if (FAILED(hr = pIRTPStream->SelectLocalIPAddress( htonl(m_Settings.dwIPLocal) ))) { LOG((MSP_ERROR, "set locol Address, hr:%x", hr)); return hr; } } // Set the priority of the session if (FAILED(hr = pIRTPStream->SetSessionClassPriority( RTP_CLASS_VIDEO, g_dwVideoThreadPriority ))) { LOG((MSP_ERROR, "set session class and priority. %x", hr)); } // Set the sample rate of the session LOG((MSP_INFO, "setting session sample rate to %d", g_dwVideoSampleRate)); if (FAILED(hr = pIRTPStream->SetDataClock(g_dwVideoSampleRate))) { LOG((MSP_ERROR, "set session sample rate. %x", hr)); } // Enable the RTCP events if (FAILED(hr = ::EnableRTCPEvents(pIBaseFilter))) { LOG((MSP_WARN, "can not enable RTCP events %x", hr)); } DWORD dwLoopback = 0; if (TRUE == ::GetRegValue(gszMSPLoopback, &dwLoopback) && dwLoopback != 0) { // Loopback is required. if (FAILED(hr = ::SetLoopbackOption(pIBaseFilter, dwLoopback))) { LOG((MSP_ERROR, "set loopback option. %x", hr)); return hr; } } if (m_Settings.dwQOSLevel != QSL_BEST_EFFORT) { if (FAILED(hr = ::SetQOSOption( pIBaseFilter, m_Settings.dwPayloadType, // payload -1, // use the default bitrate (m_Settings.dwQOSLevel == QSL_NEEDED), // fail if no qos. TRUE, // receive stream. g_dwVideoChannels, // number of streams reserved. m_Settings.fCIF ))) { LOG((MSP_ERROR, "set QOS option. %x", hr)); return hr; } } SetLocalInfoOnRTPFilter(pIBaseFilter); return S_OK; } HRESULT CStreamVideoRecv::SetUpInternalFilters() /*++ Routine Description: set up the filters used in the stream. RTP->Demux->RPH->DECODER->Render terminal This function only creates the RTP and demux filter and the rest of the graph is connected in ConnectTerminal. Arguments: Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoRecv.SetUpInternalFilters")); CComPtr pSourceFilter; HRESULT hr; // create and add the source fitler. if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, CLSID_RTPSourceFilter, L"RtpSource", &pSourceFilter))) { LOG((MSP_ERROR, "adding source filter. %x", hr)); return hr; } if (FAILED(hr = ConfigureRTPFilter(pSourceFilter))) { LOG((MSP_ERROR, "configure RTP source filter. %x", hr)); return hr; } CComPtr pDemuxFilter; // create and add the demux fitler. if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, CLSID_IntelRTPDemux, L"RtpDemux", &pDemuxFilter))) { LOG((MSP_ERROR, "adding demux filter. %x", hr)); return hr; } // Connect the source filter and the demux filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pSourceFilter, (IBaseFilter *)pDemuxFilter))) { LOG((MSP_ERROR, "connect source filter and demux filter. %x", hr)); return hr; } // Get the IRTPParticipant interface pointer on the RTP filter. CComQIPtr pIRTPParticipant(pSourceFilter); if (pIRTPParticipant == NULL) { LOG((MSP_ERROR, "can't get RTP participant interface")); return E_NOINTERFACE; } CComQIPtr pIRTPDemux(pDemuxFilter); if (pIRTPDemux == NULL) { LOG((MSP_ERROR, "get RTP Demux interface")); return E_NOINTERFACE; } m_pEdgeFilter = pDemuxFilter; m_pEdgeFilter->AddRef(); _ASSERTE(m_pIRTPDemux == NULL); m_pIRTPDemux = pIRTPDemux; m_pIRTPDemux->AddRef(); m_pRTPFilter = pIRTPParticipant; m_pRTPFilter->AddRef(); return hr; } HRESULT CStreamVideoRecv::AddOneBranch( BRANCH * pBranch ) /*++ Routine Description: Create a new branch of filters off the demux. Arguments: pBranch - a pointer to a structure that remembers the info about the branch. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "AddOneBranch entered.")); _ASSERTE(m_pIRTPDemux != NULL); // Find the next output pin on the demux fitler. CComPtr pIPinOutput; HRESULT hr; // Set the media type on this output pin. if (FAILED(hr = ::FindPin( (IBaseFilter *)m_pEdgeFilter, (IPin**)&pIPinOutput, PINDIR_OUTPUT ))) { LOG((MSP_ERROR, "find free pin on demux, %x", hr)); return hr; } // Set the media type on this output pin. if (FAILED(hr = m_pIRTPDemux->SetPinTypeInfo( pIPinOutput, (BYTE)m_Settings.dwPayloadType, *m_pRPHInputMinorType ))) { LOG((MSP_ERROR, "set demux output pin type info, %x", hr)); return hr; } LOG((MSP_INFO, "set demux output pin payload type to %d", m_Settings.dwPayloadType)); // Set the default timeout on this output pin. if (FAILED(hr = m_pIRTPDemux->SetPinSourceTimeout( pIPinOutput, g_dwVideoPinTimeOut ))) { LOG((MSP_ERROR, "set demux output pin type info, %x", hr)); return hr; } LOG((MSP_INFO, "set demux output pin timeout to %dms", g_dwVideoPinTimeOut)); // Create and add the payload handler into the filtergraph. CComPtr pIRPHFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, *m_pClsidPHFilter, L"RPH", &pIRPHFilter ))) { LOG((MSP_ERROR, "add RPH filter. %x", hr)); return hr; } // Get the IRPHH26XSettings interface used in configuring the RPH // filter to the right image size. CComQIPtr pIRPHH26XSettings(pIRPHFilter); if (pIRPHH26XSettings == NULL) { LOG((MSP_WARN, "can't get IRPHH26XSettings interface")); } else if (FAILED(pIRPHH26XSettings->SetCIF(m_Settings.fCIF))) { LOG((MSP_WARN, "can't set CIF or QCIF")); } // Connect the payload handler to the output pin on the demux. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IPin *)pIPinOutput, (IBaseFilter *)pIRPHFilter ))) { LOG((MSP_ERROR, "connect demux and RPH filter. %x", hr)); m_pIGraphBuilder->RemoveFilter(pIRPHFilter); return hr; } CComPtr pCodecFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, *m_pClsidCodecFilter, L"codec", &pCodecFilter ))) { LOG((MSP_ERROR, "add Codec filter. %x", hr)); return hr; } // Connect the payload handler to the output pin on the demux. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pIRPHFilter, (IBaseFilter *)pCodecFilter ))) { LOG((MSP_ERROR, "connect RPH filter and codec. %x", hr)); m_pIGraphBuilder->RemoveFilter(pIRPHFilter); m_pIGraphBuilder->RemoveFilter(pCodecFilter); return hr; } pBranch->pIPin = pIPinOutput; pBranch->pRPHFilter = pIRPHFilter; pBranch->pCodecFilter = pCodecFilter; pBranch->pIPin->AddRef(); pBranch->pRPHFilter->AddRef(); pBranch->pCodecFilter->AddRef(); LOG((MSP_TRACE, "AddOneBranch exits ok.")); return S_OK; } HRESULT CStreamVideoRecv::RemoveOneBranch( BRANCH * pBranch ) /*++ Routine Description: Remove all the filters in a branch and release all the pointers. the caller of this function should not use any member of this branch after this function call. Arguments: pBranch - a pointer to a structure that has the info about the branch. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "RemoveOneBranch entered.")); if (pBranch->pIPin) { pBranch->pIPin->Release(); } if (pBranch->pRPHFilter) { m_pIGraphBuilder->RemoveFilter(pBranch->pRPHFilter); pBranch->pRPHFilter->Release(); } if (pBranch->pCodecFilter) { m_pIGraphBuilder->RemoveFilter(pBranch->pCodecFilter); pBranch->pCodecFilter->Release(); } if (pBranch->pITTerminal) { // get the terminal control interface. CComQIPtr pTerminal(pBranch->pITTerminal); _ASSERTE(pTerminal != NULL); if (pTerminal != NULL) { HRESULT hr = pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); LOG((MSP_TRACE, "terminal %p is disonnected. hr:%x", pBranch->pITTerminal, hr)); } pBranch->pITTerminal->Release(); } if (pBranch->pITSubStream) { ((CSubStreamVideoRecv*)pBranch->pITSubStream)-> ClearCurrentTerminal(); pBranch->pITSubStream->Release(); } LOG((MSP_TRACE, "RemoveOneBranch exits ok.")); return S_OK; } HRESULT CStreamVideoRecv::ConnectCodecToTerminal( IN IBaseFilter * pCodecFilter, IN ITTerminal * pITTerminal ) /*++ Routine Description: Connect the codec filter to the render filter inside the terminal. Arguments: pCodecFilter - a pointer to the Codec filter. pITTerminal - the terminal object. Return Value: HRESULT. --*/ { // get the terminal control interface. CComQIPtr pTerminal(pITTerminal); if (pTerminal == NULL) { LOG((MSP_ERROR, "can't get Terminal Control interface")); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, E_NOINTERFACE, pITTerminal); return E_NOINTERFACE; } // try to disable DDraw because our decoders can't handle DDraw. HRESULT hr2; IDrawVideoImage *pIDrawVideoImage; hr2 = pTerminal->QueryInterface(IID_IDrawVideoImage, (void **)&pIDrawVideoImage); if (SUCCEEDED(hr2)) { hr2 = pIDrawVideoImage->DrawVideoImageBegin(); if (FAILED(hr2)) { LOG((MSP_WARN, "Can't disable DDraw. %x", hr2)); } else { LOG((MSP_INFO, "DDraw disabled.")); } pIDrawVideoImage->Release(); } else { LOG((MSP_WARN, "Can't get IDrawVideoImage. %x", hr2)); } const DWORD MAXPINS = 8; DWORD dwNumPins = MAXPINS; IPin * Pins[MAXPINS]; HRESULT hr = pTerminal->ConnectTerminal(m_pIGraphBuilder, 0, &dwNumPins, Pins); if (FAILED(hr)) { LOG((MSP_ERROR, "can't connect to terminal, %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_CONNECT_FAIL, hr, pITTerminal); return hr; } // the number of pins should never be 0. if (dwNumPins == 0) { LOG((MSP_ERROR, "terminal has no pins.")); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, pITTerminal); pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); m_lock.Unlock(); return E_UNEXPECTED; } // Connect the codec filter to the video render terminal. hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pCodecFilter, (IPin *)Pins[0], FALSE // use Connect instead of ConnectDirect. ); // release the refcounts on the pins. for (DWORD i = 0; i < dwNumPins; i ++) { Pins[i]->Release(); } if (FAILED(hr)) { LOG((MSP_ERROR, "connect to the codec filter. %x", hr)); pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); return hr; } // // Now we are actually connected. Update our state and perform postconnection // (ignore postconnection error code). // pTerminal->CompleteConnectTerminal(); return hr; } HRESULT CStreamVideoRecv::ConnectTerminal( IN ITTerminal * pITTerminal ) /*++ Routine Description: connect video render terminal. Arguments: pITTerminal - The terminal to be connected. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoRecv.ConnectTerminal, pTerminal %p", pITTerminal)); HRESULT hr; // if our filters have not been contructed, do it now. if (m_pEdgeFilter == NULL) { hr = SetUpInternalFilters(); if (FAILED(hr)) { LOG((MSP_ERROR, "Set up internal filter failed, %x", hr)); CleanUpFilters(); return hr; } } // first create the RPH and CODEC filter needed before the terminal. BRANCH aBranch; ZeroMemory(&aBranch, sizeof BRANCH); hr = AddOneBranch(&aBranch); if (FAILED(hr)) { LOG((MSP_ERROR, "Set up a new decode branch failed, %x", hr)); return hr; } // finish the connection. hr = ConnectCodecToTerminal(aBranch.pCodecFilter, pITTerminal); if (FAILED(hr)) { LOG((MSP_ERROR, "connecting codec to terminal failed, %x", hr)); // remove the added filters from the graph. RemoveOneBranch(&aBranch); return hr; } pITTerminal->AddRef(); aBranch.pITTerminal = pITTerminal; if (!m_Branches.Add(aBranch)) { RemoveOneBranch(&aBranch); return E_OUTOFMEMORY; } return S_OK; } HRESULT CStreamVideoRecv::DisconnectTerminal( IN ITTerminal * pITTerminal ) /*++ Routine Description: Disconnect a terminal. It will remove its filters from the graph and also release its references to the graph. A branch of filters is also released. Arguments: pITTerminal - the terminal. Return Value: HRESULT. --*/ { for (int i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pITTerminal == pITTerminal) { break; } } if (i < m_Branches.GetSize()) { RemoveOneBranch(&m_Branches[i]); m_Branches.RemoveAt(i); } return S_OK; } HRESULT CStreamVideoRecv::SetUpFilters() /*++ Routine Description: Insert filters into the graph and connect to the terminals. Arguments: Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoRecv.SetUpFilters")); // if our filters have not been contructed, do it now. if (m_pEdgeFilter == NULL) { HRESULT hr = SetUpInternalFilters(); if (FAILED(hr)) { LOG((MSP_ERROR, "Set up internal filter failed, %x", hr)); CleanUpFilters(); return hr; } } for (int i = 0; i < m_Terminals.GetSize(); i ++) { HRESULT hr = ConnectTerminal(m_Terminals[i]); if (FAILED(hr)) { return hr; } } return S_OK; } // ITParticipantSubStreamControl methods, called by the app. STDMETHODIMP CStreamVideoRecv::get_SubStreamFromParticipant( IN ITParticipant * pITParticipant, OUT ITSubStream ** ppITSubStream ) /*++ Routine Description: Find out which substream is rendering the participant. Arguments: pITParticipant - the participant. ppITSubStream - the returned sub stream. Return Value: S_OK, TAPI_E_NOITEMS, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "get substream from participant:%p", pITParticipant)); if (IsBadWritePtr(ppITSubStream, sizeof(VOID *))) { LOG((MSP_ERROR, "ppITSubStream is a bad pointer")); return E_POINTER; } CLock lock(m_lock); ITSubStream * pITSubStream = NULL; // find out which substream has the participant. for (int i = 0; i < m_SubStreams.GetSize(); i ++) { ITParticipant *pTempParticipant; DWORD dwSSRC; ((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pTempParticipant ); _ASSERTE(pTempParticipant != NULL); pTempParticipant->Release(); // we dont' need the ref here. if (pITParticipant == pTempParticipant) { pITSubStream = m_SubStreams[i]; pITSubStream->AddRef(); break; } } if (pITSubStream == NULL) { return TAPI_E_NOITEMS; } *ppITSubStream = pITSubStream; return S_OK; } STDMETHODIMP CStreamVideoRecv::get_ParticipantFromSubStream( IN ITSubStream * pITSubStream, OUT ITParticipant ** ppITParticipant ) /*++ Routine Description: Find out which participant the substream is rendering. Arguments: pITSubStream - the sub stream. ppITParticipant - the returned participant Return Value: S_OK, TAPI_E_NOITEMS, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "get participant from substream:%p", pITSubStream)); if (IsBadWritePtr(ppITParticipant, sizeof(VOID *))) { LOG((MSP_ERROR, "ppITParticipant is a bad pointer")); return E_POINTER; } CLock lock(m_lock); int i; // check to see if the substream is in our list. if ((i = m_SubStreams.Find(pITSubStream)) < 0) { LOG((MSP_ERROR, "wrong SubStream handle %p", pITSubStream)); return E_INVALIDARG; } ITParticipant *pITParticipant; DWORD dwSSRC; if (((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pITParticipant ) == FALSE) { return TAPI_E_NOITEMS; } *ppITParticipant = pITParticipant; return S_OK; } STDMETHODIMP CStreamVideoRecv::SwitchTerminalToSubStream( IN ITTerminal * pITTerminal, IN ITSubStream * pITSubStream ) /*++ Routine Description: Switch terminal to a substream to display the participant that is on the substream. Arguments: pITTerminal - the terminal. pITSubStream - the sub stream. Return Value: S_OK, E_INVALIDARG, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "switch terminal %p to substream:%p", pITTerminal, pITSubStream)); CLock lock(m_lock); if (m_pIRTPDemux == NULL) { LOG((MSP_ERROR, "the demux filter doesn't exist.")); return E_UNEXPECTED; } // first, find out which branch has the terminal now. for (int i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pITTerminal == pITTerminal) { break; } } if (i >= m_Branches.GetSize()) { LOG((MSP_TRACE, "terminal %p doesn't exist", pITTerminal)); return E_INVALIDARG; } // second, find out if the substream exists. if (m_SubStreams.Find(pITSubStream) < 0) { LOG((MSP_TRACE, "SubStream %p doesn't exist", pITSubStream)); return E_INVALIDARG; } // thrid, find the participant on the substream and configure the demux // filter to render the participant on the chosen branch. ITParticipant *pITParticipant = NULL; DWORD dwSSRC; ((CSubStreamVideoRecv*)pITSubStream)->GetCurrentParticipant( &dwSSRC, &pITParticipant ) ; _ASSERTE(pITParticipant != NULL); // we don't need the reference here. pITParticipant->Release(); // map the pin to this SSRC only. HRESULT hr = m_pIRTPDemux->MapSSRCToPin(dwSSRC, m_Branches[i].pIPin); if (FAILED(hr)) { LOG((MSP_ERROR, "map SSRC %x to pin %p returned %x", dwSSRC, m_Branches[i].pIPin, hr)); return hr; } DWORD dwOldSSRC = 0; // Finally, set up the mappings among the branch, the substream and // the terminal // release the refcount on the old branch that the substream was on. for (int j = 0; j < m_Branches.GetSize(); j ++) { if (m_Branches[j].pITSubStream == pITSubStream) { m_Branches[j].pITSubStream->Release(); m_Branches[j].pITSubStream = NULL; break; } } if (m_Branches[i].pITSubStream != NULL) { ((CSubStreamVideoRecv*)m_Branches[i].pITSubStream)-> ClearCurrentTerminal(); m_Branches[i].pITSubStream->Release(); dwOldSSRC = m_Branches[i].dwSSRC; } pITSubStream->AddRef(); m_Branches[i].pITSubStream = pITSubStream; m_Branches[i].dwSSRC = dwSSRC; ((CSubStreamVideoRecv*)pITSubStream)->ClearCurrentTerminal(); ((CSubStreamVideoRecv*)pITSubStream)->SetCurrentTerminal( m_Branches[i].pITTerminal ); // After all the steps, we still have to change QOS reservation. if (dwOldSSRC != 0) { // cancel QOS for the old participant. if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwOldSSRC, 0))) { LOG((MSP_ERROR, "disabling QOS for %x. hr:%x", dwOldSSRC, hr)); } else { LOG((MSP_INFO, "disabled video QOS for %x.", dwOldSSRC)); } } // reserve QOS for the new participant. if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 1))) { LOG((MSP_ERROR, "enabling video QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "enabled video QOS for %x.", dwSSRC)); } return S_OK; } HRESULT CStreamVideoRecv::ProcessNewSender( IN DWORD dwSSRC, IN ITParticipant *pITParticipant ) /*++ Routine Description: A sender has just joined. A substream needs to be created for the participant. A pin mapped event might have happended when we didn't have the participant's name so it was queued in a list. Now that we have a new participant, let's check if this is the same participant. If it is, we complete the pin mapped event by sending the app an notification. Arguments: dwSSRC - the SSRC of the participant. pITParticipant - the participant object. Return Value: S_OK, E_UNEXPECTED --*/ { CLock lock(m_lock); if (m_pRTPFilter == NULL) { LOG((MSP_ERROR, "the network filter doesn't exist.")); return E_UNEXPECTED; } // Find out if a substream has been created for this participant when we // processed PinMapped event and receiver reports. for (int i = 0; i < m_SubStreams.GetSize(); i ++) { ITParticipant *pTempParticipant = NULL; DWORD dwSSRC; ((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pTempParticipant ); _ASSERTE(pTempParticipant != NULL); pTempParticipant->Release(); // we dont' need the ref here. if (pITParticipant == pTempParticipant) { // the participant has been created. return S_OK; } } ITSubStream * pITSubStream; HRESULT hr = InternalCreateSubStream(&pITSubStream); if (FAILED(hr)) { LOG((MSP_ERROR, "%ls can't create a SubStream, %x", m_szName, hr)); return hr; } ((CSubStreamVideoRecv*)pITSubStream)->SetCurrentParticipant( dwSSRC, pITParticipant ); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_NEW_SUBSTREAM, pITParticipant, pITSubStream ); // look at the pending SSRC list and find out if this report // fits in the list. IPin *pIPin = NULL; for (i = 0; i < m_PinMappedEvents.GetSize(); i ++) { if (m_PinMappedEvents[i].dwSSRC == dwSSRC) { pIPin = m_PinMappedEvents[i].pIPin; break; } } if (!pIPin) { // the SSRC is not in the list of pending PinMappedEvents. LOG((MSP_TRACE, "the SSRC %x is not in the pending list", dwSSRC)); pITSubStream->Release(); return S_OK;; } // get rid of the peding event. m_PinMappedEvents.RemoveAt(i); // reserve QOS since we are rendering this sender. if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 1))) { LOG((MSP_ERROR, "enabling video QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "enabled video QOS for %x.", dwSSRC)); } // tell the app about the newly mapped sender. for (i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pIPin == pIPin) { if (m_Branches[i].pITSubStream != NULL) { ((CSubStreamVideoRecv*)m_Branches[i].pITSubStream) ->ClearCurrentTerminal(); m_Branches[i].pITSubStream->Release(); } m_Branches[i].dwSSRC = dwSSRC; m_Branches[i].pITSubStream = pITSubStream; pITSubStream->AddRef(); ((CSubStreamVideoRecv*)pITSubStream)-> SetCurrentTerminal(m_Branches[i].pITTerminal); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_MAPPED, pITParticipant, pITSubStream ); break; } } pITSubStream->Release(); return S_OK; } HRESULT CStreamVideoRecv::NewParticipantPostProcess( IN DWORD dwSSRC, IN ITParticipant *pITParticipant ) /*++ Routine Description: A pin mapped event might have happended when we didn't have the participant's name so it was queued in a list. Now that we have a new participant, let's check if this is the same participant. If it is, we complete the pin mapped event by creating a substream and send the app a notification. Arguments: dwSSRC - the SSRC of the participant. pITParticipant - the participant object. Return Value: S_OK, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "%ls Check pending mapped event, dwSSRC: %x", m_szName, dwSSRC)); // look at the pending SSRC list and find out if this report // fits in the list. IPin *pIPin = NULL; for (int i = 0; i < m_PinMappedEvents.GetSize(); i ++) { if (m_PinMappedEvents[i].dwSSRC == dwSSRC) { pIPin = m_PinMappedEvents[i].pIPin; break; } } if (!pIPin) { // the SSRC is not in the list of pending PinMappedEvents. LOG((MSP_TRACE, "the SSRC %x is not in the pending list", dwSSRC)); return S_OK;; } ITSubStream * pITSubStream; HRESULT hr = InternalCreateSubStream(&pITSubStream); if (FAILED(hr)) { LOG((MSP_ERROR, "%ls can't create a SubStream, %x", m_szName, hr)); return hr; } ((CSubStreamVideoRecv*)pITSubStream)->SetCurrentParticipant( dwSSRC, pITParticipant ); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_NEW_SUBSTREAM, pITParticipant, pITSubStream ); // get rid of the peding event. m_PinMappedEvents.RemoveAt(i); if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 1))) { LOG((MSP_ERROR, "enabling video QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "enabled video QOS for %x.", dwSSRC)); } // Now we get the participant, the substream, and the pin. Establish a mapping // between the decoding branch and the substream. for (i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pIPin == pIPin) { if (m_Branches[i].pITSubStream != NULL) { ((CSubStreamVideoRecv*)m_Branches[i].pITSubStream) ->ClearCurrentTerminal(); m_Branches[i].pITSubStream->Release(); } m_Branches[i].dwSSRC = dwSSRC; m_Branches[i].pITSubStream = pITSubStream; pITSubStream->AddRef(); ((CSubStreamVideoRecv*)pITSubStream)-> SetCurrentTerminal(m_Branches[i].pITTerminal); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_MAPPED, pITParticipant, pITSubStream ); break; } } _ASSERTE(i < m_Branches.GetSize()); pITSubStream->Release(); return S_OK; } HRESULT CStreamVideoRecv::ProcessPinMappedEvent( IN IPin * pIPin ) /*++ Routine Description: A pin just got a new SSRC mapped to it. If the participant doesn't exist, put the event in a pending queue and wait for a RTCP report that has the participant's name. If the participant exists, check to see if a SubStream has been created for the stream. If not, a SubStream is created. Then a Particiapnt substream event is fired. Arguments: pIPin - the output pin of the demux filter that just got a new SSRC. Return Value: S_OK, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "%ls Process pin mapped event, pIPin: %p", m_szName, pIPin)); CLock lock(m_lock); if (m_pIRTPDemux == NULL) { LOG((MSP_ERROR, "the demux filter doesn't exist.")); return E_UNEXPECTED; } for (int iBranch = 0; iBranch < m_Branches.GetSize(); iBranch ++) { if (m_Branches[iBranch].pIPin == pIPin) { break; } } LOG((MSP_INFO, "Branch %d has the pin", iBranch)); if (iBranch >= m_Branches.GetSize()) { LOG((MSP_ERROR, "Wrong pin is mapped. %p", pIPin)); return E_UNEXPECTED; } BYTE PayloadType; DWORD dwSSRC; BOOL fAutoMapping; DWORD dwTimeOut; HRESULT hr = m_pIRTPDemux->GetPinInfo( pIPin, &dwSSRC, &PayloadType, &fAutoMapping, &dwTimeOut ); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get info for pin:%p, hr:%x", pIPin, hr)); return E_UNEXPECTED; } // sometimes we might get a mapped event for branches that are still // in use. if (m_Branches[iBranch].pITSubStream != NULL) { // sometimes we might get duplicated map events if (m_Branches[iBranch].dwSSRC == dwSSRC) { LOG((MSP_ERROR, "The same pin mapped twice. %p", pIPin)); return E_UNEXPECTED; } else { LOG((MSP_ERROR, "The branch is in use. Cleaning up.")); ((CSubStreamVideoRecv*)m_Branches[iBranch].pITSubStream)-> ClearCurrentTerminal(); m_Branches[iBranch].pITSubStream->Release(); m_Branches[iBranch].pITSubStream = NULL; m_Branches[iBranch].dwSSRC = 0; } } ITParticipant * pITParticipant = NULL; // find the SSRC in our participant list. for (int i = 0; i < m_Participants.GetSize(); i ++) { if (((CParticipant *)m_Participants[i])-> HasSSRC((ITStream *)this, dwSSRC)) { pITParticipant = m_Participants[i]; break; } } // if the participant is not there yet, put the event in a queue and it // will be fired when we have the CName fo the participant. if (!pITParticipant) { LOG((MSP_INFO, "can't find a participant that has SSRC %x", dwSSRC)); PINMAPEVENT Event; Event.pIPin = pIPin; Event.dwSSRC = dwSSRC; m_PinMappedEvents.Add(Event); LOG((MSP_INFO, "added the event to pending list, new list size:%d", m_PinMappedEvents.GetSize())); return S_OK; } // Enable QOS for the participant since it is being rendered. if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 1))) { LOG((MSP_ERROR, "enabling vidoe QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "enabled video QOS for %x.", dwSSRC)); } // Find out if a substream has been created for this participant who might // have been a receiver only and hasn't got a substream. ITSubStream * pITSubStream = NULL; for (i = 0; i < m_SubStreams.GetSize(); i ++) { ITParticipant *pTempParticipant; DWORD dwSSRC; ((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pTempParticipant ); _ASSERTE(pTempParticipant != NULL); pTempParticipant->Release(); // we dont' need the ref here. if (pITParticipant == pTempParticipant) { pITSubStream = m_SubStreams[i]; pITSubStream->AddRef(); break; } } if (pITSubStream == NULL) { // we need to create a substream for this participant since he has // started sending. hr = InternalCreateSubStream(&pITSubStream); if (FAILED(hr)) { LOG((MSP_ERROR, "%ls can't create a SubStream, %x", m_szName, hr)); return hr; } ((CSubStreamVideoRecv*)pITSubStream)->SetCurrentParticipant( dwSSRC, pITParticipant ); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_NEW_SUBSTREAM, pITParticipant, pITSubStream ); } if (((CSubStreamVideoRecv*)pITSubStream)->ClearCurrentTerminal()) { // The substrem has a terminal before. This is an error. LOG((MSP_ERROR, "SubStream %p has already got a terminal", pITSubStream)); // remove the mapping if the substream was mapped to a branch. for (i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pITSubStream == pITSubStream) { m_Branches[i].pITSubStream->Release(); m_Branches[i].pITSubStream = NULL; m_Branches[i].dwSSRC = 0; LOG((MSP_ERROR, "SubStream %p was mapped to branch %d", i)); break; } } } // Now we get the participant, the substream, and the pin. Establish a mapping // between the decoding branch and the substream. m_Branches[iBranch].dwSSRC = dwSSRC; m_Branches[iBranch].pITSubStream = pITSubStream; pITSubStream->AddRef(); ((CSubStreamVideoRecv*)pITSubStream)-> SetCurrentTerminal(m_Branches[iBranch].pITTerminal); ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_MAPPED, pITParticipant, pITSubStream ); pITSubStream->Release(); return S_OK; } HRESULT CStreamVideoRecv::ProcessPinUnmapEvent( IN IPin * pIPin ) /*++ Routine Description: A pin just got unmapped by the demux. Notify the app which substream is not going to have any data. Arguments: pIPin - the output pin of the demux filter Return Value: S_OK, E_UNEXPECTED --*/ { LOG((MSP_TRACE, "%ls Proces pin unmapped event, pIPin: %p", m_szName, pIPin)); CLock lock(m_lock); if (m_pIRTPDemux == NULL) { LOG((MSP_ERROR, "the demux filter doesn't exist.")); return E_UNEXPECTED; } // look at the pending SSRC list and find out if the pin is in the // pending list. for (int i = 0; i < m_PinMappedEvents.GetSize(); i ++) { if (m_PinMappedEvents[i].pIPin == pIPin) { break; } } // if the pin is in the pending list, just remove it. if (i < m_PinMappedEvents.GetSize()) { m_PinMappedEvents.RemoveAt(i); return S_OK; } // find out which substream got unmapped. ITSubStream * pITSubStream = NULL; for (i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pIPin == pIPin) { pITSubStream = m_Branches[i].pITSubStream; if (pITSubStream) { // Don't release the ref until the end of this function. m_Branches[i].pITSubStream = NULL; m_Branches[i].dwSSRC = 0; } break; } } if (!pITSubStream) { LOG((MSP_ERROR, "can't find a substream that got unmapped.")); return TAPI_E_NOITEMS; } ((CSubStreamVideoRecv*)pITSubStream)->ClearCurrentTerminal(); ITParticipant *pITParticipant = NULL; DWORD dwSSRC; ((CSubStreamVideoRecv*)pITSubStream)->GetCurrentParticipant( &dwSSRC, &pITParticipant ) ; _ASSERTE(pITParticipant != NULL); if (pITParticipant != NULL) { // fire an event to tell the app that the substream is not used. ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_UNMAPPED, pITParticipant, pITSubStream ); pITParticipant->Release(); // cancel QOS for this participant. HRESULT hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 0); if (FAILED(hr)) { LOG((MSP_ERROR, "disabling QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "disabled video QOS for %x.", dwSSRC)); } } pITSubStream->Release(); return S_OK; } HRESULT CStreamVideoRecv::ProcessParticipantLeave( IN DWORD dwSSRC ) /*++ Routine Description: When participant left the session, remove the stream from the participant object's list of streams. If all streams are removed, remove the participant from the call object's list too. Arguments: dwSSRC - the SSRC of the participant left. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "%ls ProcessParticipantLeave, SSRC: %x", m_szName, dwSSRC)); CLock lock(m_lock); if (m_pRTPFilter == NULL) { LOG((MSP_ERROR, "the network filter doesn't exist.")); return E_UNEXPECTED; } CParticipant *pParticipant; BOOL fLast = FALSE; HRESULT hr = E_FAIL; // first try to find the SSRC in our participant list. for (int iParticipant = 0; iParticipant < m_Participants.GetSize(); iParticipant ++) { pParticipant = (CParticipant *)m_Participants[iParticipant]; hr = pParticipant->RemoveStream( (ITStream *)this, dwSSRC, &fLast ); if (SUCCEEDED(hr)) { break; } } // if the participant is not found if (FAILED(hr)) { LOG((MSP_ERROR, "%ws, can't find the SSRC %x", m_szName, dwSSRC)); return hr; } ITParticipant *pITParticipant = m_Participants[iParticipant]; // cancel QOS for this participant. if (FAILED(hr = m_pRTPFilter->SetParticipantQOSstate(dwSSRC, 0))) { LOG((MSP_ERROR, "disabling QOS for %x. hr:%x", dwSSRC, hr)); } else { LOG((MSP_INFO, "disabled video QOS for %x.", dwSSRC)); } // find out which substream is going away. ITSubStream * pITSubStream = NULL; for (int i = 0; i < m_SubStreams.GetSize(); i ++) { // Find out the participant on the SubStream. ITParticipant *pTempParticipant; DWORD dwSSRC; ((CSubStreamVideoRecv*)m_SubStreams[i])->GetCurrentParticipant( &dwSSRC, &pTempParticipant ); _ASSERTE(pTempParticipant != NULL); pTempParticipant->Release(); // we dont' need the ref here. if (pTempParticipant == pITParticipant) { pITSubStream = m_SubStreams[i]; break; } } if (pITSubStream) { // remove the mapping if the substream was mapped to a branch. for (int i = 0; i < m_Branches.GetSize(); i ++) { if (m_Branches[i].pITSubStream == pITSubStream) { m_Branches[i].pITSubStream->Release(); m_Branches[i].pITSubStream = NULL; m_Branches[i].dwSSRC = 0; // fire an event to tell the app that the substream is not used. ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_UNMAPPED, pITParticipant, pITSubStream ); break; } } ((CIPConfMSPCall *)m_pMSPCall)->SendParticipantEvent( PE_SUBSTREAM_REMOVED, pITParticipant, pITSubStream ); if (m_SubStreams.Remove(pITSubStream)) { pITSubStream->Release(); } } m_Participants.RemoveAt(iParticipant); // if this stream is the last stream that the participant is on, // tell the call object to remove it from its list. if (fLast) { ((CIPConfMSPCall *)m_pMSPCall)->ParticipantLeft(pITParticipant); } pITParticipant->Release(); return S_OK; } HRESULT CStreamVideoRecv::ProcessGraphEvent( IN long lEventCode, IN long lParam1, IN long lParam2 ) { LOG((MSP_TRACE, "%ws ProcessGraphEvent %d", m_szName, lEventCode)); switch (lEventCode) { // These events are designed to solve the problem of mapping video // windows to incoming streams. The app needs to know which window // should be painted. Whenever the demux starts using a RPH pin to // stream data, it sends a MAPPED event. The first parameter is the // input pin on the RPH, the second parameter is the payload type. // When the demux stops using a pin, it sends a UNMAPPED event. case RTPDMX_EVENTBASE + RTPDEMUX_PIN_MAPPED: LOG((MSP_INFO, "handling pin mapped event, Pin%x", lParam1)); ProcessPinMappedEvent((IPin *)lParam1); break; case RTPDMX_EVENTBASE + RTPDEMUX_PIN_UNMAPPED: LOG((MSP_INFO, "handling pin unmap event, Pin%x", lParam1)); ProcessPinUnmapEvent((IPin *)lParam1); break; default: return CIPConfMSPStream::ProcessGraphEvent( lEventCode, lParam1, lParam2 ); } return S_OK; } ///////////////////////////////////////////////////////////////////////////// // // CStreamVideoSend // ///////////////////////////////////////////////////////////////////////////// CStreamVideoSend::CStreamVideoSend() : CIPConfMSPStream() { m_szName = L"VideoSend"; } HRESULT CStreamVideoSend::Configure( IN STREAMSETTINGS &StreamSettings ) /*++ Routine Description: Configure this stream. Arguments: StreamSettings - The setting structure got from the SDP blob. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoSend.Configure")); CLock lock(m_lock); _ASSERTE(m_fIsConfigured == FALSE); switch (StreamSettings.dwPayloadType) { case PAYLOAD_H261: m_pClsidCodecFilter = &CLSID_H261_ENCODE_FILTER; m_pRPHInputMinorType = &MEDIASUBTYPE_RTP_Payload_H261; m_pClsidPHFilter = &CLSID_INTEL_SPHH26X; break; case PAYLOAD_H263: m_pClsidCodecFilter = &CLSID_H263_ENCODE_FILTER; m_pRPHInputMinorType = &MEDIASUBTYPE_RTP_Payload_H263; m_pClsidPHFilter = &CLSID_INTEL_SPHH26X; break; default: LOG((MSP_ERROR, "unknow payload type, %x", StreamSettings.dwPayloadType)); return E_FAIL; } m_Settings = StreamSettings; m_fIsConfigured = TRUE; if (!GetRegValue(L"FrameRate", &m_dwFrameRate)) { m_dwFrameRate = g_dwVideoSampleRate; } return InternalConfigure(); } HRESULT SetVideoFormat( IN IUnknown * pIUnknown, IN BOOL bCIF, IN DWORD dwFramesPerSecond ) /*++ Routine Description: Set the video format to be CIF or QCIF and also set the frames per second. Arguments: pIUnknown - a capture terminal. bCIF - CIF or QCIF. dwFramesPerSecond - Frames per second. Return Value: HRESULT --*/ { LOG((MSP_TRACE, "SetVideoFormat")); HRESULT hr; // first get eht IAMStreamConfig interface. CComPtr pIAMStreamConfig; if (FAILED(hr = pIUnknown->QueryInterface( IID_IAMStreamConfig, (void **)&pIAMStreamConfig ))) { LOG((MSP_ERROR, "Can't get IAMStreamConfig interface.%8x", hr)); return hr; } // get the current format of the video capture terminal. AM_MEDIA_TYPE *pmt; if (FAILED(hr = pIAMStreamConfig->GetFormat(&pmt))) { LOG((MSP_ERROR, "GetFormat returns error: %8x", hr)); return hr; } VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat; if (pVideoInfo == NULL) { MSPDeleteMediaType(pmt); return E_UNEXPECTED; } BITMAPINFOHEADER *pHeader = HEADER(pmt->pbFormat); if (pHeader == NULL) { MSPDeleteMediaType(pmt); return E_UNEXPECTED; } LOG((MSP_INFO, "Video capture: Format BitRate: %d, TimePerFrame: %d", pVideoInfo->dwBitRate, pVideoInfo->AvgTimePerFrame)); LOG((MSP_INFO, "Video capture: Format Compression:%c%c%c%c %dbit %dx%d", (DWORD)pHeader->biCompression & 0xff, ((DWORD)pHeader->biCompression >> 8) & 0xff, ((DWORD)pHeader->biCompression >> 16) & 0xff, ((DWORD)pHeader->biCompression >> 24) & 0xff, pHeader->biBitCount, pHeader->biWidth, pHeader->biHeight)); // The time is in 100ns unit. pVideoInfo->AvgTimePerFrame = (DWORD) 1e7 / dwFramesPerSecond; if (bCIF) { pHeader->biWidth = CIFWIDTH; pHeader->biHeight = CIFHEIGHT; } else { pHeader->biWidth = QCIFWIDTH; pHeader->biHeight = QCIFHEIGHT; } #if defined(ALPHA) // update bmiSize with new Width/Height pHeader->biSizeImage = DIBSIZE( ((VIDEOINFOHEADER *)pmt->pbFormat)->bmiHeader ); #endif if (FAILED(hr = pIAMStreamConfig->SetFormat(pmt))) { LOG((MSP_ERROR, "putMediaFormat returns error: %8x", hr)); } else { LOG((MSP_INFO, "Video capture: Format BitRate: %d, TimePerFrame: %d", pVideoInfo->dwBitRate, pVideoInfo->AvgTimePerFrame)); LOG((MSP_INFO, "Video capture: Format Compression:%c%c%c%c %dbit %dx%d", (DWORD)pHeader->biCompression & 0xff, ((DWORD)pHeader->biCompression >> 8) & 0xff, ((DWORD)pHeader->biCompression >> 16) & 0xff, ((DWORD)pHeader->biCompression >> 24) & 0xff, pHeader->biBitCount, pHeader->biWidth, pHeader->biHeight)); } MSPDeleteMediaType(pmt); return hr; } HRESULT SetVideoBufferSize( IN IUnknown *pIUnknown ) /*++ Routine Description: Set the video capture terminal's buffersize. Arguments: pIUnknown - a capture terminal. Return Value: HRESULT --*/ { // The number of capture buffers is four for now. #define NUMCAPTUREBUFFER 4 LOG((MSP_TRACE, "SetVideoBufferSize")); HRESULT hr; CComPtr pBN; if (FAILED(hr = pIUnknown->QueryInterface( IID_IAMBufferNegotiation, (void **)&pBN ))) { LOG((MSP_ERROR, "Can't get buffer negotiation interface.%8x", hr)); return hr; } ALLOCATOR_PROPERTIES prop; #if 0 // Get allocator property is not working. if (FAILED(hr = pBN->GetAllocatorProperties(&prop))) { LOG((MSP_ERROR, "GetAllocatorProperties returns error: %8x", hr)); return hr; } // Set the number of buffers. if (prop.cBuffers > NUMCAPTUREBUFFER) { prop.cBuffers = NUMCAPTUREBUFFER; } #endif DWORD dwBuffers = NUMCAPTUREBUFFER; GetRegValue(gszNumVideoCaptureBuffers, &dwBuffers); prop.cBuffers = dwBuffers; prop.cbBuffer = -1; prop.cbAlign = -1; prop.cbPrefix = -1; if (FAILED(hr = pBN->SuggestAllocatorProperties(&prop))) { LOG((MSP_ERROR, "SuggestAllocatorProperties returns error: %8x", hr)); } else { LOG((MSP_INFO, "SetVidedobuffersize" " buffers: %d, buffersize: %d, align: %d, Prefix: %d", prop.cBuffers, prop.cbBuffer, prop.cbAlign, prop.cbPrefix )); } return hr; } HRESULT CStreamVideoSend::ConfigureVideoCaptureTerminal( IN ITTerminalControl* pTerminal, IN WCHAR * szPinName, OUT IPin ** ppIPin ) /*++ Routine Description: Given a terminal, find the capture pin and configure it. Arguments: pTerminal - a capture terminal. szPinName - the name of the pin needed. ppIPin - the address to store a pointer to a IPin interface. Return Value: HRESULT --*/ { LOG((MSP_TRACE, "ConfigureVideoCaptureTerminal, pTerminal %x", pTerminal)); // Get the pins from the first terminal because we only use on terminal // on this stream. const DWORD MAXPINS = 8; DWORD dwNumPins = MAXPINS; IPin * Pins[MAXPINS]; HRESULT hr = pTerminal->ConnectTerminal( m_pIGraphBuilder, 0, &dwNumPins, Pins ); if (FAILED(hr)) { LOG((MSP_ERROR, "can't connect to terminal, %x", hr)); return hr; } if (dwNumPins == 0) { LOG((MSP_ERROR, "terminal has no pins.")); return hr; } CComPtr pIPin; /* // look through the pins to find the right one. for (DWORD i = 0; i < dwNumPins; i ++) { LPWSTR szName; hr = Pins[i]->QueryId(&szName); if (FAILED(hr)) { continue; } LOG((MSP_INFO, "Pin name: %ws", szName)); BOOL fEqual = (lstrcmpiW(szName, szPinName) == 0); CoTaskMemFree(szName); if (fEqual) { pIPin = Pins[i]; break; } } */ // we only need the first pin pIPin = Pins[0]; // release the refcount because we don't need them. for (DWORD i = 0; i < dwNumPins; i ++) { Pins[i]->Release(); } if (!pIPin) { LOG((MSP_ERROR, "can't find %ws pin", szPinName)); return E_UNEXPECTED; } // set the video format. 7 Frames/Sec. QCIF. hr = SetVideoFormat( pIPin, m_Settings.fCIF, m_dwFrameRate ); if (FAILED(hr)) { LOG((MSP_ERROR, "can't set video format, %x", hr)); return hr; } // set the video buffer size. hr = SetVideoBufferSize( pIPin ); if (FAILED(hr)) { LOG((MSP_ERROR, "can't set aduio capture buffer size, %x", hr)); return hr; } pIPin->AddRef(); *ppIPin = pIPin; return hr; } HRESULT CStreamVideoSend::FindPreviewInputPin( IN ITTerminalControl* pTerminal, OUT IPin ** ppIPin ) /*++ Routine Description: Find the input pin on a preview terminal. Arguments: pTerminal - a video render terminal. ppIPin - the address to store a pointer to a IPin interface. Return Value: HRESULT --*/ { LOG((MSP_TRACE, "VideoSend.FindPreviewInputPin, pTerminal %x", pTerminal)); // try to disable DDraw because our decoders can't handle DDraw. HRESULT hr2; IDrawVideoImage *pIDrawVideoImage; hr2 = pTerminal->QueryInterface(IID_IDrawVideoImage, (void **)&pIDrawVideoImage); if (SUCCEEDED(hr2)) { hr2 = pIDrawVideoImage->DrawVideoImageBegin(); if (FAILED(hr2)) { LOG((MSP_WARN, "Can't disable DDraw. %x", hr2)); } else { LOG((MSP_INFO, "DDraw disabled.")); } pIDrawVideoImage->Release(); } else { LOG((MSP_WARN, "Can't get IDrawVideoImage. %x", hr2)); } // Get the pins from the first terminal because we only use on terminal // on this stream. const DWORD MAXPINS = 8; DWORD dwNumPins = MAXPINS; IPin * Pins[MAXPINS]; HRESULT hr = pTerminal->ConnectTerminal( m_pIGraphBuilder, 0, &dwNumPins, Pins ); if (FAILED(hr)) { LOG((MSP_ERROR, "can't connect to terminal, %x", hr)); return hr; } if (dwNumPins == 0) { LOG((MSP_ERROR, "terminal has no pins.")); return hr; } // Save the first pin and release the others. CComPtr pIPin = Pins[0]; for (DWORD i = 0; i < dwNumPins; i ++) { Pins[i]->Release(); } pIPin->AddRef(); *ppIPin = pIPin; return hr; } HRESULT CStreamVideoSend::CheckTerminalTypeAndDirection( IN ITTerminal * pTerminal ) /*++ Routine Description: Check if the terminal is allowed on this stream. VideoSend allows both a capture terminal and a preivew terminal. Arguments: pTerminal - the terminal. Return value: HRESULT. S_OK means the terminal is OK. */ { LOG((MSP_TRACE, "VideoSend.CheckTerminalTypeAndDirection")); // This stream only support one capture + one preview terminal if (m_Terminals.GetSize() > 1) { return TAPI_E_MAXTERMINALS; } // check the media type of this terminal. long lMediaType; HRESULT hr = pTerminal->get_MediaType(&lMediaType); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal media type. %x", hr)); return TAPI_E_INVALIDTERMINAL; } if ((DWORD)lMediaType != m_dwMediaType) { return TAPI_E_INVALIDTERMINAL; } // check the direction of this terminal. TERMINAL_DIRECTION Direction; hr = pTerminal->get_Direction(&Direction); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal direction. %x", hr)); return TAPI_E_INVALIDTERMINAL; } if (m_Terminals.GetSize() > 0) { // check the direction of this terminal. TERMINAL_DIRECTION Direction2; hr = m_Terminals[0]->get_Direction(&Direction2); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal direction. %x", hr)); return TAPI_E_INVALIDTERMINAL; } if (Direction == Direction2) { LOG((MSP_ERROR, "can't have two terminals with the same direction. %x", hr)); return TAPI_E_MAXTERMINALS; } } return S_OK; } HRESULT CStreamVideoSend::SetUpFilters() /*++ Routine Description: Insert filters into the graph and connect to the terminals. Arguments: Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoSend.SetUpFilters")); // we only support one capture terminal and one preview // window on this stream. if (m_Terminals.GetSize() > 2) { return E_UNEXPECTED; } int iCaptureIndex = -1, iPreviewIndex = -1; // Find out which terminal is capture and which is preview. HRESULT hr; for (int i = 0; i < m_Terminals.GetSize(); i ++) { TERMINAL_DIRECTION Direction; hr = m_Terminals[i]->get_Direction(&Direction); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal direction. %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, m_Terminals[i]); return hr; } if (Direction == TD_CAPTURE) { iCaptureIndex = i; } else { iPreviewIndex = i; } } // the stream will not work without a capture terminal. if (iCaptureIndex == -1) { LOG((MSP_ERROR, "no capture terminal selected.")); return E_UNEXPECTED; } // Connect the capture filter to the terminal. if (FAILED(hr = ConnectTerminal( m_Terminals[iCaptureIndex] ))) { LOG((MSP_ERROR, "connect the codec filter to terminal. %x", hr)); return hr; } if (iPreviewIndex != -1) { // Connect the preview filter to the terminal. if (FAILED(hr = ConnectTerminal( m_Terminals[iPreviewIndex] ))) { LOG((MSP_ERROR, "connect the codec filter to terminal. %x", hr)); return hr; } } return hr; } HRESULT CStreamVideoSend::ConfigureRTPFilter( IN IBaseFilter * pIBaseFilter ) /*++ Routine Description: Configure the source RTP filter. Including set the address, port, TTL, QOS, thread priority, clockrate, etc. Arguments: pIBaseFilter - The source RTP Filter. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoSend.ConfigureRTPFilter")); HRESULT hr; // Get the IRTPStream interface pointer on the filter. CComQIPtr pIRTPStream(pIBaseFilter); if (pIRTPStream == NULL) { LOG((MSP_ERROR, "get RTP Stream interface")); return E_NOINTERFACE; } LOG((MSP_INFO, "set remote Address:%x, port:%d, TTL:%d", m_Settings.dwIPRemote, m_Settings.wRTPPortRemote, m_Settings.dwTTL)); // Set the remote address and port used in the filter. if (FAILED(hr = pIRTPStream->SetAddress( 0, // local port. htons(m_Settings.wRTPPortRemote), // remote port. htonl(m_Settings.dwIPRemote) ))) { LOG((MSP_ERROR, "set remote Address, hr:%x", hr)); return hr; } // Set the TTL used in the filter. if (FAILED(hr = pIRTPStream->SetMulticastScope(m_Settings.dwTTL))) { LOG((MSP_ERROR, "set TTL. %x", hr)); return hr; } if (m_Settings.dwIPLocal != INADDR_ANY) { // set the local interface that the socket should bind to LOG((MSP_INFO, "set locol Address:%x", m_Settings.dwIPLocal)); if (FAILED(hr = pIRTPStream->SelectLocalIPAddress( htonl(m_Settings.dwIPLocal) ))) { LOG((MSP_ERROR, "set local Address, hr:%x", hr)); return hr; } } // Set the priority of the session if (FAILED(hr = pIRTPStream->SetSessionClassPriority( RTP_CLASS_VIDEO, g_dwVideoThreadPriority ))) { LOG((MSP_ERROR, "set session class and priority. %x", hr)); } // Set the sample rate of the session LOG((MSP_INFO, "setting session sample rate to %d", m_dwFrameRate)); if (FAILED(hr = pIRTPStream->SetDataClock(m_dwFrameRate))) { LOG((MSP_ERROR, "set session sample rate. %x", hr)); } // Enable the RTCP events if (FAILED(hr = ::EnableRTCPEvents(pIBaseFilter))) { LOG((MSP_WARN, "can not enable RTCP events %x", hr)); } if (m_Settings.dwQOSLevel != QSL_BEST_EFFORT) { if (FAILED(hr = ::SetQOSOption( pIBaseFilter, m_Settings.dwPayloadType, // payload -1, // use the default bitrate (m_Settings.dwQOSLevel == QSL_NEEDED), // fail if no qos. FALSE, // this is the send stream. 1, // only one stream m_Settings.fCIF ))) { LOG((MSP_ERROR, "set QOS option. %x", hr)); return hr; } } SetLocalInfoOnRTPFilter(pIBaseFilter); return S_OK; } HRESULT CStreamVideoSend::ConnectTerminal( IN ITTerminal * pITTerminal ) /*++ Routine Description: connect the video terminals to the stream. Arguments: Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "VideoSend.ConnectTerminal %x", pITTerminal)); // Get the TerminalControl interface on the terminal CComQIPtr pTerminal(pITTerminal); if (pTerminal == NULL) { LOG((MSP_ERROR, "can't get Terminal Control interface")); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, E_NOINTERFACE, pITTerminal); return E_NOINTERFACE; } // Find out the direction of the terminal. TERMINAL_DIRECTION Direction; HRESULT hr = pITTerminal->get_Direction(&Direction); if (FAILED(hr)) { LOG((MSP_ERROR, "can't get terminal direction. %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, pITTerminal); return hr; } if (Direction == TD_CAPTURE) { // find the capture pin on the capture terminal and configure it. CComPtr pCapturePin; hr = ConfigureVideoCaptureTerminal(pTerminal, L"Capture", &pCapturePin); if (FAILED(hr)) { LOG((MSP_ERROR, "configure video capture termianl failed. %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, pITTerminal); return hr; } hr = CreateSendFilters(pCapturePin); if (FAILED(hr)) { LOG((MSP_ERROR, "Create video send filters failed. %x", hr)); // disconnect the terminal. pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); // clean up internal filters as well. CleanUpFilters(); return hr; } // // Now we are actually connected. Update our state and perform postconnection // (ignore postconnection error code). // pTerminal->CompleteConnectTerminal(); #if 0 // We don't have the preview pin now, enable this code later when we // the have preview pin. if (FAILED(hr)) { // find the preview pin on the capture terminal and configure it. CComPtr pCapturePin; hr = ConfigureVideoCaptureTerminal(pTerminal, L"Preview", &pCapturePin); if (FAILED(hr)) { LOG((MSP_ERROR, "configure video capture termianl failed. %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, pITTerminal); return hr; } hr = CreateSendFilters(pCapturePin); if (FAILED(hr)) { LOG((MSP_ERROR, "Create video send filters failed. %x", hr)); // disconnect the terminal. pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); // clean up internal filters as well. CleanUpFilters(); return hr; } // // Now we are actually connected. Update our state and perform postconnection // (ignore postconnection error code). // pTerminal->CompleteConnectTerminal(); } #endif } else { // find the input pin on the preview window. If there is no preview window, // we just pass in NULL for the next function. CComPtr pPreviewInputPin; hr = FindPreviewInputPin(pTerminal, &pPreviewInputPin); if (FAILED(hr)) { LOG((MSP_ERROR, "find preview input pin failed. %x", hr)); SendStreamEvent(CALL_TERMINAL_FAIL, CALL_CAUSE_BAD_DEVICE, hr, pITTerminal); return hr; } hr = ConnectPreview(pPreviewInputPin); if (FAILED(hr)) { LOG((MSP_ERROR, "Create video send filters failed. %x", hr)); pTerminal->DisconnectTerminal(m_pIGraphBuilder, 0); return hr; } // // Now we are actually connected. Update our state and perform postconnection // (ignore postconnection error code). // pTerminal->CompleteConnectTerminal(); } return hr; } HRESULT CStreamVideoSend::ConnectPreview( IN IPin *pPreviewInputPin ) /*++ Routine Description: connect the preview pin the the TEE filter. Capturepin->TEE+->Encoder->SPH->RTPRender +->PreviewInputPin Arguments: pPin - The output pin on the capture filter. Return Value: HRESULT. --*/ { HRESULT hr; if (m_pEdgeFilter == NULL) { LOG((MSP_ERROR, "no capture to preview")); return E_UNEXPECTED; } // Create the AVI decompressor filter and add it into the graph. // This will make the graph construction faster for since the AVI // decompressor are always needed for the preview CComPtr pAviFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, CLSID_AVIDec, L"Avi", &pAviFilter))) { LOG((MSP_ERROR, "add Avi filter. %x", hr)); return hr; } // connect the preview input pin with the smart tee filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)m_pEdgeFilter, (IPin *)pPreviewInputPin, FALSE // not direct connect ))) { LOG((MSP_ERROR, "connect preview input pin with the tee. %x", hr)); return hr; } return hr; } HRESULT ConfigureEncoder( IN IBaseFilter * pIFilter, IN BOOL bCIF, IN DWORD dwFramesPerSecond ) /*++ Routine Description: Set the video capture terminal's buffersize. Arguments: pIFilter - a H26x encoder. bCIF - CIF or QCIF. pdwFramesPerSecond - Frames per second. Return Value: HRESULT --*/ { LOG((MSP_TRACE, "ConfigureEncoder")); HRESULT hr; CComPtr pIH26XEncoderControl; if (FAILED(hr = pIFilter->QueryInterface( IID_IH26XEncoderControl, (void **)&pIH26XEncoderControl ))) { LOG((MSP_ERROR, "Can't get pIH26XEncoderControl interface.%8x", hr)); return hr; } // get the current encoder properties of the video capture terminal. ENC_CMP_DATA prop; if (FAILED(hr = pIH26XEncoderControl->get_EncodeCompression(&prop))) { LOG((MSP_ERROR, "get_EncodeCompression returns error: %8x", hr)); return hr; } LOG((MSP_INFO, "Video encoder::get_EncodeCompression" " FrameRate: %d, BitRate: %d, Width %d, bSendKey: %s, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwQuality )); // Set the key frame interval. prop.bSendKey = TRUE; prop.dwFrameRate = dwFramesPerSecond; prop.dwKeyFramePeriod = 5000; // a key frame every five seconds. prop.dwQuality = 7500; #define SETBITRATE #ifdef SETBITRATE DWORD dwBitRate = 0; if (GetRegValue(L"BitRate", &dwBitRate)) { prop.bFrameSizeBRC = FALSE; // control bit rate prop.dwDataRate = dwBitRate; if (dwBitRate < 100) { prop.dwQuality = 0; prop.dwKeyFrameInterval = 100; } } DWORD dwQuality = 0; if (GetRegValue(L"Quality", &dwQuality)) { prop.dwQuality = dwQuality; } #endif if (bCIF) { prop.dwWidth = CIFWIDTH; } else { prop.dwWidth = QCIFWIDTH; } if (FAILED(hr = pIH26XEncoderControl->set_EncodeCompression(&prop))) { LOG((MSP_ERROR, "set_EncodeCompression returns error: %8x", hr)); } else { LOG((MSP_INFO, "Video encoder::set_EncodeCompression" " FrameRate: %d, BitRate: %d, Width %d, bSendKey: %s, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwQuality )); } return hr; } HRESULT CStreamVideoSend::CreateSendFilters( IN IPin *pCapturePin ) /*++ Routine Description: Insert filters into the graph and connect to the capture pin. Capturepin->TEE+->Encoder->SPH->RTPRender Arguments: pPin - The output pin on the capture filter. Return Value: HRESULT. --*/ { HRESULT hr; if (m_pEdgeFilter) { // connect the capture pin with the smart tee filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IPin *)pCapturePin, (IBaseFilter *)m_pEdgeFilter ))) { LOG((MSP_ERROR, "connect capture pin with the tee. %x", hr)); return hr; } return hr; } // Create the tee filter and add it into the graph. CComPtr pTeeFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, CLSID_SmartTee, // CLSID_InfTee, L"tee", &pTeeFilter))) { LOG((MSP_ERROR, "add smart tee filter. %x", hr)); return hr; } // connect the capture pin with the tee filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IPin *)pCapturePin, (IBaseFilter *)pTeeFilter ))) { LOG((MSP_ERROR, "connect capture pin with the tee. %x", hr)); return hr; } // Create the codec filter and add it into the graph. CComPtr pCodecFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, *m_pClsidCodecFilter, L"Encoder", &pCodecFilter))) { LOG((MSP_ERROR, "add Codec filter. %x", hr)); return hr; } // tell the encoder to send key frame if (FAILED(hr = ::ConfigureEncoder( pCodecFilter, m_Settings.fCIF, m_dwFrameRate ))) { LOG((MSP_WARN, "Configure video encoder. %x", hr)); } // connect the smart tee filter and the Codec filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pTeeFilter, (IBaseFilter *)pCodecFilter ))) { LOG((MSP_ERROR, "connect Tee filter and codec filter. %x", hr)); return hr; } // Create the send payload handler and add it into the graph. CComPtr pISPHFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, *m_pClsidPHFilter, L"SPH", &pISPHFilter ))) { LOG((MSP_ERROR, "add SPH filter. %x", hr)); return hr; } // Connect the Codec filter with the SPH filter . if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pCodecFilter, (IBaseFilter *)pISPHFilter ))) { LOG((MSP_ERROR, "connect codec filter and SPH filter. %x", hr)); return hr; } // Get the IRTPSPHFilter interface. CComQIPtr pIRTPSPHFilter(pISPHFilter); if (pIRTPSPHFilter == NULL) { LOG((MSP_ERROR, "get IRTPSPHFilter interface")); return E_NOINTERFACE; } // Create the RTP render filter and add it into the graph. CComPtr pRenderFilter; if (FAILED(hr = ::AddFilter( m_pIGraphBuilder, CLSID_RTPRenderFilter, L"RtpRender", &pRenderFilter))) { LOG((MSP_ERROR, "adding render filter. %x", hr)); return hr; } // Set the address for the render fitler. if (FAILED(hr = ConfigureRTPFilter(pRenderFilter))) { LOG((MSP_ERROR, "configure RTP Filter failed %x", hr)); return hr; } // Connect the SPH filter with the RTP Render filter. if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pISPHFilter, (IBaseFilter *)pRenderFilter ))) { LOG((MSP_ERROR, "connect SPH filter and Render filter. %x", hr)); return hr; } // remember the first filter after the terminal m_pEdgeFilter = pTeeFilter; m_pEdgeFilter->AddRef(); // Get the IRTPParticipant interface pointer on the RTP filter. CComQIPtr pIRTPParticipant(pRenderFilter); if (pIRTPParticipant == NULL) { LOG((MSP_WARN, "can't get RTP participant interface")); } else { m_pRTPFilter = pIRTPParticipant; m_pRTPFilter->AddRef(); } return S_OK; } CSubStreamVideoRecv::CSubStreamVideoRecv() : m_pFTM(NULL), m_pStream(NULL), m_pCurrentParticipant(NULL) { } // methods called by the videorecv object. HRESULT CSubStreamVideoRecv::Init( IN CStreamVideoRecv * pStream ) /*++ Routine Description: Initialize the substream object. Arguments: pStream - The pointer to the stream that owns this substream. Return Value: HRESULT. --*/ { LOG((MSP_TRACE, "CSubStreamVideoRecv::Init, pStream %p", pStream)); // This method is called only once when the object is created. No other // method will be called until this function succeeds. No need to lock. _ASSERTE(m_pStream == NULL); // initialize the terminal array so that the array is not NULL. Used for // generating an empty enumerator if no terminal is selected. if (!m_Terminals.Grow()) { LOG((MSP_ERROR, "CSubStreamVideoRecv::Init - exit E_OUTOFMEMORY")); return E_OUTOFMEMORY; } // create the marshaler. HRESULT hr; hr = CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pFTM); if (FAILED(hr)) { LOG((MSP_ERROR, "create marshaler failed, %x", hr)); return hr; } // save the stream reference. m_pStream = pStream; (pStream->GetControllingUnknown())->AddRef(); LOG((MSP_TRACE, "CSubStreamVideoRecv::Init returns S_OK")); return S_OK; } #ifdef DEBUG_REFCOUNT ULONG CSubStreamVideoRecv::InternalAddRef() { ULONG lRef = CComObjectRootEx::InternalAddRef(); LOG((MSP_TRACE, "SubStreamVideoRecv %p Addref, ref = %d", this, lRef)); return lRef; } ULONG CSubStreamVideoRecv::InternalRelease() { ULONG lRef = CComObjectRootEx::InternalRelease(); LOG((MSP_TRACE, "SubStreamVideoRecv %p Release, ref = %d", this, lRef)); return lRef; } #endif void CSubStreamVideoRecv::FinalRelease() /*++ Routine Description: release everything before being deleted. Arguments: Return Value: --*/ { LOG((MSP_TRACE, "CSubStreamVideoRecv::FinalRelease - enter")); if (m_pCurrentParticipant) { m_pCurrentParticipant->Release(); } for ( int i = 0; i < m_Terminals.GetSize(); i ++ ) { m_Terminals[i]->Release(); } m_Terminals.RemoveAll(); if (m_pStream) { (m_pStream->GetControllingUnknown())->Release(); } if (m_pFTM) { m_pFTM->Release(); } LOG((MSP_TRACE, "CSubStreamVideoRecv::FinalRelease - exit")); } STDMETHODIMP CSubStreamVideoRecv::SelectTerminal( IN ITTerminal * pTerminal ) /*++ Routine Description: Select a terminal on this substream. This method calls the same method on the stream object to handle that. Arguments: pTerminal - the terminal to be selected. Return Value: --*/ { LOG((MSP_TRACE, "CSubStreamVideoRecv::SelectTerminal, pTerminal %p", pTerminal)); HRESULT hr; m_lock.Lock(); if (m_Terminals.GetSize() > 0) { m_lock.Unlock(); return TAPI_E_MAXTERMINALS; } BOOL bFlag = m_Terminals.Add(pTerminal); _ASSERTE(bFlag); m_lock.Unlock(); if (!bFlag) { return E_OUTOFMEMORY; } // This is the refcount for the pointer in m_Terminals. pTerminal->AddRef(); // Call the stream's select terminal to handle the state changes and also // make sure that locking happens only from the stream to substream. hr = m_pStream->SubStreamSelectTerminal(this, pTerminal); if (FAILED(hr)) { LOG((MSP_ERROR, "CSubStreamVideoRecv::SelectTerminal failed, hr:%x", hr)); m_lock.Lock(); m_Terminals.Remove(pTerminal); pTerminal->Release(); m_lock.Unlock(); } return hr; } STDMETHODIMP CSubStreamVideoRecv::UnselectTerminal( IN ITTerminal * pTerminal ) /*++ Routine Description: Unselect a terminal on this substream. This method calls the same method on the stream object to handle that. Arguments: pTerminal - the terminal to be unselected. Return Value: --*/ { LOG((MSP_TRACE, "CSubStreamVideoRecv::UnSelectTerminal, pTerminal %p", pTerminal)); m_lock.Lock(); if (!m_Terminals.Remove(pTerminal)) { m_lock.Unlock(); LOG((MSP_ERROR, "SubStreamVideoRecv::UnselectTerminal, invalid terminal.")); return TAPI_E_INVALIDTERMINAL; } pTerminal->Release(); m_lock.Unlock(); HRESULT hr; // Call the stream's unselect terminal to handle the state changes and also // make sure that locking happens only from the stream to substream. hr = m_pStream->UnselectTerminal(pTerminal); if (FAILED(hr)) { LOG((MSP_ERROR, "CSubStreamVideoRecv::UnSelectTerminal failed, hr:%x", hr)); } return hr; } STDMETHODIMP CSubStreamVideoRecv::EnumerateTerminals( OUT IEnumTerminal ** ppEnumTerminal ) { LOG((MSP_TRACE, "EnumerateTerminals entered. ppEnumTerminal:%x", ppEnumTerminal)); if (IsBadWritePtr(ppEnumTerminal, sizeof(VOID *))) { LOG((MSP_ERROR, "ppEnumTerminal is a bad pointer")); return E_POINTER; } // acquire the lock before accessing the Terminal object list. CLock lock(m_lock); if (m_Terminals.GetData() == NULL) { LOG((MSP_ERROR, "CSubStreamVideoRecv::EnumerateTerminals - " "stream appears to have been shut down - exit E_UNEXPECTED")); return E_UNEXPECTED; } typedef _CopyInterface CCopy; typedef CSafeComEnum CEnumerator; HRESULT hr; CMSPComObject *pEnum = NULL; hr = CMSPComObject::CreateInstance(&pEnum); if (pEnum == NULL) { LOG((MSP_ERROR, "Could not create enumerator object, %x", hr)); return hr; } // query for the IID_IEnumTerminal i/f IEnumTerminal * pEnumTerminal; hr = pEnum->_InternalQueryInterface(IID_IEnumTerminal, (void**)&pEnumTerminal); if (FAILED(hr)) { LOG((MSP_ERROR, "query enum interface failed, %x", hr)); delete pEnum; return hr; } // The CSafeComEnum can handle zero-sized array. hr = pEnum->Init( m_Terminals.GetData(), // the begin itor m_Terminals.GetData() + m_Terminals.GetSize(), // the end itor, NULL, // IUnknown AtlFlagCopy // copy the data. ); if (FAILED(hr)) { LOG((MSP_ERROR, "init enumerator object failed, %x", hr)); pEnumTerminal->Release(); return hr; } LOG((MSP_TRACE, "CSubStreamVideoRecv::EnumerateTerminals - exit S_OK")); *ppEnumTerminal = pEnumTerminal; return hr; } STDMETHODIMP CSubStreamVideoRecv::get_Terminals( OUT VARIANT * pVariant ) { LOG((MSP_TRACE, "CSubStreamVideoRecv::get_Terminals - enter")); // // Check parameters. // if ( IsBadWritePtr(pVariant, sizeof(VARIANT) ) ) { LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "bad pointer argument - exit E_POINTER")); return E_POINTER; } // // See if this stream has been shut down. Acquire the lock before accessing // the terminal object list. // CLock lock(m_lock); if (m_Terminals.GetData() == NULL) { LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "stream appears to have been shut down - exit E_UNEXPECTED")); return E_UNEXPECTED; } // // create the collection object - see mspcoll.h // HRESULT hr; typedef CTapiIfCollection< ITTerminal * > TerminalCollection; CComObject * pCollection; hr = CComObject::CreateInstance( &pCollection ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "can't create collection - exit 0x%08x", hr)); return hr; } // // get the Collection's IDispatch interface // IDispatch * pDispatch; hr = pCollection->_InternalQueryInterface(IID_IDispatch, (void **) &pDispatch ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "QI for IDispatch on collection failed - exit 0x%08x", hr)); delete pCollection; return hr; } // // Init the collection using an iterator -- pointers to the beginning and // the ending element plus one. // hr = pCollection->Initialize( m_Terminals.GetSize(), m_Terminals.GetData(), m_Terminals.GetData() + m_Terminals.GetSize() ); if (FAILED(hr)) { LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "Initialize on collection failed - exit 0x%08x", hr)); pDispatch->Release(); return hr; } // // put the IDispatch interface pointer into the variant // LOG((MSP_ERROR, "CSubStreamVideoRecv::get_Terminals - " "placing IDispatch value %08x in variant", pDispatch)); VariantInit(pVariant); pVariant->vt = VT_DISPATCH; pVariant->pdispVal = pDispatch; LOG((MSP_TRACE, "CSubStreamVideoRecv::get_Terminals - exit S_OK")); return S_OK; } STDMETHODIMP CSubStreamVideoRecv::get_Stream ( OUT ITStream ** ppITStream ) { LOG((MSP_TRACE, "VideoRecvSubStream.get_Stream, ppITStream %x", ppITStream)); if (IsBadWritePtr(ppITStream, sizeof (VOID *))) { LOG((MSP_ERROR, "Bad pointer, ppITStream:%x",ppITStream)); return E_POINTER; } ITStream * pITStream; HRESULT hr = m_pStream->_InternalQueryInterface( IID_ITStream, (void **)&pITStream ); if (FAILED(hr)) { LOG((MSP_ERROR, "get_Stream:QueryInterface failed: %x", hr)); return hr; } *ppITStream = pITStream; return S_OK; } STDMETHODIMP CSubStreamVideoRecv::StartSubStream() { return TAPI_E_NOTSUPPORTED; } STDMETHODIMP CSubStreamVideoRecv::PauseSubStream() { return TAPI_E_NOTSUPPORTED; } STDMETHODIMP CSubStreamVideoRecv::StopSubStream() { return TAPI_E_NOTSUPPORTED; } BOOL CSubStreamVideoRecv::GetCurrentParticipant( DWORD * pdwSSRC, ITParticipant** ppITParticipant ) { CLock lock(m_lock); if (m_pCurrentParticipant) { m_pCurrentParticipant->AddRef(); *ppITParticipant = m_pCurrentParticipant; ((CParticipant *)m_pCurrentParticipant)->GetSSRC( (ITStream*)m_pStream, pdwSSRC ); return TRUE; } return FALSE; } VOID CSubStreamVideoRecv::SetCurrentParticipant( DWORD dwSSRC, ITParticipant * pParticipant ) { CLock lock(m_lock); if (m_pCurrentParticipant) { m_pCurrentParticipant->Release(); } m_pCurrentParticipant = pParticipant; if (m_pCurrentParticipant) { m_pCurrentParticipant->AddRef(); } } BOOL CSubStreamVideoRecv::ClearCurrentTerminal() { CLock lock(m_lock); if (m_Terminals.GetSize() > 0) { m_Terminals[0]->Release(); m_Terminals.RemoveAt(0); return TRUE; } return FALSE; } BOOL CSubStreamVideoRecv::SetCurrentTerminal(ITTerminal * pTerminal) { CLock lock(m_lock); if (m_Terminals.GetSize() > 0) { _ASSERTE(FALSE); return FALSE; } BOOL bFlag = m_Terminals.Add(pTerminal); // This should never fail since the terminal array has been grown // at the init time. _ASSERTE(bFlag); if (bFlag) { pTerminal->AddRef(); return TRUE; } return FALSE; }