Windows-Server-2003/enduser/netmeeting/av/nac/vidstrm.cpp

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;
}