|
|
/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
H323vid.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 guilds
#include <ih26xcd.h> // for the h26X encoder filter
#include <initguid.h>
#include <amrtpdmx.h> // demux guild
#include <viduids.h> // for video CLSIDs
const DWORD c_SlowLinkSpeed = 40000;
/////////////////////////////////////////////////////////////////////////////
//
// CStreamVideoRecv
//
/////////////////////////////////////////////////////////////////////////////
CStreamVideoRecv::CStreamVideoRecv() : CH323MSPStream(), m_dwCurrentBitRate(0), m_dwProposedBitRate(0) { m_szName = L"VideoRecv"; m_dwLastIFrameRequestedTime = timeGetTime(); m_dwIFramePending = FALSE; }
HRESULT CStreamVideoRecv::Configure( IN HANDLE htChannel, 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_htChannel = htChannel; m_fIsConfigured = TRUE; m_dwCurrentBitRate = m_Settings.Video.dwMaxBitRate; m_dwProposedBitRate = m_dwCurrentBitRate;
InternalConfigure();
return S_OK; }
HRESULT CStreamVideoRecv::ConfigureRTPFilter( IN IBaseFilter * pIBaseFilter ) /*++
Routine Description:
Configure the source RTP filter. Including set the address, port, TTL, QOS, thread priority, clcokrate, 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 locol Address:%x", m_Settings.dwIPLocal));
// Set the local address and port used in the filter.
if (FAILED(hr = pIRTPStream->SelectLocalIPAddress( htonl(m_Settings.dwIPLocal) ))) { LOG((MSP_ERROR, "set locol Address, hr:%x", hr)); return hr; }
LOG((MSP_INFO, "set remote Address:%x, port:%d", m_Settings.dwIPRemote, m_Settings.wRTPPortRemote));
// Set the remote address and port used in the filter.
if (FAILED(hr = pIRTPStream->SetAddress( htons(m_Settings.wRTPPortLocal), // local port.
0, // remote port.
htonl(m_Settings.dwIPRemote) // remote address.
))) { LOG((MSP_ERROR, "set remote Address, hr:%x", hr)); return hr; }
// Get the IRTCPStream interface pointer.
CComQIPtr<IRTCPStream, &IID_IRTCPStream> pIRTCPStream(pIBaseFilter); if (pIRTCPStream == NULL) { LOG((MSP_ERROR, "get RTCP Stream interface")); return E_NOINTERFACE; }
LOG((MSP_INFO, "set remote RTCP Address:%x, port:%d, local port: %d", m_Settings.dwIPRemote, m_Settings.wRTCPPortRemote, m_Settings.wRTCPPortLocal));
// Set the remote RTCP address and port.
if (FAILED(hr = pIRTCPStream->SetRTCPAddress( htons(m_Settings.wRTCPPortLocal), htons(m_Settings.wRTCPPortRemote), htonl(m_Settings.dwIPRemote) ))) { LOG((MSP_ERROR, "set remote RTCP Address, hr:%x", hr)); return hr; } // Set the TTL used in the filter.
if (FAILED(hr = pIRTPStream->SetMulticastScope(DEFAULT_TTL))) { LOG((MSP_ERROR, "set TTL. %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_dwVideoSampleRateHigh)); if (FAILED(hr = pIRTPStream->SetDataClock(g_dwVideoSampleRateHigh))) { 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 (FAILED(hr = ::SetQOSOption( pIBaseFilter, m_Settings.dwPayloadType, // payload
m_Settings.Video.dwMaxBitRate, TRUE ))) { LOG((MSP_ERROR, "set QOS option. %x", hr)); return hr; }
return S_OK; }
HRESULT CStreamVideoRecv::SetUpInternalFilters() /*++
Routine Description:
set up the filters used in the stream.
RTP->Demux->RPH->DECODER->Render terminal
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; }
// 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 H323iguring 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.Video.bCIF))) { LOG((MSP_WARN, "can't set CIF or QCIF")); } // Get the IRTPRPHFilter interface.
CComQIPtr<IRTPRPHFilter, &IID_IRTPRPHFilter>pIRTPRPHFilter(pIRPHFilter); if (pIRTPRPHFilter == NULL) { LOG((MSP_ERROR, "get IRTPRPHFilter interface")); return hr; }
if (FAILED(hr = pIRTPRPHFilter->OverridePayloadType( (BYTE)m_Settings.dwPayloadType ))) { LOG((LOG_ERROR, "override payload type. %x", hr)); return FALSE; }
// Connect the payload handler to the output pin on the source filter.
if (FAILED(hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)pSourceFilter, (IBaseFilter *)pIRPHFilter ))) { LOG((MSP_ERROR, "connect demux and RPH filter. %x", hr)); 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)); return hr; }
m_pEdgeFilter = pCodecFilter; m_pEdgeFilter->AddRef();
return hr; }
HRESULT CStreamVideoRecv::ConnectTerminal( IN ITTerminal * pITTerminal ) /*++
Routine Description:
connect the codec to the 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; } }
// 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 the decoders can't handle DDraw now.
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];
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_BAD_DEVICE, 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);
return E_UNEXPECTED; }
// Connect the codec filter to the video render terminal.
hr = ::ConnectFilters( m_pIGraphBuilder, (IBaseFilter *)m_pEdgeFilter, (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::SetUpFilters() /*++
Routine Description:
Insert filters into the graph and connect to the terminals.
Arguments: Return Value:
HRESULT.
--*/ { LOG((MSP_TRACE, "VideoRecv.SetUpFilters"));
// we only support one terminal for this stream.
if (m_Terminals.GetSize() != 1) { return E_UNEXPECTED; }
HRESULT hr;
// Connect the terminal.
if (FAILED(hr = ConnectTerminal( m_Terminals[0] ))) { LOG((MSP_ERROR, "connect the terminal. %x", hr));
return hr; }
return hr; }
HRESULT CStreamVideoRecv::HandlePacketReceiveLoss( IN DWORD dwLossRate ) { LOG((MSP_TRACE, "%ls HandlePacketReceiveLoss, lossRate:%d", m_szName, dwLossRate));
CLock lock(m_lock);
if (m_pMSPCall == NULL) { LOG((MSP_WARN, "The call has shut down the stream.")); return S_OK; }
DWORD dwCurrentTime = timeGetTime();
if (dwLossRate == 0) { if (m_dwIFramePending) { ((CH323MSPCall*)m_pMSPCall)->SendTSPMessage( H323MSP_VIDEO_FAST_UPDATE_PICTURE_COMMAND, (ITStream *)this, m_htChannel );
m_dwLastIFrameRequestedTime = dwCurrentTime; m_dwIFramePending = 0; }
if (m_dwCurrentBitRate >= m_Settings.Video.dwMaxBitRate) { return S_OK; }
// Adjust the proposed bitrate and
m_dwProposedBitRate += BITRATEINC; if (m_dwProposedBitRate > m_Settings.Video.dwMaxBitRate) { m_dwProposedBitRate = m_Settings.Video.dwMaxBitRate; }
if ((m_dwProposedBitRate - m_dwCurrentBitRate >= BITRATEDELTA) || (m_dwProposedBitRate == m_Settings.Video.dwMaxBitRate)) { m_dwCurrentBitRate = m_dwProposedBitRate;
// Tell the TSP to send a new flow control command.
((CH323MSPCall*)m_pMSPCall)->SendTSPMessage( H323MSP_FLOW_CONTROL_COMMAND, (ITStream *)this, m_htChannel, (m_dwMediaType == TAPIMEDIATYPE_AUDIO) ? MEDIA_AUDIO : MEDIA_VIDEO, 0, m_dwCurrentBitRate ); LOG((MSP_INFO, "%ls New bitrate:%d", m_szName, m_dwCurrentBitRate)); }
return S_OK; }
_ASSERTE(dwLossRate < 100);
m_dwProposedBitRate = (DWORD)(m_dwCurrentBitRate / 100.0 * (100 - dwLossRate));
if (m_dwProposedBitRate < BITRATELOWERLIMIT) { // we don't want the bitRate to go too low.
m_dwProposedBitRate = BITRATELOWERLIMIT;
// TODO, if this happens too many times, close the channel.
}
if (m_dwCurrentBitRate - m_dwProposedBitRate >= BITRATEDELTA) { m_dwCurrentBitRate = m_dwProposedBitRate;
// Tell the TSP to send a new flow control command.
((CH323MSPCall*)m_pMSPCall)->SendTSPMessage( H323MSP_FLOW_CONTROL_COMMAND, (ITStream *)this, m_htChannel, (m_dwMediaType == TAPIMEDIATYPE_AUDIO) ? MEDIA_AUDIO : MEDIA_VIDEO, 0, m_dwCurrentBitRate ); LOG((MSP_INFO, "%ls New bitrate:%d", m_szName, m_dwCurrentBitRate)); }
if (dwCurrentTime - m_dwLastIFrameRequestedTime > IFRAMEINTERVAL) { ((CH323MSPCall*)m_pMSPCall)->SendTSPMessage( H323MSP_VIDEO_FAST_UPDATE_PICTURE_COMMAND, (ITStream *)this, m_htChannel );
m_dwLastIFrameRequestedTime = dwCurrentTime; m_dwIFramePending = 0; } else { // Remember that we need to send an I Frame request when time arrives.
m_dwIFramePending = 1; }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
//
// CStreamVideoSend
//
/////////////////////////////////////////////////////////////////////////////
CStreamVideoSend::CStreamVideoSend() : CH323MSPStream(), m_pIEncoderFilter(NULL), m_dwCurrentBitRate(0), m_dwProposedBitRate(0) { m_szName = L"VideoSend"; m_dwIFramePending = FALSE; m_dwLastIFrameSentTime = timeGetTime(); }
HRESULT CStreamVideoSend::ShutDown() /*++
Routine Description:
Shut down the stream. Release our members and then calls the base class's ShutDown method.
Arguments:
Return Value:
S_OK --*/ { CLock lock(m_lock);
if (m_pIEncoderFilter) { m_pIEncoderFilter->Release(); m_pIEncoderFilter = NULL; }
return CH323MSPStream::ShutDown(); }
HRESULT CStreamVideoSend::Configure( IN HANDLE htChannel, 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_htChannel = htChannel; m_fIsConfigured = TRUE;
m_dwCurrentBitRate = m_Settings.Video.dwStartUpBitRate; m_dwProposedBitRate = m_dwCurrentBitRate;
InternalConfigure();
return S_OK; }
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) { DeleteMediaType(pmt); return E_UNEXPECTED; }
BITMAPINFOHEADER *pHeader = HEADER(pmt->pbFormat); if (pHeader == NULL) { DeleteMediaType(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)); }
DeleteMediaType(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
prop.cBuffers = NUMCAPTUREBUFFER; 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, OUT IPin ** ppIPin ) /*++
Routine Description:
Given a terminal, find the capture pin and configure it.
Arguments: pTerminal - a capture terminal.
ppIPin - the address to store a pointer to a IPin interface.
Return Value:
HRESULT
--*/ { LOG((MSP_TRACE, "ConfigureVideoCaptureTerminal, pTerminal %x", pTerminal));
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(); }
// set the video format.
hr = SetVideoFormat( pIPin, m_Settings.Video.bCIF, #ifdef TWOFRAMERATES
(m_dwCurrentBitRate > c_SlowLinkSpeed) ? g_dwVideoSampleRateHigh : g_dwVideoSampleRateLow #else
g_dwVideoSampleRateHigh #endif
);
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));
// 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, clcokrate, 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 locol Address:%x", m_Settings.dwIPLocal));
// Set the local address and port used in the filter.
if (FAILED(hr = pIRTPStream->SelectLocalIPAddress( htonl(m_Settings.dwIPLocal) ))) { LOG((MSP_ERROR, "set locol Address, hr:%x", hr)); return hr; }
LOG((MSP_INFO, "set remote Address:%x, port:%d, TTL:%d", m_Settings.dwIPRemote, m_Settings.wRTPPortRemote, DEFAULT_TTL));
// Set the 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) // remote IP.
))) { LOG((MSP_ERROR, "set remote Address, hr:%x", hr)); return hr; }
// Get the IRTCPStream interface pointer.
CComQIPtr<IRTCPStream, &IID_IRTCPStream> pIRTCPStream(pIBaseFilter); if (pIRTCPStream == NULL) { LOG((MSP_ERROR, "get RTCP Stream interface")); return E_NOINTERFACE; }
LOG((MSP_INFO, "set remote RTCP Address:%x, port:%d, local port:%d", m_Settings.dwIPRemote, m_Settings.wRTCPPortRemote, m_Settings.wRTCPPortLocal));
// Set the remote RTCP address and port.
if (FAILED(hr = pIRTCPStream->SetRTCPAddress( htons(m_Settings.wRTCPPortLocal), htons(m_Settings.wRTCPPortRemote), htonl(m_Settings.dwIPRemote) ))) { LOG((MSP_ERROR, "set remote RTCP Address, hr:%x", hr)); return hr; } // Set the TTL used in the filter.
if (FAILED(hr = pIRTPStream->SetMulticastScope(DEFAULT_TTL))) { LOG((MSP_ERROR, "set TTL. %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
#ifdef TWOFRAMERATES
LOG((MSP_INFO, "setting session sample rate to %d", (m_dwCurrentBitRate > c_SlowLinkSpeed) ? g_dwVideoSampleRateHigh : g_dwVideoSampleRateLow )); #else
LOG((MSP_INFO, "setting session sample rate to %d", g_dwVideoSampleRateHigh )); #endif
#ifdef TWOFRAMERATES
if (FAILED(hr = pIRTPStream->SetDataClock( (m_dwCurrentBitRate > c_SlowLinkSpeed) ? g_dwVideoSampleRateHigh : g_dwVideoSampleRateLow ))) #else
if (FAILED(hr = pIRTPStream->SetDataClock( g_dwVideoSampleRateHigh ))) #endif
{ 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 (FAILED(hr = ::SetQOSOption( pIBaseFilter, m_Settings.dwPayloadType, // payload
m_Settings.Video.dwMaxBitRate, FALSE ))) { LOG((MSP_ERROR, "set QOS option. %x", hr)); return hr; }
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, &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();
} 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 EncoderDoCommand( IN IBaseFilter * pIFilter, IN ENCODERCOMMAND command, IN DWORD dwParam1 ) /*++
Routine Description:
Set the video capture terminal's buffersize.
Arguments: pIFilter - a H26x encoder.
command - the command needs to be performed on the encoder.
dwparam1 - the parameter.
Return Value:
HRESULT
--*/ { LOG((MSP_TRACE, "EncoderDoCommand, command:%d, param1: %d", command, dwParam1));
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, DataRate: %d, Width %d, bSendKey: %s, interval: %d, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwKeyFrameInterval, prop.dwQuality ));
switch (command) { case EC_BITRATE: prop.bFrameSizeBRC = FALSE; // control bit rate
prop.dwDataRate = dwParam1 / 1000; // in kbps
break;
case EC_IFRAME: prop.bSendKey = TRUE; break; } 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, DataRate: %d, Width %d, bSendKey: %s, interval: %d, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwKeyFrameInterval, prop.dwQuality ));
} return hr; }
HRESULT ConfigureEncoder( IN IBaseFilter * pIFilter, IN BOOL bCIF, IN DWORD dwMaxBitRate, 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.
dwKeyFrameInterval - The number of frames before sending a key frame.
Return Value:
HRESULT
--*/ { LOG((MSP_TRACE, "ConfigureEncoder, dwMaxBitRate :%d", dwMaxBitRate));
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, DataRate: %d, Width %d, bSendKey: %s, interval: %d, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwKeyFrameInterval, prop.dwQuality ));
prop.bSendKey = TRUE; prop.dwFrameRate = dwFramesPerSecond; prop.bFrameSizeBRC = FALSE; // control bit rate
prop.dwQuality = 8500; prop.dwDataRate = dwMaxBitRate / 1000; // in kbps
prop.dwKeyFrameInterval = 9999999; // don't send Keyframes.
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, DataRate: %d, Width %d, bSendKey: %s, interval: %d, Quality: %d", prop.dwFrameRate, prop.dwDataRate, prop.dwWidth, prop.bSendKey ? "TRUE" : "FALSE", prop.dwKeyFrameInterval, prop.dwQuality ));
} 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 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; }
// configure the encoder
#ifdef TWOFRAMERATES
if (FAILED(hr = ::ConfigureEncoder( pCodecFilter, m_Settings.Video.bCIF, m_dwCurrentBitRate, (m_dwCurrentBitRate > c_SlowLinkSpeed) ? g_dwVideoSampleRateHigh : g_dwVideoSampleRateLow ))) #else
if (FAILED(hr = ::ConfigureEncoder( pCodecFilter, m_Settings.Video.bCIF, m_dwCurrentBitRate, g_dwVideoSampleRateHigh ))) #endif
{ 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();
m_pIEncoderFilter = pCodecFilter; m_pIEncoderFilter->AddRef();
return S_OK; }
HRESULT CStreamVideoSend::SendIFrame() /*++
Routine Description:
Now we got a I Frame request from the TSP. Use the encoder filter to generate a I frame.
Arguments:
Return Value:
HRESULT.
--*/ { CLock lock(m_lock);
if (!m_pIEncoderFilter) { return E_UNEXPECTED; } DWORD dwCurrentTime = timeGetTime(); if (dwCurrentTime - m_dwLastIFrameSentTime > IFRAMEINTERVAL) { // this function always succeeds.
EncoderDoCommand(m_pIEncoderFilter, EC_IFRAME, 0);
m_dwLastIFrameSentTime = dwCurrentTime; m_dwIFramePending = 0; } else { m_dwIFramePending = 1; } return S_OK; }
HRESULT CStreamVideoSend::ChangeMaxBitRate( IN DWORD dwMaxBitRate ) /*++
Routine Description:
The receiver set the max bit-rate to a new value.
Arguments: dwMaxBitRate - the new max bit rate requested by the other endpoint.
Return Value:
S_OK;
--*/ { LOG((MSP_INFO, "new Max bitrate: %d", dwMaxBitRate));
CLock lock(m_lock);
m_Settings.Video.dwMaxBitRate = dwMaxBitRate;
if (m_dwCurrentBitRate > m_Settings.Video.dwMaxBitRate) { m_dwCurrentBitRate = m_Settings.Video.dwMaxBitRate;
if (m_pIEncoderFilter) { // this function always succeeds.
EncoderDoCommand( m_pIEncoderFilter, EC_BITRATE, m_dwCurrentBitRate ); } } return S_OK; }
HRESULT CStreamVideoSend::HandlePacketTransmitLoss( IN DWORD dwLossRate ) /*++
Routine Description:
.
Arguments: dwMaxBitRate - the new max bit rate requested by the other endpoint.
Return Value:
S_OK;
--*/ { LOG((MSP_TRACE, "%ls HandlePacketTransmitLoss, lossRate:%d", m_szName, dwLossRate));
CLock lock(m_lock);
if (m_pMSPCall == NULL) { LOG((MSP_WARN, "The call has shut down the stream.")); return S_OK; }
DWORD dwCurrentTime = timeGetTime();
if (dwLossRate == 0) { if (m_dwIFramePending) { // this function always succeeds.
EncoderDoCommand(m_pIEncoderFilter, EC_IFRAME, 0); m_dwLastIFrameSentTime = dwCurrentTime; m_dwIFramePending = 0; }
if (m_dwCurrentBitRate >= m_Settings.Video.dwMaxBitRate) { return S_OK; }
// Adjust the proposed bitrate and
m_dwProposedBitRate += BITRATEINC; if (m_dwProposedBitRate > m_Settings.Video.dwMaxBitRate) { m_dwProposedBitRate = m_Settings.Video.dwMaxBitRate; }
if ((m_dwProposedBitRate - m_dwCurrentBitRate >= BITRATEDELTA) || (m_dwProposedBitRate == m_Settings.Video.dwMaxBitRate)) { m_dwCurrentBitRate = m_dwProposedBitRate;
// this function always succeeds.
EncoderDoCommand( m_pIEncoderFilter, EC_BITRATE, m_dwCurrentBitRate ); }
return S_OK; }
_ASSERTE(dwLossRate < 100);
m_dwProposedBitRate = (DWORD)(m_dwCurrentBitRate / 100.0 * (100 - dwLossRate));
if (m_dwProposedBitRate < BITRATELOWERLIMIT) { // we don't want the bitRate to go too low.
m_dwProposedBitRate = BITRATELOWERLIMIT;
// TODO, if this happens too many times, close the channel.
}
if (m_dwCurrentBitRate - m_dwProposedBitRate >= BITRATEDELTA) { m_dwCurrentBitRate = m_dwProposedBitRate;
// this function always succeeds.
EncoderDoCommand( m_pIEncoderFilter, EC_BITRATE, m_dwCurrentBitRate ); }
if (dwCurrentTime - m_dwLastIFrameSentTime > IFRAMEINTERVAL) { // this function always succeeds.
EncoderDoCommand( m_pIEncoderFilter, EC_IFRAME, 0 ); m_dwLastIFrameSentTime = dwCurrentTime; m_dwIFramePending = 0; } else { // Remember that we need to send an I Frame when time arrives.
m_dwIFramePending = 1; }
return S_OK; }
|