mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4284 lines
100 KiB
4284 lines
100 KiB
/*++
|
|
|
|
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 <irtprph.h> // for IRTPRPHFilter
|
|
#include <irtpsph.h> // for IRTPSPHFilter
|
|
#include <amrtpuid.h> // AMRTP media types
|
|
#include <amrtpnet.h> // rtp guids
|
|
#include <ih26xcd.h> // for the h26X encoder filter
|
|
|
|
#include <initguid.h>
|
|
#include <amrtpdmx.h> // demux guid
|
|
|
|
#include <viduids.h> // 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<CSubStreamVideoRecv> * pCOMSubStream;
|
|
|
|
HRESULT hr = CComObject<CSubStreamVideoRecv>::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<ITSubStream> CCopy;
|
|
typedef CSafeComEnum<IEnumSubStream, &IID_IEnumSubStream,
|
|
ITSubStream *, CCopy> CEnumerator;
|
|
|
|
HRESULT hr;
|
|
|
|
CComObject<CEnumerator> *pEnum = NULL;
|
|
|
|
hr = CComObject<CEnumerator>::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<SubStreamCollection> * pCollection;
|
|
HRESULT hr = CComObject<SubStreamCollection>::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<IRTPStream, &IID_IRTPStream> 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<IBaseFilter> 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<IBaseFilter> 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<IRTPParticipant,
|
|
&IID_IRTPParticipant> pIRTPParticipant(pSourceFilter);
|
|
if (pIRTPParticipant == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "can't get RTP participant interface"));
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
CComQIPtr<IRTPDemuxFilter, &IID_IRTPDemuxFilter> 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<IPin> 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<IBaseFilter> 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<IRPHH26XSettings,
|
|
&IID_IRPHH26XSettings> 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<IBaseFilter> 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<ITTerminalControl, &IID_ITTerminalControl>
|
|
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<ITTerminalControl, &IID_ITTerminalControl>
|
|
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<IAMStreamConfig> 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<IAMBufferNegotiation> 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 <IPin> 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 <IPin> 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<IRTPStream, &IID_IRTPStream> 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<ITTerminalControl, &IID_ITTerminalControl>
|
|
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<IPin> 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<IPin> 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<IPin> 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<IBaseFilter> 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<IH26XEncoderControl> 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<IBaseFilter> 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<IBaseFilter> 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<IBaseFilter> 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<IRTPSPHFilter,
|
|
&IID_IRTPSPHFilter> 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<IBaseFilter> 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<IRTPParticipant,
|
|
&IID_IRTPParticipant> 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<CComMultiThreadModelNoCS>::InternalAddRef();
|
|
|
|
LOG((MSP_TRACE, "SubStreamVideoRecv %p Addref, ref = %d", this, lRef));
|
|
|
|
return lRef;
|
|
}
|
|
|
|
ULONG CSubStreamVideoRecv::InternalRelease()
|
|
{
|
|
ULONG lRef = CComObjectRootEx<CComMultiThreadModelNoCS>::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<ITTerminal> CCopy;
|
|
typedef CSafeComEnum<IEnumTerminal, &IID_IEnumTerminal,
|
|
ITTerminal *, CCopy> CEnumerator;
|
|
|
|
HRESULT hr;
|
|
|
|
CMSPComObject<CEnumerator> *pEnum = NULL;
|
|
|
|
hr = CMSPComObject<CEnumerator>::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<TerminalCollection> * pCollection;
|
|
hr = CComObject<TerminalCollection>::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;
|
|
}
|