3357 lines
95 KiB
C++
3357 lines
95 KiB
C++
|
#include "precomp.h"
|
||
|
|
||
|
#ifndef SIZEOF_VIDEOFORMATEX
|
||
|
#define SIZEOF_VIDEOFORMATEX(pwfx) (sizeof(VIDEOFORMATEX))
|
||
|
#endif
|
||
|
|
||
|
// #define LOGSTATISTICS_ON 1
|
||
|
|
||
|
// Used to translate between frame sizes and the FRAME_* bit flags
|
||
|
#define NON_STANDARD 0x80000000
|
||
|
#define SIZE_TO_FLAG(s) (s == Small ? FRAME_SQCIF : s == Medium ? FRAME_QCIF: s == Large ? FRAME_CIF : NON_STANDARD)
|
||
|
|
||
|
|
||
|
const int VID_AVG_PACKET_SIZE = 450; // avg from NetMon stats
|
||
|
|
||
|
|
||
|
// maps temporal spatial tradeoff to a target frame rate
|
||
|
|
||
|
// assume the MAX frame rate for QCIF and SQCIF is 10 on modem
|
||
|
// let the "best quality" be 2 frames/sec
|
||
|
int g_TSTable_Modem_QCIF[] =
|
||
|
{
|
||
|
200, 225, 250, 275, // best quality
|
||
|
300, 325, 350, 375,
|
||
|
400, 425, 450, 475,
|
||
|
500, 525, 550, 575,
|
||
|
600, 625, 650, 675,
|
||
|
700, 725, 750, 775,
|
||
|
800, 825, 850, 875,
|
||
|
900, 925, 950, 1000 // fast frames
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
// max frame rate for CIF be 2.5 frames/sec on modem
|
||
|
// best quality will be .6 frame/sec
|
||
|
int g_TSTable_Modem_CIF[] =
|
||
|
{
|
||
|
60, 66, 72, 78,
|
||
|
84, 90, 96, 102,
|
||
|
108, 114, 120, 126,
|
||
|
132, 140, 146, 152,
|
||
|
158, 164, 170, 174,
|
||
|
180, 186, 192, 198,
|
||
|
208, 216, 222, 228,
|
||
|
232, 238, 244, 250
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#ifdef USE_NON_LINEAR_FPS_ADJUSTMENT
|
||
|
// this table and related code anc be used for non-linear adjustment of our frame rate based
|
||
|
// on QOS information in QosNotifyVideoCB
|
||
|
int g_QoSMagic[19][19] =
|
||
|
{
|
||
|
{-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90,-90},
|
||
|
{-90,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80,-80},
|
||
|
{-90,-80,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70,-70},
|
||
|
{-90,-80,-70,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60,-60},
|
||
|
{-90,-80,-70,-60,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50,-50},
|
||
|
{-90,-80,-70,-60,-50,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-20,-20,-20,-20,-20,-20,-20,-20,-20,-20,-20},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10,-10,-10,-10,-10,-10,-10,-10,-10,-10,-10},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 20, 20, 20, 20, 20, 20, 20},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 30, 30, 30, 30, 30, 30},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 40, 40, 40, 40, 40},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 50, 50, 50, 50, 50},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 50, 60, 60, 60, 60},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 50, 60, 70, 70, 70},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 80},
|
||
|
{-90,-80,-70,-60,-50,-40,-30,-20,-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90},
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
BOOL SortOrder(IAppVidCap *pavc, BASIC_VIDCAP_INFO* pvidcaps, DWORD dwcFormats,
|
||
|
DWORD dwFlags, WORD wDesiredSortOrder, int nNumFormats);
|
||
|
|
||
|
|
||
|
UINT ChoosePacketSize(VIDEOFORMATEX *pvf)
|
||
|
{
|
||
|
// set default samples per pkt to 1
|
||
|
UINT spp, sblk;
|
||
|
spp = 1;
|
||
|
// calculate samples per block ( aka frame)
|
||
|
sblk = pvf->nBlockAlign* pvf->nSamplesPerSec/ pvf->nAvgBytesPerSec;
|
||
|
if (sblk <= spp) {
|
||
|
spp = (spp/sblk)*sblk;
|
||
|
} else
|
||
|
spp = sblk;
|
||
|
return spp;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT STDMETHODCALLTYPE SendVideoStream::QueryInterface(REFIID iid, void **ppVoid)
|
||
|
{
|
||
|
// resolve duplicate inheritance to the SendMediaStream;
|
||
|
|
||
|
extern IID IID_IProperty;
|
||
|
|
||
|
if (iid == IID_IUnknown)
|
||
|
{
|
||
|
*ppVoid = (IUnknown*)((RecvMediaStream*)this);
|
||
|
}
|
||
|
else if (iid == IID_IMediaChannel)
|
||
|
{
|
||
|
*ppVoid = (IMediaChannel*)((RecvMediaStream *)this);
|
||
|
}
|
||
|
else if (iid == IID_IVideoChannel)
|
||
|
{
|
||
|
*ppVoid = (IVideoChannel*)this;
|
||
|
}
|
||
|
else if (iid == IID_IProperty)
|
||
|
{
|
||
|
*ppVoid = NULL;
|
||
|
ERROR_OUT(("Don't QueryInterface for IID_IProperty, use IMediaChannel"));
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
else if (iid == IID_IVideoRender)// satisfy symmetric property of QI
|
||
|
{
|
||
|
*ppVoid = (IVideoRender *)this;
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
*ppVoid = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
AddRef();
|
||
|
|
||
|
return S_OK;
|
||
|
|
||
|
}
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE SendVideoStream::AddRef(void)
|
||
|
{
|
||
|
return InterlockedIncrement(&m_lRefCount);
|
||
|
}
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE SendVideoStream::Release(void)
|
||
|
{
|
||
|
LONG lRet;
|
||
|
|
||
|
lRet = InterlockedDecrement(&m_lRefCount);
|
||
|
|
||
|
if (lRet == 0)
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
else
|
||
|
return lRet;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
DWORD CALLBACK SendVideoStream::StartCaptureThread(LPVOID pVoid)
|
||
|
{
|
||
|
SendVideoStream *pThisStream = (SendVideoStream*)pVoid;
|
||
|
return pThisStream->CapturingThread();
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
SendVideoStream::Initialize(DataPump *pDP)
|
||
|
{
|
||
|
HRESULT hr = DPR_OUT_OF_MEMORY;
|
||
|
DWORD dwFlags = DP_FLAG_FULL_DUPLEX | DP_FLAG_AUTO_SWITCH ;
|
||
|
MEDIACTRLINIT mcInit;
|
||
|
FX_ENTRY ("DP::InitChannel")
|
||
|
FINDCAPTUREDEVICE fcd;
|
||
|
|
||
|
m_pIUnknown = (IUnknown *)NULL;
|
||
|
|
||
|
InitializeCriticalSection(&m_crsVidQoS);
|
||
|
InitializeCriticalSection(&m_crs);
|
||
|
dwFlags |= DP_FLAG_VCM | DP_FLAG_VIDEO ;
|
||
|
|
||
|
m_maxfps = 2997; // max of 29.97 fps
|
||
|
m_frametime = 1000 / 30; // default of 30 fps (time in ms) QOS will slow us, if
|
||
|
// need be
|
||
|
|
||
|
// Modem connections will use a frame rate control table
|
||
|
// to implement TS-Tradeoff
|
||
|
m_pTSTable = NULL;
|
||
|
m_dwCurrentTSSetting = VCM_DEFAULT_IMAGE_QUALITY;
|
||
|
|
||
|
// store the platform flags
|
||
|
// enable Send and Recv by default
|
||
|
m_DPFlags = (dwFlags & DP_MASK_PLATFORM) | DPFLAG_ENABLE_SEND ;
|
||
|
// store a back pointer to the datapump container
|
||
|
m_pDP = pDP;
|
||
|
m_pRTPSend = NULL;
|
||
|
|
||
|
// m_PrevFormatId = INVALID_MEDIA_FORMAT;
|
||
|
ZeroMemory(&m_fCodecOutput, sizeof(VIDEOFORMATEX));
|
||
|
|
||
|
// Initialize data (should be in constructor)
|
||
|
m_CaptureDevice = (UINT) -1; // use VIDEO_MAPPER
|
||
|
m_PreviousCaptureDevice = (UINT) -1;
|
||
|
|
||
|
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_SendStream = new TxStream();
|
||
|
if (!m_SendStream)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: TxStream new failed\r\n", _fx_));
|
||
|
goto StreamAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Create Input and Output video filters
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_pVideoFilter = new VcmFilter();
|
||
|
m_dwDstSize = 0;
|
||
|
if (m_pVideoFilter==NULL)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: VcmFilter new failed\r\n", _fx_));
|
||
|
goto FilterAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
//Create Video MultiMedia device control objects
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_InMedia = new VideoInControl();
|
||
|
if (!m_InMedia )
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: MediaControl new failed\r\n", _fx_));
|
||
|
goto MediaAllocError;
|
||
|
}
|
||
|
|
||
|
// Initialize the send-stream media control object
|
||
|
mcInit.dwFlags = dwFlags | DP_FLAG_SEND;
|
||
|
hr = m_InMedia->Initialize(&mcInit);
|
||
|
if (hr != DPR_SUCCESS)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: IMedia->Init failed, hr=0x%lX\r\n", _fx_, hr));
|
||
|
goto MediaAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
// determine if the video devices are available
|
||
|
fcd.dwSize = sizeof (FINDCAPTUREDEVICE);
|
||
|
if (FindFirstCaptureDevice(&fcd, NULL)) {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: OMedia->have capture cap\r\n", _fx_));
|
||
|
m_DPFlags |= DP_FLAG_RECORD_CAP ;
|
||
|
}
|
||
|
|
||
|
// set media to half duplex mode by default
|
||
|
m_InMedia->SetProp(MC_PROP_DUPLEX_TYPE, DP_FLAG_HALF_DUPLEX);
|
||
|
|
||
|
m_SavedTickCount = timeGetTime(); //so we start with low timestamps
|
||
|
m_DPFlags |= DPFLAG_INITIALIZED;
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
|
||
|
|
||
|
MediaAllocError:
|
||
|
if (m_InMedia) delete m_InMedia;
|
||
|
FilterAllocError:
|
||
|
if (m_pVideoFilter) delete m_pVideoFilter;
|
||
|
StreamAllocError:
|
||
|
if (m_SendStream) delete m_SendStream;
|
||
|
|
||
|
ERRORMESSAGE( ("SendVideoStream::Initialize: exit, hr=0x%lX\r\n", hr));
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
// LOOK: identical to SendAudioStream version.
|
||
|
SendVideoStream::~SendVideoStream()
|
||
|
{
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_INITIALIZED) {
|
||
|
m_DPFlags &= ~DPFLAG_INITIALIZED;
|
||
|
|
||
|
// TEMP:Make sure preview stops
|
||
|
m_DPFlags &= ~DPFLAG_ENABLE_PREVIEW;
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_CONFIGURED_SEND )
|
||
|
{
|
||
|
UnConfigure();
|
||
|
}
|
||
|
|
||
|
if (m_pRTPSend)
|
||
|
{
|
||
|
m_pRTPSend->Release();
|
||
|
m_pRTPSend = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Close the receive and transmit streams
|
||
|
if (m_SendStream) delete m_SendStream;
|
||
|
|
||
|
// Close the wave devices
|
||
|
if (m_InMedia) { delete m_InMedia;}
|
||
|
// Close the filters
|
||
|
if (m_pVideoFilter)
|
||
|
{
|
||
|
delete m_pVideoFilter;
|
||
|
}
|
||
|
m_pDP->RemoveMediaChannel(MCF_SEND| MCF_VIDEO, (IMediaChannel*)(RecvMediaStream *)this);
|
||
|
|
||
|
}
|
||
|
DeleteCriticalSection(&m_crs);
|
||
|
DeleteCriticalSection(&m_crsVidQoS);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT STDMETHODCALLTYPE SendVideoStream::Configure(
|
||
|
BYTE *pFormat,
|
||
|
UINT cbFormat,
|
||
|
BYTE *pChannelParams,
|
||
|
UINT cbParams,
|
||
|
IUnknown *pUnknown)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
BOOL fRet;
|
||
|
MEDIAPACKETINIT pcktInit;
|
||
|
MEDIACTRLCONFIG mcConfig;
|
||
|
MediaPacket **ppPckt;
|
||
|
ULONG cPckt, uIndex;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
VIDEOFORMATEX *pfSend = (VIDEOFORMATEX*)pFormat;
|
||
|
DWORD maxBitRate=0;
|
||
|
DWORD i, dwSrcSize, dwMaxFragSize=0;
|
||
|
int iXOffset, iYOffset;
|
||
|
VIDEO_CHANNEL_PARAMETERS vidChannelParams;
|
||
|
struct {
|
||
|
int cResources;
|
||
|
RESOURCE aResources[1];
|
||
|
} m_aLocalRs;
|
||
|
vidChannelParams.RTP_Payload = 0;
|
||
|
int optval = 0 ;
|
||
|
CCaptureChain *pChain;
|
||
|
HCAPDEV hCapDev=NULL;
|
||
|
LPBITMAPINFOHEADER lpcap, lpsend;
|
||
|
BOOL fNewDeviceSettings = TRUE;
|
||
|
BOOL fNewDevice = TRUE;
|
||
|
BOOL fLive = FALSE, fReconfiguring;
|
||
|
MMRESULT mmr;
|
||
|
DWORD dwStreamingMode = STREAMING_PREFER_FRAME_GRAB;
|
||
|
|
||
|
|
||
|
FX_ENTRY ("SendVideoStream::Configure")
|
||
|
|
||
|
if (pfSend)
|
||
|
{
|
||
|
// for now, don't allow SendVideoStream to be re-configured
|
||
|
// if we are already streaming.
|
||
|
if (m_DPFlags & DPFLAG_STARTED_SEND)
|
||
|
{
|
||
|
return DPR_IO_PENDING;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(!pChannelParams);
|
||
|
}
|
||
|
|
||
|
if(NULL != pChannelParams)
|
||
|
{
|
||
|
// get channel parameters
|
||
|
if (cbParams != sizeof(vidChannelParams))
|
||
|
{
|
||
|
hr = DPR_INVALID_PARAMETER;
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
|
||
|
vidChannelParams = *(VIDEO_CHANNEL_PARAMETERS *)pChannelParams;
|
||
|
fLive = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// else this is configuring for preview or is unconfiguring. There are
|
||
|
// no channel parameters
|
||
|
//
|
||
|
}
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_CONFIGURED_SEND)
|
||
|
{
|
||
|
if (pfSend)
|
||
|
{
|
||
|
if (m_CaptureDevice == m_PreviousCaptureDevice)
|
||
|
fNewDevice = FALSE;
|
||
|
if (IsSimilarVidFormat(&m_fCodecOutput, pfSend))
|
||
|
fNewDeviceSettings = FALSE;
|
||
|
}
|
||
|
|
||
|
// When using a different capture device, we systematically configure everyting
|
||
|
// although it would probably be possible to optimize the configuration
|
||
|
// of the filters and transmit stream
|
||
|
EndSend();
|
||
|
UnConfigureSendVideo(fNewDeviceSettings, fNewDevice);
|
||
|
}
|
||
|
|
||
|
if (!pfSend)
|
||
|
{
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
if (fLive)
|
||
|
m_DPFlags |= DPFLAG_REAL_THING;
|
||
|
|
||
|
// m_Net = pNet;
|
||
|
|
||
|
if (! (m_DPFlags & DPFLAG_INITIALIZED))
|
||
|
return DPR_OUT_OF_MEMORY; //BUGBUG: return proper error;
|
||
|
|
||
|
if (fNewDeviceSettings || fNewDevice)
|
||
|
{
|
||
|
m_ThreadFlags |= DPTFLAG_PAUSE_CAPTURE;
|
||
|
|
||
|
mcConfig.uDuration = MC_USING_DEFAULT; // set duration by samples per pkt
|
||
|
// force an unknown device to be profiled by fetching
|
||
|
// it's streaming capabilites BEFORE opening it
|
||
|
mmr = vcmGetDevCapsStreamingMode(m_CaptureDevice, &dwStreamingMode);
|
||
|
if (mmr != MMSYSERR_NOERROR)
|
||
|
{
|
||
|
dwStreamingMode = STREAMING_PREFER_FRAME_GRAB;
|
||
|
}
|
||
|
|
||
|
|
||
|
m_InMedia->GetProp (MC_PROP_MEDIA_DEV_HANDLE, &dwPropVal);
|
||
|
|
||
|
if (!dwPropVal) {
|
||
|
// if capture device isn't already open, then open it
|
||
|
m_InMedia->SetProp(MC_PROP_MEDIA_DEV_ID, (DWORD)m_CaptureDevice);
|
||
|
if (fNewDevice)
|
||
|
{
|
||
|
hr = m_InMedia->Open();
|
||
|
if (hr != DPR_SUCCESS) {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: m_InMedia->Open failed to open capture, hr=0x%lX\r\n", _fx_, hr));
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
}
|
||
|
m_InMedia->GetProp (MC_PROP_MEDIA_DEV_HANDLE, &dwPropVal);
|
||
|
if (!dwPropVal) {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: capture device not open (0x%lX)\r\n", _fx_));
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
}
|
||
|
hCapDev = (HCAPDEV)dwPropVal;
|
||
|
|
||
|
if (m_pCaptureChain) {
|
||
|
delete m_pCaptureChain;
|
||
|
m_pCaptureChain = NULL;
|
||
|
}
|
||
|
|
||
|
i = 0; // assume no colortable
|
||
|
|
||
|
// m_fDevSend is the uncompressed format
|
||
|
// pfSend is the compressed format
|
||
|
mmr = VcmFilter::SuggestEncodeFormat(m_CaptureDevice, &m_fDevSend, pfSend);
|
||
|
|
||
|
if (mmr == MMSYSERR_NOERROR) {
|
||
|
i = m_fDevSend.bih.biClrUsed; // non-zero, if vcmstrm gave us a colortable
|
||
|
SetCaptureDeviceFormat(hCapDev, &m_fDevSend.bih, 0, 0);
|
||
|
}
|
||
|
|
||
|
dwPropVal = GetCaptureDeviceFormatHeaderSize(hCapDev);
|
||
|
while (1) {
|
||
|
if (lpcap = (LPBITMAPINFOHEADER)MemAlloc((UINT)dwPropVal)) {
|
||
|
lpcap->biSize = (DWORD)dwPropVal;
|
||
|
if (!GetCaptureDeviceFormat(hCapDev, lpcap)) {
|
||
|
MemFree(lpcap);
|
||
|
DEBUGMSG (ZONE_DP, ("%s: failed to set/get capture format\r\n", _fx_));
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
UPDATE_REPORT_ENTRY(g_prptSystemSettings, (lpcap->biWidth << 22) | (lpcap->biHeight << 12) | ((lpcap->biCompression == VIDEO_FORMAT_UYVY) ? VIDEO_FORMAT_NUM_COLORS_UYVY : (lpcap->biCompression == VIDEO_FORMAT_YUY2) ? VIDEO_FORMAT_NUM_COLORS_YUY2 : (lpcap->biCompression == VIDEO_FORMAT_IYUV) ? VIDEO_FORMAT_NUM_COLORS_IYUV : (lpcap->biCompression == VIDEO_FORMAT_I420) ? VIDEO_FORMAT_NUM_COLORS_I420 : (lpcap->biCompression == VIDEO_FORMAT_YVU9) ? VIDEO_FORMAT_NUM_COLORS_YVU9 : (lpcap->biCompression == 0) ? ((lpcap->biBitCount == 24) ? VIDEO_FORMAT_NUM_COLORS_16777216 : (lpcap->biBitCount == 16) ? VIDEO_FORMAT_NUM_COLORS_65536 : (lpcap->biBitCount == 8) ? VIDEO_FORMAT_NUM_COLORS_256 : (lpcap->biBitCount == 4) ? VIDEO_FORMAT_NUM_COLORS_16 : 0x00000800) : 0x00000800), REP_DEVICE_IMAGE_SIZE);
|
||
|
if (lpcap->biBitCount > 8)
|
||
|
break;
|
||
|
else if (dwPropVal > 256 * sizeof(RGBQUAD)) {
|
||
|
if (i) {
|
||
|
// vcmstrm gave us a colortable in m_fDevSend, so use it
|
||
|
CopyMemory(((BYTE*)lpcap) + lpcap->biSize, (BYTE*)&m_fDevSend.bih + m_fDevSend.bih.biSize,
|
||
|
256 * sizeof(RGBQUAD));
|
||
|
}
|
||
|
else {
|
||
|
CAPTUREPALETTE pal;
|
||
|
LPRGBQUAD lprgb;
|
||
|
|
||
|
GetCaptureDevicePalette(hCapDev, &pal);
|
||
|
lprgb = (LPRGBQUAD)(((BYTE*)lpcap) + lpcap->biSize);
|
||
|
for (i = 0; i < 256; i++) {
|
||
|
lprgb->rgbRed = pal.pe[i].peRed;
|
||
|
lprgb->rgbGreen = pal.pe[i].peGreen;
|
||
|
lprgb->rgbBlue = pal.pe[i].peBlue;
|
||
|
lprgb++;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
dwPropVal += 256 * sizeof(RGBQUAD);
|
||
|
MemFree(lpcap); // free this lpcap, and alloc a new with room for palette
|
||
|
}
|
||
|
else {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: failed to set/get capture format\r\n", _fx_));
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
if (pChain = new CCaptureChain) {
|
||
|
VIDEOFORMATEX *capfmt;
|
||
|
|
||
|
// if pfSend is 128x96, but capture is greater, then InitCaptureChain with a larger size so
|
||
|
// that the codec will just crop to 128x96
|
||
|
iXOffset = pfSend->bih.biWidth;
|
||
|
iYOffset = pfSend->bih.biHeight;
|
||
|
if ((iXOffset == 128) && (iYOffset == 96)) {
|
||
|
if (lpcap->biWidth == 160) {
|
||
|
iXOffset = lpcap->biWidth;
|
||
|
iYOffset = lpcap->biHeight;
|
||
|
}
|
||
|
else if (lpcap->biWidth == 320) {
|
||
|
iXOffset = lpcap->biWidth / 2;
|
||
|
iYOffset = lpcap->biHeight / 2;
|
||
|
}
|
||
|
}
|
||
|
if ((hr = pChain->InitCaptureChain(hCapDev,
|
||
|
(dwStreamingMode==STREAMING_PREFER_STREAMING),
|
||
|
lpcap, iXOffset, iYOffset, 0, &lpsend)) != NO_ERROR) {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: failed to init capture chain\r\n", _fx_));
|
||
|
MemFree(lpcap);
|
||
|
delete pChain;
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
DEBUGMSG (ZONE_DP, ("%s: failed allocate capture chain\r\n", _fx_));
|
||
|
MemFree((HANDLE)lpcap);
|
||
|
hr = DPR_OUT_OF_MEMORY;
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
MemFree((HANDLE)lpcap);
|
||
|
|
||
|
m_pCaptureChain = pChain;
|
||
|
|
||
|
// build m_fDevSend format as format that will be input to codec
|
||
|
CopyMemory(&m_fDevSend, pfSend, sizeof(VIDEOFORMATEX)-sizeof(BITMAPINFOHEADER)-BMIH_SLOP_BYTES);
|
||
|
|
||
|
// m_fDevSend.bih is the output format of the CaptureChain
|
||
|
CopyMemory(&m_fDevSend.bih, lpsend, lpsend->biSize);
|
||
|
//LOOKLOOK RP - need to get colortable too?
|
||
|
|
||
|
m_fDevSend.dwFormatSize = sizeof(VIDEOFORMATEX);
|
||
|
m_fDevSend.dwFormatTag = lpsend->biCompression;
|
||
|
m_fDevSend.nAvgBytesPerSec = m_fDevSend.nMinBytesPerSec =
|
||
|
m_fDevSend.nMaxBytesPerSec = m_fDevSend.nSamplesPerSec * lpsend->biSizeImage;
|
||
|
m_fDevSend.nBlockAlign = lpsend->biSizeImage;
|
||
|
m_fDevSend.wBitsPerSample = lpsend->biBitCount;
|
||
|
LocalFree((HANDLE)lpsend);
|
||
|
|
||
|
mcConfig.pDevFmt = &m_fDevSend;
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, pfSend->dwFormatTag, REP_SEND_VIDEO_FORMAT);
|
||
|
RETAILMSG(("NAC: Video Send Format: %.4s", (LPSTR)&pfSend->dwFormatTag));
|
||
|
|
||
|
// Initialize the send-stream media control object
|
||
|
mcConfig.hStrm = (DPHANDLE) m_SendStream;
|
||
|
m_InMedia->GetProp(MC_PROP_MEDIA_DEV_ID, &dwPropVal);
|
||
|
mcConfig.uDevId = (DWORD)dwPropVal;
|
||
|
|
||
|
mcConfig.cbSamplesPerPkt = ChoosePacketSize(pfSend);
|
||
|
|
||
|
hr = m_InMedia->Configure(&mcConfig);
|
||
|
if (hr != DPR_SUCCESS)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: IVMedia->Config failed, hr=0x%lX\r\n", _fx_, hr));
|
||
|
goto IMediaInitError;
|
||
|
}
|
||
|
|
||
|
// initialize m_cliprect
|
||
|
iXOffset = 0; iYOffset = 0;
|
||
|
if (m_fDevSend.bih.biWidth > pfSend->bih.biWidth)
|
||
|
iXOffset = (m_fDevSend.bih.biWidth - pfSend->bih.biWidth) >> 1;
|
||
|
if (m_fDevSend.bih.biHeight > pfSend->bih.biHeight)
|
||
|
iYOffset = (m_fDevSend.bih.biHeight - pfSend->bih.biHeight) >> 1;
|
||
|
SetRect(&m_cliprect, iXOffset, iYOffset, pfSend->bih.biWidth + iXOffset, pfSend->bih.biHeight + iYOffset);
|
||
|
|
||
|
dwMaxFragSize = 512; // default video packet size
|
||
|
CopyMemory (&m_fCodecOutput, pfSend, sizeof(VIDEOFORMATEX));
|
||
|
m_InMedia->GetProp (MC_PROP_SIZE, &dwPropVal);
|
||
|
dwSrcSize = (DWORD)dwPropVal;
|
||
|
|
||
|
mmr = m_pVideoFilter->Open(&m_fDevSend, &m_fCodecOutput, dwMaxFragSize);
|
||
|
|
||
|
if (mmr != MMSYSERR_NOERROR)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: VcmFilter->Open failed, mmr=%d\r\n", _fx_, mmr));
|
||
|
hr = DPR_CANT_OPEN_CODEC;
|
||
|
goto SendFilterInitError;
|
||
|
}
|
||
|
|
||
|
// Initialize the send queue
|
||
|
ZeroMemory (&pcktInit, sizeof (pcktInit));
|
||
|
|
||
|
pcktInit.dwFlags = DP_FLAG_SEND | DP_FLAG_VCM | DP_FLAG_VIDEO;
|
||
|
pcktInit.pStrmConvSrcFmt = &m_fDevSend;
|
||
|
pcktInit.pStrmConvDstFmt = &m_fCodecOutput;
|
||
|
pcktInit.cbSizeRawData = dwSrcSize;
|
||
|
pcktInit.cbOffsetRawData = 0;
|
||
|
|
||
|
|
||
|
m_InMedia->FillMediaPacketInit (&pcktInit);
|
||
|
m_InMedia->GetProp (MC_PROP_SIZE, &dwPropVal);
|
||
|
|
||
|
|
||
|
m_pVideoFilter->SuggestDstSize(dwSrcSize, &m_dwDstSize);
|
||
|
pcktInit.cbSizeNetData = m_dwDstSize;
|
||
|
|
||
|
m_pVideoFilter->GetProperty(FM_PROP_PAYLOAD_HEADER_SIZE,
|
||
|
&pcktInit.cbPayloadHeaderSize);
|
||
|
|
||
|
|
||
|
pcktInit.cbOffsetNetData = sizeof (RTP_HDR);
|
||
|
pcktInit.payload = vidChannelParams.RTP_Payload;
|
||
|
|
||
|
|
||
|
|
||
|
fRet = m_SendStream->Initialize (DP_FLAG_VIDEO, MAX_TXVRING_SIZE, m_pDP, &pcktInit);
|
||
|
if (!fRet)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: TxvStream->Init failed, fRet=0%u\r\n", _fx_, fRet));
|
||
|
hr = DPR_CANT_INIT_TXV_STREAM;
|
||
|
goto TxStreamInitError;
|
||
|
}
|
||
|
|
||
|
// Prepare headers for TxvStream
|
||
|
m_SendStream->GetRing (&ppPckt, &cPckt);
|
||
|
m_InMedia->RegisterData (ppPckt, cPckt);
|
||
|
m_InMedia->PrepareHeaders ();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The following fields may change with the capabilities of the other end point
|
||
|
dwMaxFragSize = 512; // default video packet size
|
||
|
if (pChannelParams)
|
||
|
{
|
||
|
m_pVideoFilter->GetProperty(FM_PROP_PAYLOAD_HEADER_SIZE,
|
||
|
&pcktInit.cbPayloadHeaderSize);
|
||
|
|
||
|
pcktInit.cbOffsetNetData = sizeof (RTP_HDR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pChannelParams)
|
||
|
{
|
||
|
// Update the bitrate
|
||
|
maxBitRate = vidChannelParams.ns_params.maxBitRate*100;
|
||
|
if (maxBitRate < BW_144KBS_BITS)
|
||
|
maxBitRate = BW_144KBS_BITS;
|
||
|
|
||
|
// set the max. fragment size
|
||
|
DEBUGMSG(ZONE_DP,("%s: Video Send: maxBitRate=%d, maxBPP=%d, MPI=%d\r\n",
|
||
|
_fx_,maxBitRate,
|
||
|
vidChannelParams.ns_params.maxBPP*1024, vidChannelParams.ns_params.MPI*33));
|
||
|
|
||
|
// Initialize the max frame rate with the negociated max
|
||
|
if ((vidChannelParams.ns_params.MPI > 0UL) && (vidChannelParams.ns_params.MPI < 33UL))
|
||
|
{
|
||
|
dwPropVal = 2997UL / vidChannelParams.ns_params.MPI;
|
||
|
m_maxfps = (DWORD)dwPropVal;
|
||
|
INIT_COUNTER_MAX(g_pctrVideoSend, (m_maxfps + 50) / 100);
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, (m_maxfps + 50) / 100, REP_SEND_VIDEO_MAXFPS);
|
||
|
RETAILMSG(("NAC: Video Send Max Frame Rate (negotiated - fps): %ld", (m_maxfps + 50) / 100));
|
||
|
DEBUGMSG(1,("%s: Video Send: Negociated max fps = %d.%d\r\n", _fx_, m_maxfps/100, m_maxfps - m_maxfps / 100 * 100));
|
||
|
}
|
||
|
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, maxBitRate, REP_SEND_VIDEO_BITRATE);
|
||
|
RETAILMSG(("NAC: Video Send Max Bitrate (negotiated - bps): %ld", maxBitRate));
|
||
|
INIT_COUNTER_MAX(g_pctrVideoSendBytes, maxBitRate * 75 / 100);
|
||
|
|
||
|
// At this point we actually know what is the minimum bitrate chosen
|
||
|
// by the sender and the receiver. Let's reset the resources reserved
|
||
|
// by the QoS with those more meaningfull values.
|
||
|
if (m_pDP->m_pIQoS)
|
||
|
{
|
||
|
// Fill in the resource list
|
||
|
m_aLocalRs.cResources = 1;
|
||
|
m_aLocalRs.aResources[0].resourceID = RESOURCE_OUTGOING_BANDWIDTH;
|
||
|
|
||
|
// Do a sanity check on the minimal bit rate
|
||
|
m_aLocalRs.aResources[0].nUnits = maxBitRate;
|
||
|
m_aLocalRs.aResources[0].ulResourceFlags = m_aLocalRs.aResources[0].reserved = 0;
|
||
|
|
||
|
DEBUGMSG(1,("%s: Video Send: Negociated max bps = %d\r\n", _fx_, maxBitRate));
|
||
|
|
||
|
// Set the resources on the QoS object
|
||
|
hr = m_pDP->m_pIQoS->SetResources((LPRESOURCELIST)&m_aLocalRs);
|
||
|
}
|
||
|
|
||
|
// if we're sending on the LAN, fragment video frames into Ethernet packet sized chunks
|
||
|
// On slower links use smaller packets for better bandwidth sharing
|
||
|
// NOTE: codec packetizer can occasionally exceed the fragment size limit
|
||
|
if (maxBitRate > BW_ISDN_BITS)
|
||
|
dwMaxFragSize = 1350;
|
||
|
|
||
|
m_pVideoFilter->SetProperty(FM_PROP_VIDEO_MAX_PACKET_SIZE, dwMaxFragSize);
|
||
|
|
||
|
// To correctly initialize the flow spec structure we need to get the values that
|
||
|
// our QoS module will be effectively using. Typically, we only use 70% of the max
|
||
|
// advertized. On top of that, some system administrator may have significantly
|
||
|
// reduced the maximum bitrate on this machine.
|
||
|
if (m_pDP->m_pIQoS)
|
||
|
{
|
||
|
LPRESOURCELIST pResourceList = NULL;
|
||
|
|
||
|
// Get a list of all resources from QoS
|
||
|
hr = m_pDP->m_pIQoS->GetResources(&pResourceList);
|
||
|
if (SUCCEEDED(hr) && pResourceList)
|
||
|
{
|
||
|
// Find the BW resource
|
||
|
for (i=0; i < pResourceList->cResources; i++)
|
||
|
{
|
||
|
if (pResourceList->aResources[i].resourceID == RESOURCE_OUTGOING_BANDWIDTH)
|
||
|
{
|
||
|
maxBitRate = min(maxBitRate, (DWORD)pResourceList->aResources[i].nUnits);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Release memory
|
||
|
m_pDP->m_pIQoS->FreeBuffer(pResourceList);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// WS2Qos will be called in Start to communicate stream information to the
|
||
|
// remote endpoint using a PATH message
|
||
|
//
|
||
|
// We use a peak-rate allocation approach based on our target bitrates
|
||
|
// Note that for the token bucket size and the maximum SDU size, we now
|
||
|
// account for IP header overhead, and use the max frame fragment size
|
||
|
// instead of the maximum compressed image size returned by the codec
|
||
|
|
||
|
ASSERT(maxBitRate > 0);
|
||
|
|
||
|
InitVideoFlowspec(&m_flowspec, maxBitRate, dwMaxFragSize, VID_AVG_PACKET_SIZE);
|
||
|
|
||
|
// Update RTCP send address and payload type. It should be known now
|
||
|
// We have to explicitly set the payload again because the preview
|
||
|
// channel configuration has already set it to zero.
|
||
|
m_RTPPayload = vidChannelParams.RTP_Payload;
|
||
|
m_SendStream->GetRing (&ppPckt, &cPckt);
|
||
|
for (uIndex = 0; uIndex < cPckt; uIndex++)
|
||
|
{
|
||
|
ppPckt[uIndex]->SetPayload(m_RTPPayload);
|
||
|
}
|
||
|
|
||
|
// Keep a weak reference to the IUnknown interface
|
||
|
// We will use it to query a Stream Signal interface pointer in Start()
|
||
|
m_pIUnknown = pUnknown;
|
||
|
}
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_REAL_THING)
|
||
|
{
|
||
|
if (m_pDP->m_pIQoS)
|
||
|
{
|
||
|
// Initialize our requests. One for CPU usage, one for bandwidth usage.
|
||
|
m_aRRq.cResourceRequests = 2;
|
||
|
m_aRRq.aResourceRequest[0].resourceID = RESOURCE_OUTGOING_BANDWIDTH;
|
||
|
m_aRRq.aResourceRequest[0].nUnitsMin = 0;
|
||
|
m_aRRq.aResourceRequest[1].resourceID = RESOURCE_CPU_CYCLES;
|
||
|
m_aRRq.aResourceRequest[1].nUnitsMin = 0;
|
||
|
|
||
|
// Initialize QoS structure
|
||
|
ZeroMemory(&m_Stats, sizeof(m_Stats));
|
||
|
|
||
|
// Start collecting CPU performance data from the registry
|
||
|
StartCPUUsageCollection();
|
||
|
|
||
|
// Register with the QoS module. This call should NEVER fail. If it does, we'll do without the QoS
|
||
|
m_pDP->m_pIQoS->RequestResources((GUID *)&MEDIA_TYPE_H323VIDEO, (LPRESOURCEREQUESTLIST)&m_aRRq, QosNotifyVideoCB, (DWORD_PTR)this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// reset the temporal spatial tradeoff to best quality
|
||
|
// it's expected that the UI will re-specify the TS setting
|
||
|
// sometime after the stream is started
|
||
|
m_pVideoFilter->SetProperty(FM_PROP_VIDEO_RESET_IMAGE_QUALITY ,VCM_RESET_IMAGE_QUALITY);
|
||
|
m_pTSTable = NULL;
|
||
|
m_dwCurrentTSSetting = VCM_MAX_IMAGE_QUALITY;
|
||
|
|
||
|
|
||
|
|
||
|
//Before we start, reset the frame frame rate to the channel max.
|
||
|
//If the previous call had been slower than possible, resume
|
||
|
//previewing at the desired FPS.
|
||
|
if (pChannelParams && (m_DPFlags & DPFLAG_REAL_THING))
|
||
|
{
|
||
|
int iSlowStartFrameRate;
|
||
|
|
||
|
// us a frame-rate table for temporal spatial tradeoff settings
|
||
|
// if the bandwidth is a modem setting
|
||
|
if (maxBitRate <= BW_288KBS_BITS)
|
||
|
{
|
||
|
if (pfSend->bih.biWidth >= CIF_WIDTH)
|
||
|
{
|
||
|
m_pTSTable = g_TSTable_Modem_CIF;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pTSTable = g_TSTable_Modem_QCIF;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Let's do a slow start and then catch up with the negociated max
|
||
|
|
||
|
if (m_pTSTable == NULL)
|
||
|
{
|
||
|
iSlowStartFrameRate = m_maxfps >> 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
iSlowStartFrameRate = m_pTSTable[VCM_MAX_IMAGE_QUALITY];
|
||
|
}
|
||
|
|
||
|
|
||
|
SetProperty(PROP_VIDEO_FRAME_RATE, &iSlowStartFrameRate, sizeof(int));
|
||
|
|
||
|
// Initialize the codec with the new target bitrates and frame rates
|
||
|
// PhilF-: This assumes that we start with a silent audio channel...
|
||
|
SetTargetRates(iSlowStartFrameRate, maxBitRate);
|
||
|
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
INIT_COUNTER_MAX(g_pctrVideoSend, 30);
|
||
|
SetProperty(PROP_VIDEO_FRAME_RATE, &m_maxfps, sizeof(int));
|
||
|
}
|
||
|
|
||
|
m_ThreadFlags &= ~DPTFLAG_PAUSE_CAPTURE;
|
||
|
m_DPFlags |= DPFLAG_CONFIGURED_SEND;
|
||
|
m_PreviousCaptureDevice = m_CaptureDevice;
|
||
|
// m_PrevFormatId = SendVidFmt;
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
|
||
|
|
||
|
TxStreamInitError:
|
||
|
m_pVideoFilter->Close();
|
||
|
SendFilterInitError:
|
||
|
IMediaInitError:
|
||
|
if (m_pCaptureChain) {
|
||
|
delete m_pCaptureChain;
|
||
|
m_pCaptureChain = NULL;
|
||
|
}
|
||
|
// We need to close the video controller object on failure to open the capture device,
|
||
|
// otherwise we get a pure virtual function call on NM shutdown!
|
||
|
if (m_InMedia)
|
||
|
m_InMedia->Close();
|
||
|
ERRORMESSAGE(("%s: failed, hr=0%u\r\n", _fx_, hr));
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
void SendVideoStream::UnConfigure()
|
||
|
{
|
||
|
// By default, unconfigure all resources
|
||
|
UnConfigureSendVideo(TRUE, TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void SendVideoStream::UnConfigureSendVideo(BOOL fNewDeviceSettings, BOOL fNewDevice)
|
||
|
{
|
||
|
|
||
|
#ifdef TEST
|
||
|
DWORD dwTicks;
|
||
|
|
||
|
dwTicks = GetTickCount();
|
||
|
#endif
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_CONFIGURED_SEND)
|
||
|
{
|
||
|
if (m_hCapturingThread)
|
||
|
Stop();
|
||
|
|
||
|
if (fNewDeviceSettings || fNewDevice)
|
||
|
{
|
||
|
// m_PrevFormatId = INVALID_MEDIA_FORMAT;
|
||
|
ZeroMemory(&m_fCodecOutput, sizeof(VIDEOFORMATEX));
|
||
|
|
||
|
m_Net = NULL;
|
||
|
|
||
|
if (m_pCaptureChain)
|
||
|
{
|
||
|
delete m_pCaptureChain;
|
||
|
m_pCaptureChain = NULL;
|
||
|
}
|
||
|
|
||
|
// Close the devices
|
||
|
m_InMedia->Reset();
|
||
|
m_InMedia->UnprepareHeaders();
|
||
|
if (fNewDevice)
|
||
|
{
|
||
|
m_PreviousCaptureDevice = -1L; // VIDEO_MAPPER
|
||
|
m_InMedia->Close();
|
||
|
}
|
||
|
|
||
|
// Close the filters
|
||
|
m_pVideoFilter->Close();
|
||
|
|
||
|
// Close the transmit streams
|
||
|
m_SendStream->Destroy();
|
||
|
}
|
||
|
|
||
|
m_DPFlags &= ~DPFLAG_CONFIGURED_SEND;
|
||
|
|
||
|
// Release the QoS Resources
|
||
|
// If the associated RequestResources had failed, the ReleaseResources can be
|
||
|
// still called... it will just come back without having freed anything.
|
||
|
if (m_pDP->m_pIQoS)
|
||
|
{
|
||
|
if (m_DPFlags & DPFLAG_REAL_THING)
|
||
|
{
|
||
|
m_pDP->m_pIQoS->ReleaseResources((GUID *)&MEDIA_TYPE_H323VIDEO, (LPRESOURCEREQUESTLIST)&m_aRRq);
|
||
|
|
||
|
// Terminate CPU usage data collection
|
||
|
StopCPUUsageCollection();
|
||
|
|
||
|
}
|
||
|
m_DPFlags &= ~DPFLAG_REAL_THING;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef TEST
|
||
|
LOG((LOGMSG_TIME_SEND_VIDEO_UNCONFIGURE,GetTickCount() - dwTicks));
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
SendVideoStream::Start()
|
||
|
{
|
||
|
int nRet= IFRAMES_CAPS_UNKNOWN;
|
||
|
|
||
|
FX_ENTRY ("SendVideoStream::Start")
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_STARTED_SEND)
|
||
|
return DPR_SUCCESS;
|
||
|
if (!(m_DPFlags & DPFLAG_CONFIGURED_SEND))
|
||
|
return DPR_NOT_CONFIGURED;
|
||
|
|
||
|
// to fix: if we optimize SetNetworkInterface to allow
|
||
|
// us to transition from preview->sending without having
|
||
|
// to call stop/start, we need to make sure the flowspec/QOS
|
||
|
// stuff get's called there.
|
||
|
|
||
|
SetFlowSpec();
|
||
|
|
||
|
ASSERT(!m_hCapturingThread);
|
||
|
m_ThreadFlags &= ~(DPTFLAG_STOP_RECORD|DPTFLAG_STOP_SEND);
|
||
|
// Start recording thread
|
||
|
if (!(m_ThreadFlags & DPTFLAG_STOP_RECORD))
|
||
|
m_hCapturingThread = CreateThread(NULL,0, SendVideoStream::StartCaptureThread,this,0,&m_CaptureThId);
|
||
|
|
||
|
// ------------------------------------------------------------------------
|
||
|
// Decide whether or not we need to send periodic I-Frames during this call
|
||
|
|
||
|
// Who are we talking to?
|
||
|
if ((m_pIUnknown) && (m_DPFlags & DPFLAG_REAL_THING))
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
IStreamSignal *pIStreamSignal=NULL;
|
||
|
|
||
|
hr = m_pIUnknown->QueryInterface(IID_IStreamSignal, (void **)&pIStreamSignal);
|
||
|
if (HR_SUCCEEDED(hr))
|
||
|
{
|
||
|
nRet = GetIFrameCaps(pIStreamSignal);
|
||
|
pIStreamSignal->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// only disable sending of I Frames if and only if we know the remote party
|
||
|
// can handle it. In this case, NetMeeting 3.0 or TAPI 3.1
|
||
|
if (nRet == IFRAMES_CAPS_NM3)
|
||
|
{
|
||
|
m_pVideoFilter->SetProperty(FM_PROP_PERIODIC_IFRAMES, FALSE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pVideoFilter->SetProperty(FM_PROP_PERIODIC_IFRAMES, TRUE);
|
||
|
}
|
||
|
// ------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
m_DPFlags |= DPFLAG_STARTED_SEND;
|
||
|
|
||
|
DEBUGMSG (ZONE_DP, ("%s: Record threadid=%x,\r\n", _fx_, m_CaptureThId));
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// LOOK: identical to SendAudioStream version.
|
||
|
HRESULT
|
||
|
SendVideoStream::Stop()
|
||
|
{
|
||
|
DWORD dwWait;
|
||
|
|
||
|
|
||
|
if(!(m_DPFlags & DPFLAG_STARTED_SEND))
|
||
|
{
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
m_ThreadFlags = m_ThreadFlags | DPTFLAG_STOP_SEND | DPTFLAG_STOP_RECORD;
|
||
|
|
||
|
if(m_SendStream) {
|
||
|
m_SendStream->Stop();
|
||
|
m_SendStream->Reset();
|
||
|
}
|
||
|
/*
|
||
|
* we want to wait for all the threads to exit, but we need to handle windows
|
||
|
* messages (mostly from winsock) while waiting.
|
||
|
*/
|
||
|
|
||
|
if(m_hCapturingThread) {
|
||
|
dwWait = WaitForSingleObject (m_hCapturingThread, INFINITE);
|
||
|
|
||
|
DEBUGMSG (ZONE_VERBOSE, ("STOP2: dwWait =%d\r\n", dwWait));
|
||
|
ASSERT(dwWait != WAIT_FAILED);
|
||
|
|
||
|
CloseHandle(m_hCapturingThread);
|
||
|
m_hCapturingThread = NULL;
|
||
|
}
|
||
|
|
||
|
m_DPFlags &= ~DPFLAG_STARTED_SEND;
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT STDMETHODCALLTYPE SendVideoStream::SetMaxBitrate(UINT uMaxBitrate)
|
||
|
{
|
||
|
DWORD dwFrameRate=0;
|
||
|
UINT uSize=sizeof(DWORD);
|
||
|
BOOL bRet;
|
||
|
HRESULT hr;
|
||
|
|
||
|
hr = GetProperty(PROP_VIDEO_FRAME_RATE, &dwFrameRate, &uSize);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
bRet = SetTargetRates(dwFrameRate, (DWORD)uMaxBitrate);
|
||
|
if (bRet)
|
||
|
hr = S_OK;
|
||
|
else
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// IProperty::GetProperty / SetProperty
|
||
|
// (DataPump::MediaChannel::GetProperty)
|
||
|
// Properties of the MediaStream.
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::GetProperty(
|
||
|
DWORD prop,
|
||
|
PVOID pBuf,
|
||
|
LPUINT pcbBuf
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr = DPR_SUCCESS;
|
||
|
DWORD dwValue;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
UINT len = sizeof(DWORD); // most props are DWORDs
|
||
|
|
||
|
if (!pBuf || *pcbBuf < len)
|
||
|
{
|
||
|
*pcbBuf = len;
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
switch (prop)
|
||
|
{
|
||
|
#ifdef OLDSTUFF
|
||
|
case PROP_NET_SEND_STATS:
|
||
|
if (m_Net && *pcbBuf >= sizeof(RTP_STATS))
|
||
|
{
|
||
|
m_Net->GetSendStats((RTP_STATS *)pBuf);
|
||
|
*pcbBuf = sizeof(RTP_STATS);
|
||
|
} else
|
||
|
hr = DPR_INVALID_PROP_VAL;
|
||
|
|
||
|
break;
|
||
|
#endif
|
||
|
case PROP_DURATION:
|
||
|
hr = m_InMedia->GetProp(MC_PROP_DURATION, &dwPropVal);
|
||
|
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
|
break;
|
||
|
|
||
|
|
||
|
case PROP_RECORD_ON:
|
||
|
*(DWORD *)pBuf = ((m_DPFlags & DPFLAG_ENABLE_SEND) !=0);
|
||
|
break;
|
||
|
case PROP_CAPTURE_DEVICE:
|
||
|
*(UINT *)pBuf = m_CaptureDevice;
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_FRAME_RATE:
|
||
|
*((DWORD *)pBuf) = 100000 / m_frametime;
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_IMAGE_QUALITY:
|
||
|
hr = GetTemporalSpatialTradeOff((DWORD *)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_CAPTURE_AVAILABLE:
|
||
|
*(DWORD *)pBuf = (m_DPFlags & DP_FLAG_RECORD_CAP) != 0;
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_CAPTURE_DIALOGS_AVAILABLE:
|
||
|
hr = m_InMedia->GetProp(MC_PROP_VFW_DIALOGS, &dwPropVal);
|
||
|
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_PREVIEW_ON:
|
||
|
*(DWORD *)pBuf = ((m_DPFlags & DPFLAG_ENABLE_PREVIEW) != 0);
|
||
|
break;
|
||
|
|
||
|
case PROP_PAUSE_SEND:
|
||
|
*(DWORD *)pBuf = ((m_ThreadFlags & DPTFLAG_PAUSE_SEND) != 0);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hr = DPR_INVALID_PROP_ID;
|
||
|
break;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::SetProperty(
|
||
|
DWORD prop,
|
||
|
PVOID pBuf,
|
||
|
UINT cbBuf
|
||
|
)
|
||
|
{
|
||
|
DWORD dw;
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (cbBuf < sizeof (DWORD))
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
switch (prop)
|
||
|
{
|
||
|
|
||
|
case PROP_CAPTURE_DEVICE:
|
||
|
if (m_DPFlags & DPFLAG_ENABLE_PREVIEW)
|
||
|
{
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_CaptureDevice = *(UINT*)pBuf;
|
||
|
m_InMedia->SetProp(MC_PROP_MEDIA_DEV_ID, (DWORD)m_CaptureDevice);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_FRAME_RATE:
|
||
|
if (*(DWORD*)pBuf <= m_maxfps) {
|
||
|
DEBUGMSG(ZONE_VERBOSE, ("DP: setting fps = %d \n", *(DWORD*)pBuf));
|
||
|
// set frame rate here
|
||
|
m_frametime = 100000 / *(DWORD*)pBuf;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_IMAGE_QUALITY:
|
||
|
hr = SetTemporalSpatialTradeOff(*(DWORD*)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_RESET_IMAGE_QUALITY:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_IMAGE_QUALITY, VCM_DEFAULT_IMAGE_QUALITY);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_CAPTURE_DIALOG:
|
||
|
hr = ((VideoInControl *)m_InMedia)->DisplayDriverDialog(GetActiveWindow(), *(DWORD *)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_SIZE:
|
||
|
ASSERT(0);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_PREVIEW_ON:
|
||
|
ASSERT(0);
|
||
|
break;
|
||
|
case PROP_VIDEO_AUDIO_SYNC:
|
||
|
if (*(DWORD *)pBuf)
|
||
|
m_DPFlags |= DPFLAG_AV_SYNC;
|
||
|
else
|
||
|
m_DPFlags &= ~DPFLAG_AV_SYNC;
|
||
|
break;
|
||
|
|
||
|
case PROP_PAUSE_SEND:
|
||
|
if (*(DWORD *)pBuf)
|
||
|
m_ThreadFlags |= DPTFLAG_PAUSE_SEND;
|
||
|
else
|
||
|
m_ThreadFlags &= ~DPTFLAG_PAUSE_SEND;
|
||
|
break;
|
||
|
default:
|
||
|
return DPR_INVALID_PROP_ID;
|
||
|
break;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------
|
||
|
// IVideoRender implementation and support functions
|
||
|
|
||
|
|
||
|
|
||
|
// IVideoRender::Init
|
||
|
// (DataPump::Init)
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::Init(
|
||
|
DWORD_PTR dwUser,
|
||
|
LPFNFRAMEREADY pfCallback
|
||
|
)
|
||
|
{
|
||
|
// Save the event away. Note that we DO allow both send and receive to
|
||
|
// share an event
|
||
|
m_hRenderEvent = (HANDLE) dwUser;
|
||
|
// if pfCallback is NULL then dwUser is an event handle
|
||
|
m_pfFrameReadyCallback = pfCallback;
|
||
|
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
// IVideoRender::Done
|
||
|
// (DataPump::Done)
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::Done( )
|
||
|
{
|
||
|
m_hRenderEvent = NULL;
|
||
|
m_pfFrameReadyCallback = NULL;
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
// IVideoRender::GetFrame
|
||
|
// (DataPump::GetFrame)
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::GetFrame(
|
||
|
FRAMECONTEXT* pfc
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
PVOID pData = NULL;
|
||
|
UINT cbData = 0;
|
||
|
|
||
|
// Validate parameters
|
||
|
if (!pfc )
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
// Don't arbitrarily call out while holding this crs or you may deadlock...
|
||
|
EnterCriticalSection(&m_crs);
|
||
|
|
||
|
if ((m_DPFlags & DPFLAG_CONFIGURED_SEND) && m_pNextPacketToRender && !m_pNextPacketToRender->m_fRendering)
|
||
|
{
|
||
|
m_pNextPacketToRender->m_fRendering = TRUE;
|
||
|
m_pNextPacketToRender->GetDevData(&pData,&cbData);
|
||
|
pfc->lpData = (PUCHAR) pData;
|
||
|
pfc->dwReserved = (DWORD_PTR) m_pNextPacketToRender;
|
||
|
// set bmi length?
|
||
|
pfc->lpbmi = (PBITMAPINFO)&m_fDevSend.bih;
|
||
|
pfc->lpClipRect = &m_cliprect;
|
||
|
m_cRendering++;
|
||
|
hr = S_OK;
|
||
|
LOG((LOGMSG_GET_SEND_FRAME,m_pNextPacketToRender->GetIndex()));
|
||
|
} else
|
||
|
hr = S_FALSE; // nothing ready to render
|
||
|
|
||
|
LeaveCriticalSection(&m_crs);
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
// IVideoRender::ReleaseFrame
|
||
|
// (DataPump::ReleaseFrame)
|
||
|
|
||
|
STDMETHODIMP
|
||
|
SendVideoStream::ReleaseFrame(
|
||
|
FRAMECONTEXT* pfc
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
MediaPacket *pPacket;
|
||
|
|
||
|
// Validate parameters
|
||
|
if (!pfc)
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
// Handle a send frame
|
||
|
{
|
||
|
EnterCriticalSection(&m_crs);
|
||
|
|
||
|
// Don't arbitrarily call out while holding this crs or you may deadlock...
|
||
|
|
||
|
if ((m_DPFlags & DPFLAG_CONFIGURED_SEND) && (pPacket = (MediaPacket *)pfc->dwReserved) && pPacket->m_fRendering)
|
||
|
{
|
||
|
LOG((LOGMSG_RELEASE_SEND_FRAME,pPacket->GetIndex()));
|
||
|
pPacket->m_fRendering = FALSE;
|
||
|
pfc->dwReserved = 0;
|
||
|
// if its not the current frame
|
||
|
if (m_pNextPacketToRender != pPacket) {
|
||
|
pPacket->Recycle();
|
||
|
m_SendStream->Release(pPacket);
|
||
|
}
|
||
|
m_cRendering--;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = DPR_INVALID_PARAMETER;
|
||
|
|
||
|
LeaveCriticalSection(&m_crs);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT __stdcall SendVideoStream::SendKeyFrame(void)
|
||
|
{
|
||
|
MMRESULT mmr;
|
||
|
HVCMSTREAM hvs;
|
||
|
|
||
|
ASSERT(m_pVideoFilter);
|
||
|
|
||
|
if ((mmr = m_pVideoFilter->RequestIFrame()) != MMSYSERR_NOERROR)
|
||
|
{
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
return S_OK;
|
||
|
|
||
|
}
|
||
|
|
||
|
// IVideoChannel
|
||
|
HRESULT __stdcall SendVideoStream::SetTemporalSpatialTradeOff(DWORD dwVal)
|
||
|
{
|
||
|
HRESULT hr=DPR_NOT_CONFIGURED;
|
||
|
|
||
|
ASSERT(m_pVideoFilter);
|
||
|
|
||
|
if (m_pVideoFilter)
|
||
|
{
|
||
|
if (m_pTSTable == NULL)
|
||
|
{
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_IMAGE_QUALITY, dwVal);
|
||
|
}
|
||
|
m_dwCurrentTSSetting = dwVal;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT __stdcall SendVideoStream::GetTemporalSpatialTradeOff(DWORD *pdwVal)
|
||
|
{
|
||
|
HRESULT hr=DPR_NOT_CONFIGURED;
|
||
|
|
||
|
ASSERT(m_pVideoFilter);
|
||
|
|
||
|
if (m_pVideoFilter)
|
||
|
{
|
||
|
if (m_pTSTable == NULL)
|
||
|
{
|
||
|
*pdwVal = m_dwCurrentTSSetting;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = m_pVideoFilter->GetProperty(FM_PROP_VIDEO_IMAGE_QUALITY, pdwVal);
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT STDMETHODCALLTYPE RecvVideoStream::QueryInterface(REFIID iid, void **ppVoid)
|
||
|
{
|
||
|
// resolve duplicate inheritance to the SendMediaStream;
|
||
|
|
||
|
extern IID IID_IProperty;
|
||
|
|
||
|
if (iid == IID_IUnknown)
|
||
|
{
|
||
|
*ppVoid = (IUnknown*)((RecvMediaStream*)this);
|
||
|
}
|
||
|
else if (iid == IID_IMediaChannel)
|
||
|
{
|
||
|
*ppVoid = (IMediaChannel*)((RecvMediaStream *)this);
|
||
|
}
|
||
|
// else if (iid == IID_IVideoChannel)
|
||
|
// {
|
||
|
// *ppVoid = (IVideoChannel*)this;
|
||
|
// }
|
||
|
else if (iid == IID_IProperty)
|
||
|
{
|
||
|
*ppVoid = NULL;
|
||
|
ERROR_OUT(("Don't QueryInterface for IID_IProperty, use IMediaChannel"));
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
else if (iid == IID_IVideoRender)// satisfy symmetric property of QI
|
||
|
{
|
||
|
*ppVoid = (IVideoRender *)this;
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
*ppVoid = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
AddRef();
|
||
|
|
||
|
return S_OK;
|
||
|
|
||
|
}
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE RecvVideoStream::AddRef(void)
|
||
|
{
|
||
|
return InterlockedIncrement(&m_lRefCount);
|
||
|
}
|
||
|
|
||
|
ULONG STDMETHODCALLTYPE RecvVideoStream::Release(void)
|
||
|
{
|
||
|
LONG lRet;
|
||
|
|
||
|
lRet = InterlockedDecrement(&m_lRefCount);
|
||
|
|
||
|
if (lRet == 0)
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
else
|
||
|
return lRet;
|
||
|
}
|
||
|
|
||
|
DWORD CALLBACK RecvVideoStream::StartRenderingThread(PVOID pVoid)
|
||
|
{
|
||
|
RecvVideoStream *pThisStream = (RecvVideoStream*)pVoid;
|
||
|
return pThisStream->RenderingThread();
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
RecvVideoStream::Initialize(DataPump *pDP)
|
||
|
{
|
||
|
HRESULT hr = DPR_OUT_OF_MEMORY;
|
||
|
DWORD dwFlags = DP_FLAG_FULL_DUPLEX | DP_FLAG_AUTO_SWITCH ;
|
||
|
MEDIACTRLINIT mcInit;
|
||
|
FX_ENTRY ("DP::RecvVideoStream")
|
||
|
|
||
|
m_pIUnknown = (IUnknown *)NULL;
|
||
|
|
||
|
InitializeCriticalSection(&m_crs);
|
||
|
InitializeCriticalSection(&m_crsVidQoS);
|
||
|
InitializeCriticalSection(&m_crsIStreamSignal);
|
||
|
|
||
|
dwFlags |= DP_FLAG_VCM | DP_FLAG_VIDEO ;
|
||
|
|
||
|
// store the platform flags
|
||
|
// enable Send and Recv by default
|
||
|
m_DPFlags = (dwFlags & DP_MASK_PLATFORM) | DPFLAG_ENABLE_RECV;
|
||
|
// store a back pointer to the datapump container
|
||
|
m_pDP = pDP;
|
||
|
m_Net = NULL;
|
||
|
m_pIRTPRecv = NULL;
|
||
|
|
||
|
|
||
|
// Initialize data (should be in constructor)
|
||
|
m_RenderingDevice = (UINT) -1; // use VIDEO_MAPPER
|
||
|
|
||
|
|
||
|
// Create Receive and Transmit video streams
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_RecvStream = new RVStream(MAX_RXVRING_SIZE);
|
||
|
|
||
|
if (!m_RecvStream )
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: RxStream new failed\r\n", _fx_));
|
||
|
goto StreamAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Create Input and Output video filters
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_pVideoFilter = new VcmFilter();
|
||
|
m_dwSrcSize = 0;
|
||
|
if (m_pVideoFilter == NULL)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: VcmFilter new failed\r\n", _fx_));
|
||
|
goto FilterAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
//Create Video MultiMedia device control objects
|
||
|
DBG_SAVE_FILE_LINE
|
||
|
m_OutMedia = new VideoOutControl();
|
||
|
if ( !m_OutMedia)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: MediaControl new failed\r\n", _fx_));
|
||
|
goto MediaAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Initialize the recv-stream media control object
|
||
|
mcInit.dwFlags = dwFlags | DP_FLAG_RECV;
|
||
|
hr = m_OutMedia->Initialize(&mcInit);
|
||
|
if (hr != DPR_SUCCESS)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: OMedia->Init failed, hr=0x%lX\r\n", _fx_, hr));
|
||
|
goto MediaAllocError;
|
||
|
}
|
||
|
|
||
|
|
||
|
m_DPFlags |= DP_FLAG_RECORD_CAP ;
|
||
|
|
||
|
// set media to half duplex mode by default
|
||
|
m_OutMedia->SetProp(MC_PROP_DUPLEX_TYPE, DP_FLAG_HALF_DUPLEX);
|
||
|
m_DPFlags |= DPFLAG_INITIALIZED;
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
|
||
|
|
||
|
MediaAllocError:
|
||
|
if (m_OutMedia) delete m_OutMedia;
|
||
|
FilterAllocError:
|
||
|
if (m_pVideoFilter) delete m_pVideoFilter;
|
||
|
StreamAllocError:
|
||
|
if (m_RecvStream) delete m_RecvStream;
|
||
|
|
||
|
ERRORMESSAGE( ("%s: exit, hr=0x%lX\r\n", _fx_, hr));
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
// LOOK: identical to RecvAudioStream version.
|
||
|
RecvVideoStream::~RecvVideoStream()
|
||
|
{
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_INITIALIZED) {
|
||
|
m_DPFlags &= ~DPFLAG_INITIALIZED;
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_CONFIGURED_RECV)
|
||
|
UnConfigure();
|
||
|
|
||
|
// Close the receive and transmit streams
|
||
|
if (m_RecvStream) delete m_RecvStream;
|
||
|
|
||
|
// Close the wave devices
|
||
|
if (m_OutMedia) { delete m_OutMedia;}
|
||
|
// Close the filters
|
||
|
if (m_pVideoFilter)
|
||
|
delete m_pVideoFilter;
|
||
|
|
||
|
m_pDP->RemoveMediaChannel(MCF_RECV| MCF_VIDEO, this);
|
||
|
|
||
|
}
|
||
|
DeleteCriticalSection(&m_crs);
|
||
|
DeleteCriticalSection(&m_crsVidQoS);
|
||
|
DeleteCriticalSection(&m_crsIStreamSignal);
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
RecvVideoStream::Configure(
|
||
|
BYTE __RPC_FAR *pFormat,
|
||
|
UINT cbFormat,
|
||
|
BYTE __RPC_FAR *pChannelParams,
|
||
|
UINT cbParams,
|
||
|
IUnknown *pUnknown)
|
||
|
{
|
||
|
MMRESULT mmr;
|
||
|
DWORD dwSrcSize;
|
||
|
HRESULT hr;
|
||
|
BOOL fRet;
|
||
|
MEDIAPACKETINIT pcktInit;
|
||
|
MEDIACTRLCONFIG mcConfig;
|
||
|
MediaPacket **ppPckt;
|
||
|
ULONG cPckt;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
UINT ringSize = MAX_RXVRING_SIZE;
|
||
|
DWORD dwFlags, dwSizeDst, dwMaxFrag, dwMaxBitRate = 0;
|
||
|
|
||
|
VIDEOFORMATEX *pfRecv = (VIDEOFORMATEX*)pFormat;
|
||
|
VIDEO_CHANNEL_PARAMETERS vidChannelParams;
|
||
|
int optval=8192*4; // Use max SQCIF, QCIF I frame size
|
||
|
#ifdef TEST
|
||
|
DWORD dwTicks;
|
||
|
#endif
|
||
|
|
||
|
FX_ENTRY ("RecvVideoStream::Configure")
|
||
|
|
||
|
#ifdef TEST
|
||
|
dwTicks = GetTickCount();
|
||
|
#endif
|
||
|
|
||
|
// m_Net = pNet;
|
||
|
|
||
|
// get format details
|
||
|
if ((NULL == pFormat) || (NULL == pChannelParams)
|
||
|
|| (cbParams != sizeof(vidChannelParams)))
|
||
|
{
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
vidChannelParams = *(VIDEO_CHANNEL_PARAMETERS *)pChannelParams;
|
||
|
|
||
|
if (! (m_DPFlags & DPFLAG_INITIALIZED))
|
||
|
return DPR_OUT_OF_MEMORY; //BUGBUG: return proper error;
|
||
|
|
||
|
// if (m_Net)
|
||
|
// {
|
||
|
// hr = m_Net->QueryInterface(IID_IRTPRecv, (void **)&m_pIRTPRecv);
|
||
|
// if (!SUCCEEDED(hr))
|
||
|
// return hr;
|
||
|
// }
|
||
|
|
||
|
|
||
|
mmr = VcmFilter::SuggestDecodeFormat(pfRecv, &m_fDevRecv);
|
||
|
|
||
|
// initialize m_cliprect
|
||
|
SetRect(&m_cliprect, 0, 0, m_fDevRecv.bih.biWidth, m_fDevRecv.bih.biHeight);
|
||
|
|
||
|
// Initialize the recv-stream media control object
|
||
|
mcConfig.uDuration = MC_USING_DEFAULT; // set duration by samples per pkt
|
||
|
mcConfig.pDevFmt = &m_fDevRecv;
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, pfRecv->dwFormatTag, REP_RECV_VIDEO_FORMAT);
|
||
|
RETAILMSG(("NAC: Video Recv Format: %.4s", (LPSTR)&pfRecv->dwFormatTag));
|
||
|
|
||
|
mcConfig.hStrm = (DPHANDLE) m_RecvStream;
|
||
|
mcConfig.uDevId = m_RenderingDevice;
|
||
|
mcConfig.cbSamplesPerPkt = ChoosePacketSize(pfRecv);
|
||
|
hr = m_OutMedia->Configure(&mcConfig);
|
||
|
|
||
|
m_OutMedia->GetProp (MC_PROP_SIZE, &dwPropVal);
|
||
|
dwSizeDst = (DWORD)dwPropVal;
|
||
|
|
||
|
// BUGBUG - HARDCODED platform flags. The right way to do this is to
|
||
|
// have a smart filter object create() that creates a platform-aware
|
||
|
// instance of the object
|
||
|
|
||
|
dwFlags = DP_FLAG_RECV | DP_FLAG_VCM | DP_FLAG_VIDEO;
|
||
|
|
||
|
mmr = m_pVideoFilter->Open(pfRecv, &m_fDevRecv, 0); // maxfragsize == 0
|
||
|
|
||
|
if (hr != DPR_SUCCESS)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: RecvVideoFilter->Init failed, hr=0x%lX\r\n", _fx_, hr));
|
||
|
hr = DPR_CANT_OPEN_CODEC;
|
||
|
goto RecvFilterInitError;
|
||
|
}
|
||
|
|
||
|
// set the max. fragment size
|
||
|
DEBUGMSG(ZONE_DP,("%s: Video Recv: maxBitRate=%d, maxBPP=%d, MPI=%d\r\n", _fx_ ,vidChannelParams.ns_params.maxBitRate*100, vidChannelParams.ns_params.maxBPP*1024, vidChannelParams.ns_params.MPI ? 30 / vidChannelParams.ns_params.MPI : 30));
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, vidChannelParams.ns_params.MPI ? 30 / vidChannelParams.ns_params.MPI : 30, REP_RECV_VIDEO_MAXFPS);
|
||
|
UPDATE_REPORT_ENTRY(g_prptCallParameters, vidChannelParams.ns_params.maxBitRate*100, REP_RECV_VIDEO_BITRATE);
|
||
|
RETAILMSG(("NAC: Video Recv Max Frame Rate (negotiated - fps): %ld", vidChannelParams.ns_params.MPI ? 30 / vidChannelParams.ns_params.MPI : 30));
|
||
|
RETAILMSG(("NAC: Video Recv Max Bitrate (negotiated - bps): %ld", vidChannelParams.ns_params.maxBitRate*100));
|
||
|
INIT_COUNTER_MAX(g_pctrVideoReceive, vidChannelParams.ns_params.MPI ? 30 / vidChannelParams.ns_params.MPI : 30);
|
||
|
INIT_COUNTER_MAX(g_pctrVideoReceiveBytes, vidChannelParams.ns_params.maxBitRate*100);
|
||
|
|
||
|
// Initialize the recv stream
|
||
|
ZeroMemory (&pcktInit, sizeof (pcktInit));
|
||
|
|
||
|
pcktInit.pStrmConvSrcFmt = pfRecv;
|
||
|
pcktInit.pStrmConvDstFmt = &m_fDevRecv;
|
||
|
pcktInit.dwFlags = dwFlags;
|
||
|
pcktInit.cbOffsetRawData = 0;
|
||
|
pcktInit.cbSizeRawData = dwSizeDst;
|
||
|
|
||
|
m_OutMedia->FillMediaPacketInit (&pcktInit);
|
||
|
|
||
|
m_pVideoFilter->SuggestSrcSize(dwSizeDst, &m_dwSrcSize);
|
||
|
|
||
|
pcktInit.cbSizeNetData = m_dwSrcSize;
|
||
|
|
||
|
pcktInit.cbOffsetNetData = sizeof (RTP_HDR);
|
||
|
|
||
|
m_OutMedia->GetProp (MC_PROP_SPP, &dwPropVal);
|
||
|
|
||
|
ringSize = 8; // reserve space for 8 video frames
|
||
|
// may need to increase the number if a/v sync is enabled.
|
||
|
fRet = ((RVStream*)m_RecvStream)->Initialize (DP_FLAG_VIDEO, ringSize, NULL, &pcktInit, (DWORD)dwPropVal, pfRecv->nSamplesPerSec, m_pVideoFilter);
|
||
|
if (! fRet)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_DP, ("%s: RxvStream->Init failed, fRet=0%u\r\n", _fx_, fRet));
|
||
|
hr = DPR_CANT_INIT_RXV_STREAM;
|
||
|
goto RxStreamInitError;
|
||
|
}
|
||
|
|
||
|
// WS2Qos will be called in Start to communicate stream reservations to the
|
||
|
// remote endpoint using a RESV message
|
||
|
//
|
||
|
// We use a peak-rate allocation approach based on our target bitrates
|
||
|
// Note that for the token bucket size and the maximum SDU size, we now
|
||
|
// account for IP header overhead, and use the max frame fragment size
|
||
|
// instead of the maximum compressed image size returned by the codec
|
||
|
//
|
||
|
// Some of the parameters are left unspecified because they are set
|
||
|
// in the sender Tspec.
|
||
|
|
||
|
|
||
|
// Computer of actual bandwidth 70 % (but it's already been divided by 100)
|
||
|
dwMaxBitRate = vidChannelParams.ns_params.maxBitRate*70;
|
||
|
if (dwMaxBitRate > BW_ISDN_BITS)
|
||
|
{
|
||
|
dwMaxFrag = 1350;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dwMaxFrag = 512;
|
||
|
}
|
||
|
|
||
|
InitVideoFlowspec(&m_flowspec, dwMaxBitRate, dwMaxFrag, VID_AVG_PACKET_SIZE);
|
||
|
|
||
|
|
||
|
/*
|
||
|
// assume no more than 32 fragments for CIF and
|
||
|
// 20 fragments for SQCIF, QCIF
|
||
|
//BLOAT WARNING: this could be quite a bit of memory
|
||
|
// need to fix this to use a heap instead of fixed size buffers.
|
||
|
|
||
|
*/
|
||
|
|
||
|
// prepare headers for RxvStream
|
||
|
m_RecvStream->GetRing (&ppPckt, &cPckt);
|
||
|
m_OutMedia->RegisterData (ppPckt, cPckt);
|
||
|
m_OutMedia->PrepareHeaders ();
|
||
|
|
||
|
// Keep a weak reference to the IUnknown interface
|
||
|
// We will use it to query a Stream Signal interface pointer in Start()
|
||
|
m_pIUnknown = pUnknown;
|
||
|
|
||
|
m_DPFlags |= DPFLAG_CONFIGURED_RECV;
|
||
|
|
||
|
#ifdef TEST
|
||
|
LOG((LOGMSG_TIME_RECV_VIDEO_CONFIGURE,GetTickCount() - dwTicks));
|
||
|
#endif
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
|
||
|
RxStreamInitError:
|
||
|
m_pVideoFilter->Close();
|
||
|
RecvFilterInitError:
|
||
|
m_OutMedia->Close();
|
||
|
if (m_pIRTPRecv)
|
||
|
{
|
||
|
m_pIRTPRecv->Release();
|
||
|
m_pIRTPRecv = NULL;
|
||
|
}
|
||
|
DEBUGMSG (1, ("%s: failed, hr=0%u\r\n", _fx_, hr));
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void RecvVideoStream::UnConfigure()
|
||
|
{
|
||
|
|
||
|
|
||
|
#ifdef TEST
|
||
|
DWORD dwTicks;
|
||
|
|
||
|
dwTicks = GetTickCount();
|
||
|
#endif
|
||
|
|
||
|
if ( (m_DPFlags & DPFLAG_CONFIGURED_RECV)) {
|
||
|
|
||
|
Stop();
|
||
|
|
||
|
// Close the RTP state if its open
|
||
|
//m_Net->Close(); We should be able to do this in Disconnect()
|
||
|
m_Net = NULL;
|
||
|
if (m_pIRTPRecv)
|
||
|
{
|
||
|
m_pIRTPRecv->Release();
|
||
|
m_pIRTPRecv = NULL;
|
||
|
}
|
||
|
m_OutMedia->Reset();
|
||
|
m_OutMedia->UnprepareHeaders();
|
||
|
m_OutMedia->Close();
|
||
|
|
||
|
// Close the filter
|
||
|
m_pVideoFilter->Close();
|
||
|
|
||
|
// Close the receive stream
|
||
|
m_RecvStream->Destroy();
|
||
|
|
||
|
m_DPFlags &= ~(DPFLAG_CONFIGURED_RECV);
|
||
|
}
|
||
|
|
||
|
#ifdef TEST
|
||
|
LOG((LOGMSG_TIME_RECV_VIDEO_UNCONFIGURE,GetTickCount() - dwTicks));
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// NOTE: Identical to RecvAudioStream. Move up?
|
||
|
HRESULT
|
||
|
RecvVideoStream::Start()
|
||
|
{
|
||
|
int nRet=IFRAMES_CAPS_UNKNOWN;
|
||
|
FX_ENTRY ("RecvVideoStream::Start");
|
||
|
|
||
|
if (m_DPFlags & DPFLAG_STARTED_RECV)
|
||
|
return DPR_SUCCESS;
|
||
|
|
||
|
if ((!(m_DPFlags & DPFLAG_CONFIGURED_RECV)) || (m_pIRTPRecv==NULL))
|
||
|
return DPR_NOT_CONFIGURED;
|
||
|
|
||
|
ASSERT(!m_hRenderingThread);
|
||
|
|
||
|
m_ThreadFlags &= ~(DPTFLAG_STOP_PLAY|DPTFLAG_STOP_RECV);
|
||
|
|
||
|
m_RecvStream->SetRTP(m_pIRTPRecv);
|
||
|
|
||
|
SetFlowSpec();
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Decide whether or not we will be making I-Frame requests for lost packets
|
||
|
// This should be done for all scenarios except when we are calling
|
||
|
// NetMeeting 2.x. NM 2.x will send us periodic I-Frames.
|
||
|
|
||
|
m_fDiscontinuity = FALSE;
|
||
|
m_dwLastIFrameRequest = 0UL;
|
||
|
m_ulLastSeq = UINT_MAX;
|
||
|
|
||
|
|
||
|
if (m_pIUnknown)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
|
||
|
if (!m_pIStreamSignal)
|
||
|
{
|
||
|
hr = m_pIUnknown->QueryInterface(IID_IStreamSignal, (void **)&m_pIStreamSignal);
|
||
|
if (!HR_SUCCEEDED(hr))
|
||
|
{
|
||
|
m_pIStreamSignal = (IStreamSignal *)NULL;
|
||
|
m_pIUnknown = (IUnknown *)NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_pIStreamSignal)
|
||
|
{
|
||
|
nRet = GetIFrameCaps(m_pIStreamSignal);
|
||
|
|
||
|
if (nRet == IFRAMES_CAPS_NM2)
|
||
|
{
|
||
|
m_pIStreamSignal->Release();
|
||
|
m_pIStreamSignal = NULL;
|
||
|
m_pIUnknown = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
// Start playback thread
|
||
|
if (!(m_ThreadFlags & DPTFLAG_STOP_PLAY))
|
||
|
m_hRenderingThread = CreateThread(NULL,0,RecvVideoStream::StartRenderingThread,this,0,&m_RenderingThId);
|
||
|
// Start receive thread
|
||
|
#if 0
|
||
|
if (!m_pDP->m_hRecvThread) {
|
||
|
m_pDP->m_hRecvThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&StartDPRecvThread,m_pDP,0,&m_pDP->m_RecvThId);
|
||
|
//Tell the recv Thread we've turned on
|
||
|
if (m_pDP->m_hRecvThreadChangeEvent)
|
||
|
SetEvent (m_pDP->m_hRecvThreadChangeEvent);
|
||
|
}
|
||
|
m_pDP->m_nReceivers++;
|
||
|
#else
|
||
|
m_pDP->StartReceiving(this);
|
||
|
#endif
|
||
|
m_DPFlags |= DPFLAG_STARTED_RECV;
|
||
|
DEBUGMSG (ZONE_DP, ("%s: Rendering ThId =%x\r\n",_fx_, m_RenderingThId));
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
// LOOK: Identical to RecvAudioStream version.
|
||
|
HRESULT
|
||
|
RecvVideoStream::Stop()
|
||
|
{
|
||
|
|
||
|
DWORD dwWait;
|
||
|
|
||
|
FX_ENTRY ("RecvVideoStream::Stop");
|
||
|
|
||
|
if(!(m_DPFlags & DPFLAG_STARTED_RECV))
|
||
|
{
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
m_ThreadFlags = m_ThreadFlags |
|
||
|
DPTFLAG_STOP_RECV | DPTFLAG_STOP_PLAY ;
|
||
|
|
||
|
m_pDP->StopReceiving(this);
|
||
|
|
||
|
DEBUGMSG (ZONE_VERBOSE, ("%s: m_hRenderingThread =%x\r\n",_fx_, m_hRenderingThread));
|
||
|
|
||
|
/*
|
||
|
* we want to wait for all the threads to exit, but we need to handle windows
|
||
|
* messages (mostly from winsock) while waiting.
|
||
|
* we made several attempts at that. When we wait for messages in addition
|
||
|
* to the thread exit events, we crash in rrcm.dll, possibly because we
|
||
|
* process a winsock message to a thread that is terminating.
|
||
|
*
|
||
|
* needs more investigation before putting in code that handles messages
|
||
|
*/
|
||
|
|
||
|
if(m_hRenderingThread)
|
||
|
{
|
||
|
dwWait = WaitForSingleObject (m_hRenderingThread, INFINITE);
|
||
|
|
||
|
DEBUGMSG (ZONE_VERBOSE, ("%s: dwWait =%d\r\n", _fx_, dwWait));
|
||
|
ASSERT(dwWait != WAIT_FAILED);
|
||
|
|
||
|
|
||
|
CloseHandle(m_hRenderingThread);
|
||
|
m_hRenderingThread = NULL;
|
||
|
}
|
||
|
|
||
|
// Access to the stream signal interface needs to be serialized. We could crash
|
||
|
// if we release the interface here and we are still using that interface in the
|
||
|
// RTP callback.
|
||
|
if (m_pIStreamSignal)
|
||
|
{
|
||
|
EnterCriticalSection(&m_crsIStreamSignal);
|
||
|
m_pIStreamSignal->Release();
|
||
|
m_pIStreamSignal = (IStreamSignal *)NULL;
|
||
|
LeaveCriticalSection(&m_crsIStreamSignal);
|
||
|
}
|
||
|
|
||
|
//This is per channel, but the variable is "DPFlags"
|
||
|
m_DPFlags &= ~DPFLAG_STARTED_RECV;
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
// IProperty::GetProperty / SetProperty
|
||
|
// Properties of the MediaChannel.
|
||
|
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::GetProperty(
|
||
|
DWORD prop,
|
||
|
PVOID pBuf,
|
||
|
LPUINT pcbBuf
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr = DPR_SUCCESS;
|
||
|
RTP_STATS RTPStats;
|
||
|
DWORD dwValue;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
UINT len = sizeof(DWORD); // most props are DWORDs
|
||
|
|
||
|
if (!pBuf || *pcbBuf < len)
|
||
|
{
|
||
|
*pcbBuf = len;
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
switch (prop)
|
||
|
{
|
||
|
#ifdef OLDSTUFF
|
||
|
case PROP_NET_RECV_STATS:
|
||
|
if (m_Net && *pcbBuf >= sizeof(RTP_STATS))
|
||
|
{
|
||
|
m_Net->GetRecvStats((RTP_STATS *)pBuf);
|
||
|
*pcbBuf = sizeof(RTP_STATS);
|
||
|
} else
|
||
|
hr = DPR_INVALID_PROP_VAL;
|
||
|
|
||
|
break;
|
||
|
|
||
|
#endif
|
||
|
case PROP_DURATION:
|
||
|
hr = m_OutMedia->GetProp(MC_PROP_DURATION, &dwPropVal);
|
||
|
*(DWORD *)pBuf = (DWORD)dwPropVal;
|
||
|
break;
|
||
|
|
||
|
case PROP_PLAY_ON:
|
||
|
*(DWORD *)pBuf = ((m_ThreadFlags & DPFLAG_ENABLE_RECV)!=0);
|
||
|
break;
|
||
|
|
||
|
case PROP_PLAYBACK_DEVICE:
|
||
|
*(DWORD *)pBuf = m_RenderingDevice;
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_BRIGHTNESS:
|
||
|
hr = m_pVideoFilter->GetProperty(FM_PROP_VIDEO_BRIGHTNESS, (DWORD *)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_CONTRAST:
|
||
|
hr = m_pVideoFilter->GetProperty(FM_PROP_VIDEO_CONTRAST, (DWORD *)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_SATURATION:
|
||
|
hr = m_pVideoFilter->GetProperty(FM_PROP_VIDEO_SATURATION, (DWORD *)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_AUDIO_SYNC:
|
||
|
*(DWORD *)pBuf = ((m_DPFlags & DPFLAG_AV_SYNC) != 0);
|
||
|
break;
|
||
|
|
||
|
case PROP_PAUSE_RECV:
|
||
|
*(DWORD *)pBuf = ((m_ThreadFlags & DPTFLAG_PAUSE_RECV) != 0);
|
||
|
break;
|
||
|
default:
|
||
|
hr = DPR_INVALID_PROP_ID;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::SetProperty(
|
||
|
DWORD prop,
|
||
|
PVOID pBuf,
|
||
|
UINT cbBuf
|
||
|
)
|
||
|
{
|
||
|
DWORD dw;
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (cbBuf < sizeof (DWORD))
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
switch (prop)
|
||
|
{
|
||
|
|
||
|
#if 0
|
||
|
case PROP_PLAY_ON:
|
||
|
{
|
||
|
DWORD flag = (DPFLAG_ENABLE_RECV);
|
||
|
if (*(DWORD *)pBuf) {
|
||
|
m_DPFlags |= flag; // set the flag
|
||
|
Start();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_DPFlags &= ~flag; // clear the flag
|
||
|
Stop();
|
||
|
}
|
||
|
RETAILMSG(("NAC: %s", *(DWORD*)pBuf ? "Enabling":"Disabling"));
|
||
|
//hr = EnableStream( *(DWORD*)pBuf);
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
case PROP_PLAYBACK_DEVICE:
|
||
|
m_RenderingDevice = *(DWORD*)pBuf;
|
||
|
// RETAILMSG(("NAC: Setting default playback device to %d", m_RenderingDevice));
|
||
|
break;
|
||
|
|
||
|
|
||
|
case PROP_VIDEO_BRIGHTNESS:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_BRIGHTNESS, *(DWORD*)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_CONTRAST:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_CONTRAST, *(DWORD*)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_SATURATION:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_SATURATION, *(DWORD*)pBuf);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_RESET_BRIGHTNESS:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_BRIGHTNESS, VCM_DEFAULT_BRIGHTNESS);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_RESET_CONTRAST:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_CONTRAST, VCM_DEFAULT_CONTRAST);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_RESET_SATURATION:
|
||
|
hr = m_pVideoFilter->SetProperty(FM_PROP_VIDEO_SATURATION, VCM_DEFAULT_SATURATION);
|
||
|
break;
|
||
|
|
||
|
case PROP_VIDEO_SIZE:
|
||
|
// For now, do not change anything if we already are connected
|
||
|
ASSERT(0);
|
||
|
//return SetVideoSize(m_pDP->m_pNac, *(DWORD*)pBuf);
|
||
|
|
||
|
case PROP_VIDEO_AUDIO_SYNC:
|
||
|
if (*(DWORD *)pBuf)
|
||
|
m_DPFlags |= DPFLAG_AV_SYNC;
|
||
|
else
|
||
|
m_DPFlags &= ~DPFLAG_AV_SYNC;
|
||
|
break;
|
||
|
case PROP_PAUSE_RECV:
|
||
|
if (*(DWORD *)pBuf)
|
||
|
m_ThreadFlags |= DPTFLAG_PAUSE_RECV;
|
||
|
else
|
||
|
m_ThreadFlags &= ~DPTFLAG_PAUSE_RECV;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return DPR_INVALID_PROP_ID;
|
||
|
break;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
//---------------------------------------------------------------------
|
||
|
// IVideoRender implementation and support functions
|
||
|
|
||
|
|
||
|
|
||
|
// IVideoRender::Init
|
||
|
// (DataPump::Init)
|
||
|
// identical to SendVideoStream::Init
|
||
|
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::Init(
|
||
|
DWORD_PTR dwUser,
|
||
|
LPFNFRAMEREADY pfCallback
|
||
|
)
|
||
|
{
|
||
|
// Save the event away. Note that we DO allow both send and receive to
|
||
|
// share an event
|
||
|
m_hRenderEvent = (HANDLE)dwUser;
|
||
|
// if pfCallback is NULL then dwUser is an event handle
|
||
|
m_pfFrameReadyCallback = pfCallback;
|
||
|
|
||
|
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
// IVideoRender::Done
|
||
|
// idnentical to SendVideoStream::Done
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::Done( )
|
||
|
{
|
||
|
m_hRenderEvent = NULL;
|
||
|
m_pfFrameReadyCallback = NULL;
|
||
|
return DPR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// IVideoRender::GetFrame
|
||
|
// (RecvVideoStream::GetFrame)
|
||
|
// NOTE: subtly different from SendVideoStream implementation!
|
||
|
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::GetFrame(
|
||
|
FRAMECONTEXT* pfc
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
PVOID pData = NULL;
|
||
|
UINT cbData = 0;
|
||
|
|
||
|
// Validate parameters
|
||
|
if (!pfc )
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
// Don't arbitrarily call out while holding this crs or you may deadlock...
|
||
|
EnterCriticalSection(&m_crs);
|
||
|
|
||
|
if ((m_DPFlags & DPFLAG_CONFIGURED_RECV) && m_pNextPacketToRender && !m_pNextPacketToRender->m_fRendering)
|
||
|
{
|
||
|
m_pNextPacketToRender->m_fRendering = TRUE;
|
||
|
m_pNextPacketToRender->GetDevData(&pData,&cbData);
|
||
|
pfc->lpData = (PUCHAR) pData;
|
||
|
pfc->dwReserved = (DWORD_PTR) m_pNextPacketToRender;
|
||
|
// set bmi length?
|
||
|
pfc->lpbmi = (PBITMAPINFO)&m_fDevRecv.bih;
|
||
|
pfc->lpClipRect = &m_cliprect;
|
||
|
m_cRendering++;
|
||
|
hr = S_OK;
|
||
|
LOG((LOGMSG_GET_RECV_FRAME,m_pNextPacketToRender->GetIndex()));
|
||
|
} else
|
||
|
hr = S_FALSE; // nothing ready to render
|
||
|
|
||
|
LeaveCriticalSection(&m_crs);
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// IVideoRender::ReleaseFrame
|
||
|
// NOTE: subtly different from SendVideoStream implementation!
|
||
|
|
||
|
STDMETHODIMP
|
||
|
RecvVideoStream::ReleaseFrame(
|
||
|
FRAMECONTEXT* pfc
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
MediaPacket *pPacket;
|
||
|
|
||
|
// Validate parameters
|
||
|
if (!pfc)
|
||
|
return DPR_INVALID_PARAMETER;
|
||
|
|
||
|
// Handle a send frame
|
||
|
{
|
||
|
EnterCriticalSection(&m_crs);
|
||
|
|
||
|
// Don't arbitrarily call out while holding this crs or you may deadlock...
|
||
|
|
||
|
if ((m_DPFlags & DPFLAG_CONFIGURED_RECV) && (pPacket = (MediaPacket *)pfc->dwReserved) && pPacket->m_fRendering)
|
||
|
{
|
||
|
LOG((LOGMSG_RELEASE_SEND_FRAME,pPacket->GetIndex()));
|
||
|
pPacket->m_fRendering = FALSE;
|
||
|
pfc->dwReserved = 0;
|
||
|
// if its not the current frame
|
||
|
if (m_pNextPacketToRender != pPacket) {
|
||
|
pPacket->Recycle();
|
||
|
m_RecvStream->Release(pPacket);
|
||
|
}
|
||
|
m_cRendering--;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = DPR_INVALID_PARAMETER;
|
||
|
|
||
|
LeaveCriticalSection(&m_crs);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CALLBACK SendVideoStream::QosNotifyVideoCB(LPRESOURCEREQUESTLIST lpResourceRequestList, DWORD_PTR dwThis)
|
||
|
{
|
||
|
HRESULT hr=NOERROR;
|
||
|
LPRESOURCEREQUESTLIST prrl=lpResourceRequestList;
|
||
|
int i;
|
||
|
int iMaxBWUsage, iMaxCPUUsage;
|
||
|
DWORD dwCPUUsage, dwBWUsage;
|
||
|
int iCPUUsageId, iBWUsageId;
|
||
|
int iCPUDelta, iBWDelta, deltascale;
|
||
|
int iFrameRate, iMaxFrameRate, iOldFrameRate;
|
||
|
UINT dwSize = sizeof(int);
|
||
|
DWORD dwOverallCPUUsage;
|
||
|
#ifdef LOGSTATISTICS_ON
|
||
|
char szDebug[256];
|
||
|
HANDLE hDebugFile;
|
||
|
DWORD d;
|
||
|
#endif
|
||
|
DWORD dwEpoch;
|
||
|
SendVideoStream *pThis = (SendVideoStream *)dwThis;
|
||
|
|
||
|
FX_ENTRY("QosNotifyVideoCB");
|
||
|
|
||
|
|
||
|
// Get the max for the resources.
|
||
|
iMaxCPUUsage = -1L; iMaxBWUsage = -1L;
|
||
|
for (i=0, iCPUUsageId = -1L, iBWUsageId = -1L; i<(int)lpResourceRequestList->cRequests; i++)
|
||
|
if (lpResourceRequestList->aRequests[i].resourceID == RESOURCE_OUTGOING_BANDWIDTH)
|
||
|
iBWUsageId = i;
|
||
|
else if (lpResourceRequestList->aRequests[i].resourceID == RESOURCE_CPU_CYCLES)
|
||
|
iCPUUsageId = i;
|
||
|
|
||
|
// Enter critical section to allow QoS thread to read the statistics while capturing
|
||
|
EnterCriticalSection(&(pThis->m_crsVidQoS));
|
||
|
|
||
|
// Record the time of this callback call
|
||
|
pThis->m_Stats.dwNewestTs = timeGetTime();
|
||
|
|
||
|
// Only do anything if we have at least captured a frame in the previous epoch
|
||
|
if ((pThis->m_Stats.dwCount) && (pThis->m_Stats.dwNewestTs > pThis->m_Stats.dwOldestTs))
|
||
|
{
|
||
|
|
||
|
// Measure the epoch
|
||
|
dwEpoch = pThis->m_Stats.dwNewestTs - pThis->m_Stats.dwOldestTs;
|
||
|
|
||
|
#ifdef LOGSTATISTICS_ON
|
||
|
wsprintf(szDebug, " Epoch = %ld\r\n", dwEpoch);
|
||
|
OutputDebugString(szDebug);
|
||
|
#endif
|
||
|
// Compute the current average frame rate
|
||
|
iOldFrameRate = pThis->m_Stats.dwCount * 100000 / dwEpoch;
|
||
|
|
||
|
if (iCPUUsageId != -1L)
|
||
|
iMaxCPUUsage = lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin;
|
||
|
if (iBWUsageId != -1L)
|
||
|
iMaxBWUsage = lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin;
|
||
|
|
||
|
// Get general BW usage
|
||
|
dwBWUsage = pThis->m_Stats.dwBits * 1000UL / dwEpoch;
|
||
|
|
||
|
// Get general CPU usage. In order to reduce oscillations, apply low-pass filtering operation
|
||
|
// We will use our own CPU usage number ONLY if the call to GetCPUUsage() fails.
|
||
|
if (pThis->GetCPUUsage(&dwOverallCPUUsage))
|
||
|
{
|
||
|
if (pThis->m_Stats.dwSmoothedCPUUsage)
|
||
|
dwCPUUsage = (pThis->m_Stats.dwSmoothedCPUUsage + dwOverallCPUUsage) >> 1;
|
||
|
else
|
||
|
dwCPUUsage = dwOverallCPUUsage;
|
||
|
}
|
||
|
else
|
||
|
dwCPUUsage = (pThis->m_Stats.dwMsCap + pThis->m_Stats.dwMsComp) * 1000UL / dwEpoch;
|
||
|
|
||
|
// Record current CPU usage
|
||
|
pThis->m_Stats.dwSmoothedCPUUsage = dwCPUUsage;
|
||
|
|
||
|
#ifdef LOGSTATISTICS_ON
|
||
|
hDebugFile = CreateFile("C:\\QoS.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
|
||
|
SetFilePointer(hDebugFile, 0, NULL, FILE_END);
|
||
|
wsprintf(szDebug, " Overall CPU usage = %ld\r\n", dwOverallCPUUsage);
|
||
|
WriteFile(hDebugFile, szDebug, strlen(szDebug), &d, NULL);
|
||
|
OutputDebugString(szDebug);
|
||
|
CloseHandle(hDebugFile);
|
||
|
|
||
|
wsprintf(szDebug, " Number of frames dwCount = %ld\r\n", pThis->m_Stats.dwCount);
|
||
|
OutputDebugString(szDebug);
|
||
|
#endif
|
||
|
|
||
|
// For this first implementation, the only output variable is the frame rate of the
|
||
|
// video capture
|
||
|
#ifdef USE_NON_LINEAR_FPS_ADJUSTMENT
|
||
|
if (iCPUUsageId != -1L)
|
||
|
{
|
||
|
if (dwCPUUsage)
|
||
|
{
|
||
|
iCPUDelta = (iMaxCPUUsage - (int)dwCPUUsage) * 10 / (int)dwCPUUsage;
|
||
|
if (iCPUDelta >= 10)
|
||
|
iCPUDelta = 9;
|
||
|
else if (iCPUDelta <= -1)
|
||
|
iCPUDelta = -9;
|
||
|
}
|
||
|
else
|
||
|
iCPUDelta = 9;
|
||
|
}
|
||
|
else
|
||
|
iCPUDelta = 0;
|
||
|
|
||
|
if (iBWUsageId != -1L)
|
||
|
{
|
||
|
if (dwBWUsage)
|
||
|
{
|
||
|
iBWDelta = (iMaxBWUsage - (int)dwBWUsage) * 10 / (int)dwBWUsage;
|
||
|
if (iBWDelta >= 10)
|
||
|
iBWDelta = 9;
|
||
|
else if (iBWDelta <= -1)
|
||
|
iBWDelta = -9;
|
||
|
}
|
||
|
else
|
||
|
iBWDelta = 9;
|
||
|
}
|
||
|
else
|
||
|
iBWDelta = 0;
|
||
|
#else
|
||
|
if (iCPUUsageId != -1L)
|
||
|
{
|
||
|
if (dwCPUUsage)
|
||
|
iCPUDelta = (iMaxCPUUsage - (int)dwCPUUsage) * 100 / (int)dwCPUUsage;
|
||
|
else
|
||
|
iCPUDelta = 90;
|
||
|
}
|
||
|
else
|
||
|
iCPUDelta = 0;
|
||
|
|
||
|
if (iBWUsageId != -1L)
|
||
|
{
|
||
|
if (dwBWUsage)
|
||
|
iBWDelta = (iMaxBWUsage - (int)dwBWUsage) * 100 / (int)dwBWUsage;
|
||
|
else
|
||
|
iBWDelta = 90;
|
||
|
}
|
||
|
else
|
||
|
iBWDelta = 0;
|
||
|
#endif
|
||
|
|
||
|
UPDATE_COUNTER(g_pctrVideoCPUuse, iCPUDelta);
|
||
|
UPDATE_COUNTER(g_pctrVideoBWuse, iBWDelta);
|
||
|
|
||
|
#ifdef USE_NON_LINEAR_FPS_ADJUSTMENT
|
||
|
iFrameRate = iOldFrameRate + iOldFrameRate * g_QoSMagic[iCPUDelta + 9][iBWDelta + 9] / 100;
|
||
|
#else
|
||
|
deltascale = iCPUDelta;
|
||
|
if (deltascale > iBWDelta) deltascale = iBWDelta;
|
||
|
if (deltascale > 90) deltascale = 90;
|
||
|
if (deltascale < -90) deltascale = -90;
|
||
|
iFrameRate = iOldFrameRate + (iOldFrameRate * deltascale) / 100;
|
||
|
#endif
|
||
|
|
||
|
// Initialize QoS structure. Only the four first fields should be zeroed.
|
||
|
// The handle to the CPU performance key should not be cleared.
|
||
|
ZeroMemory(&(pThis->m_Stats), 4UL * sizeof(DWORD));
|
||
|
|
||
|
// The video should reduce its CPU and bandwidth usage quickly, but probably shouldn't
|
||
|
// be allowed to increase its CPU and bandwidth usage as fast. Let's increase the
|
||
|
// frame rate at half the speed it would be decreased when we are above 5fps.
|
||
|
if ((iFrameRate > iOldFrameRate) && (iFrameRate > 500))
|
||
|
iFrameRate -= (iFrameRate - iOldFrameRate) >> 1;
|
||
|
|
||
|
// We should keep our requirements between a minimum that will allow us to catch up
|
||
|
// quickly and the current max frame rate
|
||
|
iMaxFrameRate = pThis->m_maxfps; // max negotiated for call
|
||
|
|
||
|
// if using a modem, then the frame rate is determined by the
|
||
|
// temporal spatial tradeoff
|
||
|
|
||
|
if (pThis->m_pTSTable)
|
||
|
{
|
||
|
iMaxFrameRate = min(iMaxFrameRate, pThis->m_pTSTable[pThis->m_dwCurrentTSSetting]);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (iFrameRate > iMaxFrameRate)
|
||
|
iFrameRate = iMaxFrameRate;
|
||
|
if (iFrameRate < 50) // make sure framerate is > 0 (this does not mean 50 fps; it is .50 fps)
|
||
|
iFrameRate = 50;
|
||
|
|
||
|
// Update the frame rate
|
||
|
if (iFrameRate != iOldFrameRate)
|
||
|
pThis->SetProperty(PROP_VIDEO_FRAME_RATE, &iFrameRate, sizeof(int));
|
||
|
|
||
|
|
||
|
|
||
|
// Record the time of this call for the next callback call
|
||
|
pThis->m_Stats.dwOldestTs = pThis->m_Stats.dwNewestTs;
|
||
|
|
||
|
// Get the latest RTCP stats and update the counters.
|
||
|
// we do this here because it is called periodically.
|
||
|
if (pThis->m_pRTPSend)
|
||
|
{
|
||
|
UINT lastPacketsLost = pThis->m_RTPStats.packetsLost;
|
||
|
if (g_pctrVideoSendLost && SUCCEEDED(pThis->m_pRTPSend->GetSendStats(&pThis->m_RTPStats)))
|
||
|
UPDATE_COUNTER(g_pctrVideoSendLost, pThis->m_RTPStats.packetsLost-lastPacketsLost);
|
||
|
}
|
||
|
|
||
|
// Leave critical section
|
||
|
LeaveCriticalSection(&(pThis->m_crsVidQoS));
|
||
|
|
||
|
DEBUGMSG(ZONE_QOS, ("%s: Over the last %ld.%lds, video used %ld%% of the CPU (max allowed %ld%%) and %ld bps (max allowed %ld bps)\r\n", _fx_, dwEpoch / 1000UL, dwEpoch - (dwEpoch / 1000UL) * 1000UL, dwCPUUsage / 10UL, iMaxCPUUsage / 10UL, dwBWUsage, iMaxBWUsage));
|
||
|
DEBUGMSG(ZONE_QOS, ("%s: Ajusting target frame rate from %ld.%ld fps to %ld.%ld fps\r\n", _fx_, iOldFrameRate / 100UL, iOldFrameRate - (iOldFrameRate / 100UL) * 100UL, iFrameRate / 100UL, iFrameRate - (iFrameRate / 100UL) * 100UL));
|
||
|
|
||
|
// Set the target bitrates and frame rates on the codec
|
||
|
pThis->SetTargetRates(iFrameRate, iMaxBWUsage);
|
||
|
|
||
|
#ifdef LOGSTATISTICS_ON
|
||
|
// How are we doing?
|
||
|
if (iCPUUsageId != -1L)
|
||
|
{
|
||
|
if (iCPUDelta > 0)
|
||
|
wsprintf(szDebug, "Max CPU Usage: %ld, Current CPU Usage: %ld, Increase CPU Usage by: %li, Old Frame Rate: %ld, New Frame Rate: %ld\r\n", lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin, dwCPUUsage, iCPUDelta, iOldFrameRate, iFrameRate);
|
||
|
else
|
||
|
wsprintf(szDebug, "Max CPU Usage: %ld, Current CPU Usage: %ld, Decrese CPU Usage by: %li, Old Frame Rate: %ld, New Frame Rate: %ld\r\n", lpResourceRequestList->aRequests[iCPUUsageId].nUnitsMin, dwCPUUsage, iCPUDelta, iOldFrameRate, iFrameRate);
|
||
|
hDebugFile = CreateFile("C:\\QoS.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
|
||
|
SetFilePointer(hDebugFile, 0, NULL, FILE_END);
|
||
|
WriteFile(hDebugFile, szDebug, strlen(szDebug), &d, NULL);
|
||
|
CloseHandle(hDebugFile);
|
||
|
OutputDebugString(szDebug);
|
||
|
}
|
||
|
|
||
|
if (iBWUsageId != -1L)
|
||
|
{
|
||
|
if (iBWDelta > 0)
|
||
|
wsprintf(szDebug, "Max BW Usage: %ld, Current BW Usage: %ld, Increase BW Usage by: %li\r\n", lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin, dwBWUsage, iBWDelta);
|
||
|
else
|
||
|
wsprintf(szDebug, "Max BW Usage: %ld, Current BW Usage: %ld, Decrease BW Usage by: %li\r\n", lpResourceRequestList->aRequests[iBWUsageId].nUnitsMin, dwBWUsage, iBWDelta);
|
||
|
hDebugFile = CreateFile("C:\\QoS.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
|
||
|
SetFilePointer(hDebugFile, 0, NULL, FILE_END);
|
||
|
WriteFile(hDebugFile, szDebug, strlen(szDebug), &d, NULL);
|
||
|
CloseHandle(hDebugFile);
|
||
|
OutputDebugString(szDebug);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Leave critical section
|
||
|
LeaveCriticalSection(&(pThis->m_crsVidQoS));
|
||
|
|
||
|
#ifdef LOGSTATISTICS_ON
|
||
|
hDebugFile = CreateFile("C:\\QoS.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
|
||
|
SetFilePointer(hDebugFile, 0, NULL, FILE_END);
|
||
|
wsprintf(szDebug, "Not enough data captured -> Leave without any change\r\n");
|
||
|
WriteFile(hDebugFile, szDebug, strlen(szDebug), &d, NULL);
|
||
|
CloseHandle(hDebugFile);
|
||
|
OutputDebugString(szDebug);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// SortOrder
|
||
|
// Helper function to search for the specific format type and set its sort
|
||
|
// order to the desired number
|
||
|
BOOL
|
||
|
SortOrder(
|
||
|
IAppVidCap *pavc,
|
||
|
BASIC_VIDCAP_INFO* pvidcaps,
|
||
|
DWORD dwcFormats,
|
||
|
DWORD dwFlags,
|
||
|
WORD wDesiredSortOrder,
|
||
|
int nNumFormats
|
||
|
)
|
||
|
{
|
||
|
int i, j;
|
||
|
int nNumSizes = 0;
|
||
|
int *aFrameSizes = (int *)NULL;
|
||
|
int *aMinFrameSizes = (int *)NULL;
|
||
|
int iMaxPos;
|
||
|
WORD wTempPos, wMaxSortIndex;
|
||
|
|
||
|
// Scale sort value
|
||
|
wDesiredSortOrder *= (WORD)nNumFormats;
|
||
|
|
||
|
// Local buffer of sizes that match dwFlags
|
||
|
if (!(aFrameSizes = (int *)MEMALLOC(nNumFormats * sizeof (int))))
|
||
|
goto out;
|
||
|
|
||
|
// Look through all the formats until we find the ones we want
|
||
|
// Save the position of these entries
|
||
|
for (i=0; i<(int)dwcFormats; i++)
|
||
|
if (SIZE_TO_FLAG(pvidcaps[i].enumVideoSize) == dwFlags)
|
||
|
aFrameSizes[nNumSizes++] = i;
|
||
|
|
||
|
// Now order those entries from highest to lowest sort index
|
||
|
for (i=0; i<nNumSizes; i++)
|
||
|
{
|
||
|
for (iMaxPos = -1L, wMaxSortIndex=0UL, j=i; j<nNumSizes; j++)
|
||
|
{
|
||
|
if (pvidcaps[aFrameSizes[j]].wSortIndex > wMaxSortIndex)
|
||
|
{
|
||
|
wMaxSortIndex = pvidcaps[aFrameSizes[j]].wSortIndex;
|
||
|
iMaxPos = j;
|
||
|
}
|
||
|
}
|
||
|
if (iMaxPos != -1L)
|
||
|
{
|
||
|
wTempPos = (WORD)aFrameSizes[i];
|
||
|
aFrameSizes[i] = aFrameSizes[iMaxPos];
|
||
|
aFrameSizes[iMaxPos] = wTempPos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Change the sort index of the sorted entries
|
||
|
for (; nNumSizes--;)
|
||
|
pvidcaps[aFrameSizes[nNumSizes]].wSortIndex = wDesiredSortOrder++;
|
||
|
|
||
|
// Release memory
|
||
|
MEMFREE(aFrameSizes);
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
out:
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// LOOK: this is identical to the RecvAudioStream implementation
|
||
|
HRESULT
|
||
|
RecvVideoStream::GetCurrentPlayNTPTime(NTP_TS *pNtpTime)
|
||
|
{
|
||
|
DWORD rtpTime;
|
||
|
#ifdef OLDSTUFF
|
||
|
if ((m_DPFlags & DPFLAG_STARTED_RECV) && m_fReceiving) {
|
||
|
if (m_Net->RTPtoNTP(m_PlaybackTimestamp,pNtpTime))
|
||
|
return S_OK;
|
||
|
}
|
||
|
#endif
|
||
|
return 0xff; // return proper error
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOL RecvVideoStream::IsEmpty() {
|
||
|
return m_RecvStream->IsEmpty();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Called by the recv thread to setup the stream for receiving.
|
||
|
Call RTP object to post the initial recv buffer(s).
|
||
|
*/
|
||
|
// NOTE: identical to audio version except for choice of number of packet buffers
|
||
|
HRESULT
|
||
|
RecvVideoStream::StartRecv(HWND hWnd)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
DWORD dwPropVal = 0;
|
||
|
UINT numPackets;
|
||
|
if ((!(m_ThreadFlags & DPTFLAG_STOP_RECV) ) && (m_DPFlags & DPFLAG_CONFIGURED_RECV))
|
||
|
{
|
||
|
numPackets = m_dwSrcSize > 10000 ? MAX_VIDEO_FRAGMENTS : MAX_QCIF_VIDEO_FRAGMENTS;
|
||
|
|
||
|
hr = m_pIRTPRecv->SetRecvNotification(&RTPRecvCallback, (DWORD_PTR)this, numPackets);
|
||
|
|
||
|
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
// NOTE: identical to audio version
|
||
|
HRESULT
|
||
|
RecvVideoStream::StopRecv()
|
||
|
{
|
||
|
// Free any RTP buffers that we're holding on to
|
||
|
m_RecvStream->ReleaseNetBuffers();
|
||
|
// dont recv on this stream
|
||
|
m_pIRTPRecv->CancelRecvNotification();
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT RecvVideoStream::RTPCallback(WSABUF *pWsaBuf, DWORD timestamp, UINT seq, UINT fMark)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
BOOL fSkippedAFrame;
|
||
|
BOOL fReceivedKeyframe;
|
||
|
|
||
|
FX_ENTRY("RecvVideoStream::RTPCallback");
|
||
|
|
||
|
// if we are paused, reject the packet
|
||
|
if (m_ThreadFlags & DPTFLAG_PAUSE_RECV)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
// PutNextNetIn will return DPR_SUCESS to indicate a new frame
|
||
|
// S_FALSE if success, but no new frame
|
||
|
// error otherwise
|
||
|
// It always takes care of freeing the RTP buffers
|
||
|
hr = m_RecvStream->PutNextNetIn(pWsaBuf, timestamp, seq, fMark, &fSkippedAFrame, &fReceivedKeyframe);
|
||
|
|
||
|
if (m_pIUnknown)
|
||
|
{
|
||
|
// Check out the sequence number
|
||
|
// If there is a gap between the new sequence number and the last
|
||
|
// one, a frame got lost. Generate an I-Frame request then, but no more
|
||
|
// often than one every 15 seconds. How should we go about NM2.0? Other
|
||
|
// clients that don't support I-Frame requests.
|
||
|
//
|
||
|
// Is there a discontinuity in sequence numbers that was detected
|
||
|
// in the past but not handled because an I-Frame request had alreay
|
||
|
// been sent less than 15s ago? Is there a new discontinuity?
|
||
|
if (FAILED(hr) || fSkippedAFrame || m_fDiscontinuity || ((seq > 0) && (m_ulLastSeq != UINT_MAX) && ((seq - 1) > m_ulLastSeq)))
|
||
|
{
|
||
|
DWORD dwNow = GetTickCount();
|
||
|
|
||
|
// Was the last time we issued an I-Frame request more than 15s ago?
|
||
|
if ((dwNow > m_dwLastIFrameRequest) && ((dwNow - m_dwLastIFrameRequest) > MIN_IFRAME_REQUEST_INTERVAL))
|
||
|
{
|
||
|
DEBUGMSG (ZONE_IFRAME, ("%s: Loss detected - Sending I-Frame request...\r\n", _fx_));
|
||
|
|
||
|
m_dwLastIFrameRequest = dwNow;
|
||
|
m_fDiscontinuity = FALSE;
|
||
|
|
||
|
// Access to the stream signal interface needs to be serialized. We could crash
|
||
|
// if we used the interface here while Stop() is releasing it.
|
||
|
EnterCriticalSection(&m_crsIStreamSignal);
|
||
|
if (m_pIStreamSignal)
|
||
|
m_pIStreamSignal->PictureUpdateRequest();
|
||
|
LeaveCriticalSection(&m_crsIStreamSignal);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!fReceivedKeyframe)
|
||
|
{
|
||
|
DEBUGMSG (ZONE_IFRAME, ("%s: Loss detected but too soon to send I-Frame request. Wait %ld ms.\r\n", _fx_, MIN_IFRAME_REQUEST_INTERVAL - (dwNow - m_dwLastIFrameRequest)));
|
||
|
m_fDiscontinuity = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DEBUGMSG (ZONE_IFRAME, ("%s: Received a keyframe - resetting packet loss detector\r\n", _fx_));
|
||
|
m_fDiscontinuity = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_ulLastSeq = seq;
|
||
|
}
|
||
|
|
||
|
if (hr == DPR_SUCCESS)
|
||
|
{
|
||
|
m_OutMedia->GetProp (MC_PROP_EVENT_HANDLE, &dwPropVal);
|
||
|
if (dwPropVal)
|
||
|
{
|
||
|
SetEvent( (HANDLE) dwPropVal);
|
||
|
}
|
||
|
}
|
||
|
else if (FAILED(hr))
|
||
|
{
|
||
|
DEBUGMSG(ZONE_DP,("RVStream::PutNextNetIn (ts=%d,seq=%d,fMark=%d) failed with 0x%lX\r\n",timestamp,seq,fMark,hr));
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
#define TOTAL_BYTES 8192
|
||
|
#define BYTE_INCREMENT 1024
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc EXTERNAL QOSFUNC
|
||
|
*
|
||
|
* @func void | StartCPUUsageCollection | This function does all necessary
|
||
|
* initialization for CPU usage data collection.
|
||
|
*
|
||
|
* @rdesc Although this function doesn't ever fail, m_Stats.hPerfKey is set to a
|
||
|
* valid HKEY value if initialization occured correctly, and NULL otherwise.
|
||
|
*
|
||
|
* @comm This functions executes two different code paths: one for NT and one
|
||
|
* for Win95-98.
|
||
|
*
|
||
|
* @devnote MSDN references:
|
||
|
* Microsoft Knowledge Base, Article ID Q174631
|
||
|
* "HOWTO: Access the Performance Registry Under Windows 95"
|
||
|
*
|
||
|
* Microsoft Knowledge Base, Article ID Q107728
|
||
|
* "Retrieving Counter Data from the Registry"
|
||
|
*
|
||
|
* Microsoft Knowledge Base, Article ID Q178887
|
||
|
* "INFO: Troubleshooting Performance Registry Access Violations"
|
||
|
*
|
||
|
* Also, used section "Platform SDK\Windows Base Services\Windows NT Features\Performance Data Helper"
|
||
|
***************************************************************************/
|
||
|
void SendVideoStream::StartCPUUsageCollection(void)
|
||
|
{
|
||
|
PPERF_DATA_BLOCK pPerfDataBlock;
|
||
|
PPERF_OBJECT_TYPE pPerfObjectType;
|
||
|
PPERF_COUNTER_DEFINITION pPerfCounterDefinition;
|
||
|
PPERF_INSTANCE_DEFINITION pPerfInstanceDefinition;
|
||
|
PPERF_COUNTER_BLOCK pPerfCounterBlock;
|
||
|
OSVERSIONINFO osvInfo = {0};
|
||
|
DWORD cbCounterData;
|
||
|
DWORD cbTryCounterData;
|
||
|
DWORD dwType;
|
||
|
HANDLE hPerfData;
|
||
|
char *pszData;
|
||
|
char *pszIndex;
|
||
|
char szProcessorIndex[16];
|
||
|
long lRet;
|
||
|
|
||
|
FX_ENTRY("SendVideoStream::StartCPUUsageCollection");
|
||
|
|
||
|
// Are we on NT or Win95/98 ?
|
||
|
osvInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
||
|
GetVersionEx(&osvInfo);
|
||
|
|
||
|
if (m_Stats.fWinNT = (BOOL)(osvInfo.dwPlatformId == VER_PLATFORM_WIN32_NT))
|
||
|
{
|
||
|
// Enable the collection of CPU performance data on Win NT
|
||
|
|
||
|
// Open the registry key that contains the performance counter indices and names.
|
||
|
// 009 is the U.S. English language id. In a non-English version of Windows NT,
|
||
|
// performance counters are stored both in the native language of the system and
|
||
|
// in English.
|
||
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009", NULL, KEY_READ, &m_Stats.hPerfKey) != ERROR_SUCCESS)
|
||
|
goto MyError0;
|
||
|
else
|
||
|
{
|
||
|
// Get all the counter indices and names.
|
||
|
|
||
|
// Read the performance data from the registry. The size of that data may change
|
||
|
// between each call to the registry. We first get the current size of the buffer,
|
||
|
// allocate it, and try to read from the registry into it. If there already isn't
|
||
|
// enough room in the buffer, we realloc() it until we manage to read all the data.
|
||
|
if (RegQueryValueEx(m_Stats.hPerfKey, "Counters", NULL, &dwType, NULL, &cbCounterData) != ERROR_SUCCESS)
|
||
|
cbCounterData = TOTAL_BYTES;
|
||
|
|
||
|
// Allocate buffer for counter indices and names
|
||
|
if (!(m_Stats.NtCPUUsage.hPerfData = (PBYTE)LocalAlloc (LMEM_MOVEABLE, cbCounterData)))
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)NULL;
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
goto MyError0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)LocalLock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
|
||
|
cbTryCounterData = cbCounterData;
|
||
|
while((lRet = RegQueryValueEx(m_Stats.hPerfKey, "Counters", NULL, NULL, m_Stats.NtCPUUsage.pbyPerfData, &cbTryCounterData)) == ERROR_MORE_DATA)
|
||
|
{
|
||
|
cbCounterData += BYTE_INCREMENT;
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
hPerfData = LocalReAlloc(m_Stats.NtCPUUsage.hPerfData, cbCounterData, LMEM_MOVEABLE);
|
||
|
if (!hPerfData)
|
||
|
{
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
goto MyError1;
|
||
|
}
|
||
|
m_Stats.NtCPUUsage.hPerfData = hPerfData;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)LocalLock(hPerfData);
|
||
|
cbTryCounterData = cbCounterData;
|
||
|
}
|
||
|
|
||
|
// We don't need that key anymore
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
|
||
|
if (lRet != ERROR_SUCCESS)
|
||
|
goto MyError1;
|
||
|
else
|
||
|
{
|
||
|
// The data is stored as MULTI_SZ strings. This data type consists
|
||
|
// of a list of strings, each terminated with NULL. The last string
|
||
|
// is followed by an additional NULL. The strings are listed in
|
||
|
// pairs. The first string of each pair is the string of the index,
|
||
|
// and the second string is the actual name of the index. The Counter
|
||
|
// data uses only even-numbered indexes. For example, the Counter
|
||
|
// data contains the following object and counter name strings.
|
||
|
// Examples:
|
||
|
// 2 System
|
||
|
// 4 Memory
|
||
|
// 6 % Processor Time
|
||
|
//
|
||
|
// Look for the "% Processor Time" counter
|
||
|
pszData = (char *)m_Stats.NtCPUUsage.pbyPerfData;
|
||
|
pszIndex = (char *)m_Stats.NtCPUUsage.pbyPerfData;
|
||
|
|
||
|
while (*pszData && lstrcmpi(pszData, "% Processor Time"))
|
||
|
{
|
||
|
pszIndex = pszData;
|
||
|
pszData += lstrlen(pszData) + 1;
|
||
|
}
|
||
|
|
||
|
if (!pszData)
|
||
|
{
|
||
|
// Couldn't find "% Processor Time" counter!!!
|
||
|
goto MyError1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.dwPercentProcessorIndex = atol(pszIndex);
|
||
|
|
||
|
// Look for the "Processor" object
|
||
|
pszIndex = pszData = (char *)m_Stats.NtCPUUsage.pbyPerfData;
|
||
|
|
||
|
while (*pszData && lstrcmpi(pszData, "Processor"))
|
||
|
{
|
||
|
pszIndex = pszData;
|
||
|
pszData += lstrlen(pszData) + 1;
|
||
|
}
|
||
|
|
||
|
if (!pszData)
|
||
|
{
|
||
|
// Couldn't find "Processor" counter!!!
|
||
|
goto MyError1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.dwProcessorIndex = atol(pszIndex);
|
||
|
CopyMemory(szProcessorIndex, pszIndex, lstrlen(pszIndex));
|
||
|
|
||
|
// Read the PERF_DATA_BLOCK header structure. It describes the system
|
||
|
// and the performance data. The PERF_DATA_BLOCK structure is followed
|
||
|
// by a list of object information blocks (one per object). We use the
|
||
|
// counter index to retrieve object information.
|
||
|
|
||
|
// Under some cicumstances (cf. Q178887 for details) the RegQueryValueEx
|
||
|
// function may cause an Access Violation because of a buggy performance
|
||
|
// extension DLL such as SQL's.
|
||
|
__try
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.cbPerfData = cbCounterData;
|
||
|
while((lRet = RegQueryValueEx(HKEY_PERFORMANCE_DATA, szProcessorIndex, NULL, NULL, m_Stats.NtCPUUsage.pbyPerfData, &cbCounterData)) == ERROR_MORE_DATA)
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.cbPerfData += BYTE_INCREMENT;
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
hPerfData = LocalReAlloc(m_Stats.NtCPUUsage.hPerfData, m_Stats.NtCPUUsage.cbPerfData, LMEM_MOVEABLE);
|
||
|
if (!hPerfData)
|
||
|
goto MyError1;
|
||
|
m_Stats.NtCPUUsage.hPerfData = hPerfData;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)LocalLock(hPerfData);
|
||
|
cbCounterData = m_Stats.NtCPUUsage.cbPerfData;
|
||
|
}
|
||
|
}
|
||
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
||
|
{
|
||
|
ERRORMESSAGE(("%s: Performance Registry Access Violation -> don't use perf counters for CPU measurements\r\n", _fx_));
|
||
|
goto MyError1;
|
||
|
}
|
||
|
|
||
|
if (lRet != ERROR_SUCCESS)
|
||
|
goto MyError1;
|
||
|
else
|
||
|
{
|
||
|
// Each object information block contains a PERF_OBJECT_TYPE structure,
|
||
|
// which describes the performance data for the object. Look for the one
|
||
|
// that applies to CPU usage based on its index value.
|
||
|
pPerfDataBlock = (PPERF_DATA_BLOCK)m_Stats.NtCPUUsage.pbyPerfData;
|
||
|
pPerfObjectType = (PPERF_OBJECT_TYPE)(m_Stats.NtCPUUsage.pbyPerfData + pPerfDataBlock->HeaderLength);
|
||
|
for (int i = 0; i < (int)pPerfDataBlock->NumObjectTypes; i++)
|
||
|
{
|
||
|
if (pPerfObjectType->ObjectNameTitleIndex == m_Stats.NtCPUUsage.dwProcessorIndex)
|
||
|
{
|
||
|
// The PERF_OBJECT_TYPE structure is followed by a list of PERF_COUNTER_DEFINITION
|
||
|
// structures, one for each counter defined for the object. The list of PERF_COUNTER_DEFINITION
|
||
|
// structures is followed by a list of instance information blocks (one for each instance).
|
||
|
//
|
||
|
// Each instance information block contains a PERF_INSTANCE_DEFINITION structure and
|
||
|
// a PERF_COUNTER_BLOCK structure, followed by the data for each counter.
|
||
|
//
|
||
|
// Look for the counter defined for % processor time.
|
||
|
pPerfCounterDefinition = (PPERF_COUNTER_DEFINITION)((PBYTE)pPerfObjectType + pPerfObjectType->HeaderLength);
|
||
|
for (int j = 0; j < (int)pPerfObjectType->NumCounters; j++)
|
||
|
{
|
||
|
if (pPerfCounterDefinition->CounterNameTitleIndex == m_Stats.NtCPUUsage.dwPercentProcessorIndex)
|
||
|
{
|
||
|
// Note: looking at the CounterType filed of the PERF_COUNTER_DEFINITION
|
||
|
// structure shows that the '% Processor Time' counter has the following properties:
|
||
|
// The counter data is a large integer (PERF_SIZE_LARGE set)
|
||
|
// The counter data is an increasing numeric value (PERF_TYPE_COUNTER set)
|
||
|
// The counter value should be divided by the elapsed time (PERF_COUNTER_RATE set)
|
||
|
// The time base units of the 100-nanosecond timer should be used as the base (PERF_TIMER_100NS set)
|
||
|
// The difference between the previous counter value and the current counter value is computed before proceeding (PERF_DELTA_BASE set)
|
||
|
// The display suffix is '%' (PERF_DISPLAY_PERCENT set)
|
||
|
|
||
|
// Save the number of object instances for the CPU counter, as well as the
|
||
|
// starting time.
|
||
|
m_Stats.NtCPUUsage.dwNumProcessors = pPerfObjectType->NumInstances;
|
||
|
if (!(m_Stats.NtCPUUsage.pllCounterValue = (PLONGLONG)LocalAlloc(LMEM_FIXED, m_Stats.NtCPUUsage.dwNumProcessors * sizeof(LONGLONG))))
|
||
|
goto MyError1;
|
||
|
m_Stats.NtCPUUsage.llPerfTime100nSec = *(PLONGLONG)&pPerfDataBlock->PerfTime100nSec;
|
||
|
|
||
|
pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)((PBYTE)pPerfObjectType + pPerfObjectType->DefinitionLength);
|
||
|
for (int k = 0; k < pPerfObjectType->NumInstances; k++)
|
||
|
{
|
||
|
// Get a pointer to the PERF_COUNTER_BLOCK
|
||
|
pPerfCounterBlock = (PPERF_COUNTER_BLOCK)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength);
|
||
|
|
||
|
// This last offset steps us over any other counters to the one we need
|
||
|
m_Stats.NtCPUUsage.pllCounterValue[k] = *(PLONGLONG)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength + pPerfCounterDefinition->CounterOffset);
|
||
|
|
||
|
// Get to the next instance information block
|
||
|
pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength + pPerfCounterBlock->ByteLength);
|
||
|
}
|
||
|
|
||
|
// We're done!
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
pPerfCounterDefinition = (PPERF_COUNTER_DEFINITION)((PBYTE)pPerfCounterDefinition + pPerfCounterDefinition->ByteLength);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
pPerfObjectType = (PPERF_OBJECT_TYPE)((PBYTE)pPerfObjectType + pPerfObjectType->TotalByteLength);
|
||
|
}
|
||
|
|
||
|
// If we get here, we haven't found the counters we were looking for
|
||
|
goto MyError2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Enable the collection of CPU performance data on Win 95-98 by starting the kernel stat server
|
||
|
if (RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StartSrv", NULL, KEY_READ, &m_Stats.hPerfKey) != ERROR_SUCCESS)
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
else
|
||
|
{
|
||
|
DWORD cbData = sizeof(DWORD);
|
||
|
DWORD dwData;
|
||
|
|
||
|
if (RegQueryValueEx(m_Stats.hPerfKey, "KERNEL", NULL, &dwType, (LPBYTE)&dwData, &cbData) != ERROR_SUCCESS)
|
||
|
{
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
|
||
|
// The kernel stat server is now started. Now start the CPUUsage data collection on the kernel stat server.
|
||
|
if (RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StartStat", NULL, KEY_READ, &m_Stats.hPerfKey) != ERROR_SUCCESS)
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
else
|
||
|
{
|
||
|
if (RegQueryValueEx(m_Stats.hPerfKey, "KERNEL\\CPUUsage", NULL, &dwType, (LPBYTE)&dwData, &cbData) != ERROR_SUCCESS)
|
||
|
{
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
|
||
|
// The data and stat servers are now started. Let's get ready to collect actual data.
|
||
|
if (RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StatData", NULL, KEY_READ, &m_Stats.hPerfKey) != ERROR_SUCCESS)
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
|
||
|
MyError2:
|
||
|
if (m_Stats.NtCPUUsage.pllCounterValue)
|
||
|
LocalFree(m_Stats.NtCPUUsage.pllCounterValue);
|
||
|
m_Stats.NtCPUUsage.pllCounterValue = (PLONGLONG)NULL;
|
||
|
MyError1:
|
||
|
if (m_Stats.NtCPUUsage.hPerfData)
|
||
|
{
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
LocalFree(m_Stats.NtCPUUsage.hPerfData);
|
||
|
}
|
||
|
m_Stats.NtCPUUsage.hPerfData = (HANDLE)NULL;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)NULL;
|
||
|
MyError0:
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc EXTERNAL QOSFUNC
|
||
|
*
|
||
|
* @func void | StopCPUUsageCollection | This function does all necessary
|
||
|
* CPU usage data collection cleanup.
|
||
|
*
|
||
|
* @comm This function executes two different code paths: one for NT and one
|
||
|
* for Win95-98.
|
||
|
*
|
||
|
* @devnote MSDN references:
|
||
|
* Microsoft Knowledge Base, Article ID Q174631
|
||
|
* "HOWTO: Access the Performance Registry Under Windows 95"
|
||
|
*
|
||
|
* Microsoft Knowledge Base, Article ID Q107728
|
||
|
* "Retrieving Counter Data from the Registry"
|
||
|
*
|
||
|
* Also, used section "Platform SDK\Windows Base Services\Windows NT Features\Performance Data Helper"
|
||
|
***************************************************************************/
|
||
|
void SendVideoStream::StopCPUUsageCollection(void)
|
||
|
{
|
||
|
DWORD dwType;
|
||
|
DWORD cbData;
|
||
|
|
||
|
if (m_Stats.fWinNT)
|
||
|
{
|
||
|
if (m_Stats.NtCPUUsage.hPerfData)
|
||
|
{
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
LocalFree(m_Stats.NtCPUUsage.hPerfData);
|
||
|
}
|
||
|
m_Stats.NtCPUUsage.hPerfData = (HANDLE)NULL;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)NULL;
|
||
|
if (m_Stats.NtCPUUsage.pllCounterValue)
|
||
|
LocalFree(m_Stats.NtCPUUsage.pllCounterValue);
|
||
|
m_Stats.NtCPUUsage.pllCounterValue = (PLONGLONG)NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (m_Stats.hPerfKey)
|
||
|
{
|
||
|
// Close the data collection key
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
|
||
|
// Stop the CPUUsage data collection on the kernel stat server
|
||
|
if (RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StopStat", 0, KEY_READ, &m_Stats.hPerfKey) == ERROR_SUCCESS)
|
||
|
{
|
||
|
RegQueryValueEx(m_Stats.hPerfKey, "KERNEL\\CPUUsage", NULL, &dwType, NULL, &cbData);
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
}
|
||
|
|
||
|
// Stop the kernel stat server
|
||
|
if (RegOpenKeyEx(HKEY_DYN_DATA, "PerfStats\\StopSrv", 0, KEY_READ, &m_Stats.hPerfKey) == ERROR_SUCCESS)
|
||
|
{
|
||
|
RegQueryValueEx(m_Stats.hPerfKey, "KERNEL", NULL, &dwType, NULL, &cbData);
|
||
|
RegCloseKey(m_Stats.hPerfKey);
|
||
|
}
|
||
|
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc EXTERNAL QOSFUNC
|
||
|
*
|
||
|
* @func void | GetCPUUsage | This function does all necessary
|
||
|
* initialization for CPU usage data collection.
|
||
|
*
|
||
|
* @parm PDWORD | [OUT] pdwOverallCPUUsage | Specifies a pointer to a DWORD to
|
||
|
* receive the current CPU usage.
|
||
|
*
|
||
|
* @rdesc Returns TRUE on success, and FALSE otherwise.
|
||
|
*
|
||
|
* @comm This functions executes two different code paths: one for NT and one
|
||
|
* for Win95-98. Note that we collect data on all CPUs on NT MP machines.
|
||
|
*
|
||
|
* @devnote MSDN references:
|
||
|
* Microsoft Knowledge Base, Article ID Q174631
|
||
|
* "HOWTO: Access the Performance Registry Under Windows 95"
|
||
|
*
|
||
|
* Microsoft Knowledge Base, Article ID Q107728
|
||
|
* "Retrieving Counter Data from the Registry"
|
||
|
*
|
||
|
* Also, used section "Platform SDK\Windows Base Services\Windows NT Features\Performance Data Helper"
|
||
|
***************************************************************************/
|
||
|
BOOL SendVideoStream::GetCPUUsage(PDWORD pdwOverallCPUUsage)
|
||
|
{
|
||
|
|
||
|
PPERF_DATA_BLOCK pPerfDataBlock;
|
||
|
PPERF_OBJECT_TYPE pPerfObjectType;
|
||
|
PPERF_COUNTER_DEFINITION pPerfCounterDefinition;
|
||
|
PPERF_INSTANCE_DEFINITION pPerfInstanceDefinition;
|
||
|
PPERF_COUNTER_BLOCK pPerfCounterBlock;
|
||
|
DWORD dwType;
|
||
|
DWORD cbData = sizeof(DWORD);
|
||
|
DWORD cbTryCounterData;
|
||
|
HANDLE hPerfData;
|
||
|
LONGLONG llDeltaPerfTime100nSec;
|
||
|
LONGLONG llDeltaCPUUsage = (LONGLONG)NULL;
|
||
|
char szProcessorIndex[16];
|
||
|
long lRet;
|
||
|
|
||
|
FX_ENTRY("SendVideoStream::GetCPUUsage");
|
||
|
|
||
|
// We use the handle to the perf key as a way to figure out if we have been initialized correctly
|
||
|
if (m_Stats.hPerfKey && pdwOverallCPUUsage)
|
||
|
{
|
||
|
// Initialize result value
|
||
|
*pdwOverallCPUUsage = 0UL;
|
||
|
|
||
|
if (m_Stats.fWinNT && m_Stats.NtCPUUsage.pbyPerfData)
|
||
|
{
|
||
|
// Make a string out of the processor object index.
|
||
|
_ltoa(m_Stats.NtCPUUsage.dwProcessorIndex, szProcessorIndex, 10);
|
||
|
|
||
|
// Under some cicumstances (cf. Q178887 for details) the RegQueryValueEx
|
||
|
// function may cause an Access Violation because of a buggy performance
|
||
|
// extension DLL such as SQL's.
|
||
|
__try
|
||
|
{
|
||
|
// Read the performance data. Its size may change between each 'registry' access.
|
||
|
cbTryCounterData = m_Stats.NtCPUUsage.cbPerfData;
|
||
|
while((lRet = RegQueryValueEx(HKEY_PERFORMANCE_DATA, szProcessorIndex, NULL, &dwType, m_Stats.NtCPUUsage.pbyPerfData, &cbTryCounterData)) == ERROR_MORE_DATA)
|
||
|
{
|
||
|
m_Stats.NtCPUUsage.cbPerfData += BYTE_INCREMENT;
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
hPerfData = LocalReAlloc(m_Stats.NtCPUUsage.hPerfData, m_Stats.NtCPUUsage.cbPerfData, LMEM_MOVEABLE);
|
||
|
if (!hPerfData)
|
||
|
goto MyError;
|
||
|
m_Stats.NtCPUUsage.hPerfData = hPerfData;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)LocalLock(hPerfData);
|
||
|
cbTryCounterData = m_Stats.NtCPUUsage.cbPerfData;
|
||
|
}
|
||
|
}
|
||
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
||
|
{
|
||
|
ERRORMESSAGE(("%s: Performance Registry Access Violation -> don't use perf counters for CPU measurements\r\n", _fx_));
|
||
|
goto MyError;
|
||
|
}
|
||
|
|
||
|
if (lRet != ERROR_SUCCESS)
|
||
|
goto MyError;
|
||
|
else
|
||
|
{
|
||
|
// Read the PERF_DATA_BLOCK header structure. It describes the system
|
||
|
// and the performance data. The PERF_DATA_BLOCK structure is followed
|
||
|
// by a list of object information blocks (one per object). We use the
|
||
|
// counter index to retrieve object information.
|
||
|
//
|
||
|
// Each object information block contains a PERF_OBJECT_TYPE structure,
|
||
|
// which describes the performance data for the object. Look for the one
|
||
|
// that applies to CPU usage based on its index value.
|
||
|
pPerfDataBlock = (PPERF_DATA_BLOCK)m_Stats.NtCPUUsage.pbyPerfData;
|
||
|
pPerfObjectType = (PPERF_OBJECT_TYPE)(m_Stats.NtCPUUsage.pbyPerfData + pPerfDataBlock->HeaderLength);
|
||
|
for (int i = 0; i < (int)pPerfDataBlock->NumObjectTypes; i++)
|
||
|
{
|
||
|
if (pPerfObjectType->ObjectNameTitleIndex == m_Stats.NtCPUUsage.dwProcessorIndex)
|
||
|
{
|
||
|
// The PERF_OBJECT_TYPE structure is followed by a list of PERF_COUNTER_DEFINITION
|
||
|
// structures, one for each counter defined for the object. The list of PERF_COUNTER_DEFINITION
|
||
|
// structures is followed by a list of instance information blocks (one for each instance).
|
||
|
//
|
||
|
// Each instance information block contains a PERF_INSTANCE_DEFINITION structure and
|
||
|
// a PERF_COUNTER_BLOCK structure, followed by the data for each counter.
|
||
|
//
|
||
|
// Look for the counter defined for % processor time.
|
||
|
pPerfCounterDefinition = (PPERF_COUNTER_DEFINITION)((PBYTE)pPerfObjectType + pPerfObjectType->HeaderLength);
|
||
|
for (int j = 0; j < (int)pPerfObjectType->NumCounters; j++)
|
||
|
{
|
||
|
if (pPerfCounterDefinition->CounterNameTitleIndex == m_Stats.NtCPUUsage.dwPercentProcessorIndex)
|
||
|
{
|
||
|
// Measure elapsed time
|
||
|
llDeltaPerfTime100nSec = *(PLONGLONG)&pPerfDataBlock->PerfTime100nSec - m_Stats.NtCPUUsage.llPerfTime100nSec;
|
||
|
|
||
|
// Save the timestamp for the next round
|
||
|
m_Stats.NtCPUUsage.llPerfTime100nSec = *(PLONGLONG)&pPerfDataBlock->PerfTime100nSec;
|
||
|
|
||
|
pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)((PBYTE)pPerfObjectType + pPerfObjectType->DefinitionLength);
|
||
|
for (int k = 0; k < (int)pPerfObjectType->NumInstances && k < (int)m_Stats.NtCPUUsage.dwNumProcessors; k++)
|
||
|
{
|
||
|
// Get a pointer to the PERF_COUNTER_BLOCK
|
||
|
pPerfCounterBlock = (PPERF_COUNTER_BLOCK)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength);
|
||
|
|
||
|
// Get the CPU usage
|
||
|
llDeltaCPUUsage += *(PLONGLONG)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength + pPerfCounterDefinition->CounterOffset) - m_Stats.NtCPUUsage.pllCounterValue[k];
|
||
|
|
||
|
// Save the value for the next round
|
||
|
m_Stats.NtCPUUsage.pllCounterValue[k] = *(PLONGLONG)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength + pPerfCounterDefinition->CounterOffset);
|
||
|
|
||
|
// Go to the next instance information block
|
||
|
pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)((PBYTE)pPerfInstanceDefinition + pPerfInstanceDefinition->ByteLength + pPerfCounterBlock->ByteLength);
|
||
|
}
|
||
|
|
||
|
// Do a bit of checking on the return value and change its unit to match QoS unit
|
||
|
if ((llDeltaPerfTime100nSec != (LONGLONG)0) && pPerfObjectType->NumInstances)
|
||
|
if ((*pdwOverallCPUUsage = (DWORD)((LONGLONG)1000 - (LONGLONG)1000 * llDeltaCPUUsage / llDeltaPerfTime100nSec / (LONGLONG)pPerfObjectType->NumInstances)) > 1000UL)
|
||
|
{
|
||
|
*pdwOverallCPUUsage = 0UL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// We're done!
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
pPerfCounterDefinition = (PPERF_COUNTER_DEFINITION)((PBYTE)pPerfCounterDefinition + pPerfCounterDefinition->ByteLength);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
pPerfObjectType = (PPERF_OBJECT_TYPE)((PBYTE)pPerfObjectType + pPerfObjectType->TotalByteLength);
|
||
|
}
|
||
|
|
||
|
// If we get here, we haven't found the counters we were looking for
|
||
|
goto MyError;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Do a bit of checking on the return value and change its unit to match QoS unit.
|
||
|
if ((RegQueryValueEx(m_Stats.hPerfKey, "KERNEL\\CPUUsage", NULL, &dwType, (LPBYTE)pdwOverallCPUUsage, &cbData) == ERROR_SUCCESS) && (*pdwOverallCPUUsage > 0) && (*pdwOverallCPUUsage <= 100))
|
||
|
{
|
||
|
*pdwOverallCPUUsage *= 10UL;
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pdwOverallCPUUsage = 0UL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
MyError:
|
||
|
if (m_Stats.NtCPUUsage.pllCounterValue)
|
||
|
LocalFree(m_Stats.NtCPUUsage.pllCounterValue);
|
||
|
m_Stats.NtCPUUsage.pllCounterValue = (PLONGLONG)NULL;
|
||
|
if (m_Stats.NtCPUUsage.hPerfData)
|
||
|
{
|
||
|
LocalUnlock(m_Stats.NtCPUUsage.hPerfData);
|
||
|
LocalFree(m_Stats.NtCPUUsage.hPerfData);
|
||
|
}
|
||
|
m_Stats.NtCPUUsage.hPerfData = (HANDLE)NULL;
|
||
|
m_Stats.NtCPUUsage.pbyPerfData = (PBYTE)NULL;
|
||
|
m_Stats.hPerfKey = (HKEY)NULL;
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL SendVideoStream::SetTargetRates(DWORD dwTargetFrameRate, DWORD dwTargetBitrate)
|
||
|
{
|
||
|
MMRESULT mmr;
|
||
|
ASSERT(m_pVideoFilter);
|
||
|
|
||
|
mmr = m_pVideoFilter->SetTargetRates(dwTargetFrameRate, dwTargetBitrate >> 3);
|
||
|
return (mmr == MMSYSERR_NOERROR);
|
||
|
}
|
||
|
|
||
|
|
||
|
// dwFlags must be one of the following:
|
||
|
// CAPTURE_DIALOG_FORMAT
|
||
|
// CAPTURE_DIALOG_SOURCE
|
||
|
HRESULT __stdcall SendVideoStream::ShowDeviceDialog(DWORD dwFlags)
|
||
|
{
|
||
|
DWORD dwQueryFlags = 0;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
HRESULT hr=DPR_INVALID_PARAMETER;
|
||
|
|
||
|
// the device must be "open" for us to display the dialog box
|
||
|
if (!(m_DPFlags & DPFLAG_CONFIGURED_SEND))
|
||
|
return DPR_NOT_CONFIGURED;
|
||
|
|
||
|
((VideoInControl*)m_InMedia)->GetProp(MC_PROP_VFW_DIALOGS, &dwPropVal);
|
||
|
dwQueryFlags = (DWORD)dwPropVal;
|
||
|
|
||
|
if ((dwQueryFlags & CAPTURE_DIALOG_SOURCE) && (dwFlags & CAPTURE_DIALOG_SOURCE))
|
||
|
{
|
||
|
hr = ((VideoInControl *)m_InMedia)->DisplayDriverDialog(GetActiveWindow(), CAPTURE_DIALOG_SOURCE);
|
||
|
}
|
||
|
else if ((dwQueryFlags & CAPTURE_DIALOG_FORMAT) && (dwFlags & CAPTURE_DIALOG_FORMAT))
|
||
|
{
|
||
|
hr = ((VideoInControl *)m_InMedia)->DisplayDriverDialog(GetActiveWindow(), CAPTURE_DIALOG_FORMAT);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// will set dwFlags to one or more of the following bits
|
||
|
// CAPTURE_DIALOG_FORMAT
|
||
|
// CAPTURE_DIALOG_SOURCE
|
||
|
HRESULT __stdcall SendVideoStream::GetDeviceDialog(DWORD *pdwFlags)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
DWORD_PTR dwPropVal;
|
||
|
|
||
|
hr = ((VideoInControl*)m_InMedia)->GetProp(MC_PROP_VFW_DIALOGS, &dwPropVal);
|
||
|
*pdwFlags = (DWORD)dwPropVal;
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|