// returns the "range" of fps, etc. for this index HRESULT STDMETHODCALLTYPE CPushPinDesktop::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC) { CAutoLock cAutoLock(m_pFilter->pStateLock()); HRESULT hr = GetMediaType(iIndex, &m_mt); // ensure setup/re-use m_mt ... // some are indeed shared, apparently. if(FAILED(hr)) { return hr; } *pmt = CreateMediaType(&m_mt); // a windows lib method, also does a copy for us if (*pmt == NULL) return E_OUTOFMEMORY; DECLARE_PTR(VIDEO_STREAM_CONFIG_CAPS, pvscc, pSCC); /* most of these are listed as deprecated by msdn... yet some still used, apparently. odd. */ pvscc->VideoStandard = AnalogVideo_None; pvscc->InputSize.cx = getCaptureDesiredFinalWidth(); pvscc->InputSize.cy = getCaptureDesiredFinalHeight(); // most of these values are fakes.. pvscc->MinCroppingSize.cx = getCaptureDesiredFinalWidth(); pvscc->MinCroppingSize.cy = getCaptureDesiredFinalHeight(); pvscc->MaxCroppingSize.cx = getCaptureDesiredFinalWidth(); pvscc->MaxCroppingSize.cy = getCaptureDesiredFinalHeight(); pvscc->CropGranularityX = 1; pvscc->CropGranularityY = 1; pvscc->CropAlignX = 1; pvscc->CropAlignY = 1; pvscc->MinOutputSize.cx = 1; pvscc->MinOutputSize.cy = 1; pvscc->MaxOutputSize.cx = getCaptureDesiredFinalWidth(); pvscc->MaxOutputSize.cy = getCaptureDesiredFinalHeight(); pvscc->OutputGranularityX = 1; pvscc->OutputGranularityY = 1; pvscc->StretchTapsX = 1; // We do 1 tap. I guess... pvscc->StretchTapsY = 1; pvscc->ShrinkTapsX = 1; pvscc->ShrinkTapsY = 1; pvscc->MinFrameInterval = m_rtFrameLength; // the larger default is actually the MinFrameInterval, not the max pvscc->MaxFrameInterval = 500000000; // 0.02 fps :) [though it could go lower, really...] pvscc->MinBitsPerSecond = (LONG) 1*1*8*GetFps(); // if in 8 bit mode 1x1. I guess. pvscc->MaxBitsPerSecond = (LONG) getCaptureDesiredFinalWidth()*getCaptureDesiredFinalHeight()*32*GetFps() + 44; // + 44 header size? + the palette? return hr; }
// // GetMediaType // // Prefer 5 formats - 8, 16 (*2), 24 or 32 bits per pixel // // Prefered types should be ordered by quality, with zero as highest quality. // Therefore, iPosition = // 0 Return a 24bit mediatype "as the default" since I guessed it might be faster though who knows // 1 Return a 24bit mediatype // 2 Return 16bit RGB565 // 3 Return a 16bit mediatype (rgb555) // 4 Return 8 bit palettised format // >4 Invalid // except that we changed the orderings a bit... // HRESULT CPushPinDesktop::GetMediaType(int iPosition, CMediaType *pmt) // AM_MEDIA_TYPE basically == CMediaType { CheckPointer(pmt, E_POINTER); CAutoLock cAutoLock(m_pFilter->pStateLock()); if(m_bFormatAlreadySet) { // you can only have one option, buddy, if setFormat already called. (see SetFormat's msdn) if(iPosition != 0) return E_INVALIDARG; VIDEOINFO *pvi = (VIDEOINFO *) m_mt.Format(); // Set() copies these in for us pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader); // calculates the size for us, after we gave it the width and everything else we already chucked into it // pmt->SetSampleSize(pvi->bmiHeader.biSizeImage); // nobody uses sample size anyway :P pmt->Set(m_mt); VIDEOINFOHEADER *pVih1 = (VIDEOINFOHEADER*) m_mt.pbFormat; VIDEOINFO *pviHere = (VIDEOINFO *) pmt->pbFormat; return S_OK; } // do we ever even get past here? hmm if(iPosition < 0) return E_INVALIDARG; // Have we run out of types? if(iPosition > 6) return VFW_S_NO_MORE_ITEMS; VIDEOINFO *pvi = (VIDEOINFO *) pmt->AllocFormatBuffer(sizeof(VIDEOINFO)); if(NULL == pvi) return(E_OUTOFMEMORY); // Initialize the VideoInfo structure before configuring its members ZeroMemory(pvi, sizeof(VIDEOINFO)); if(iPosition == 0) { // pass it our "preferred" which is 16 bits...I guess...haven't really researched it, but do want it to have a consistent default. iPosition = 3; // 32 -> 24 (2): getdibits took 2.251ms // 32 -> 32 (1): getdibits took 2.916ms // except those numbers might be misleading in terms of total speed...hmm... } switch(iPosition) { case 1: { // 32bit format // Since we use RGB888 (the default for 32 bit), there is // no reason to use BI_BITFIELDS to specify the RGB // masks [sometimes even if you don't have enough bits you don't need to anyway?] // Also, not everything supports BI_BITFIELDS ... pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 32; break; } case 2: { // Return our 24bit format, same as above comments pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 24; break; } case 3: { // 16 bit per pixel RGB565 BI_BITFIELDS // Place the RGB masks as the first 3 doublewords in the palette area for(int i = 0; i < 3; i++) pvi->TrueColorInfo.dwBitMasks[i] = bits565[i]; pvi->bmiHeader.biCompression = BI_BITFIELDS; pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 16; break; } case 4: { // 16 bits per pixel RGB555 // Place the RGB masks as the first 3 doublewords in the palette area for(int i = 0; i < 3; i++) pvi->TrueColorInfo.dwBitMasks[i] = bits555[i]; // LODO ??? need? not need? BI_BITFIELDS? Or is this the default so we don't need it? Or do we need a different type that doesn't specify BI_BITFIELDS? pvi->bmiHeader.biCompression = BI_BITFIELDS; pvi->bmiHeader.biBitCount = 16; break; } case 5: { // 8 bit palettised pvi->bmiHeader.biCompression = BI_RGB; pvi->bmiHeader.biBitCount = 8; pvi->bmiHeader.biClrUsed = iPALETTE_COLORS; break; } case 6: { // the i420 freak-o pvi->bmiHeader.biCompression = FOURCC_I420; // who knows if this is right LOL pvi->bmiHeader.biBitCount = 12; pvi->bmiHeader.biSizeImage = (getCaptureDesiredFinalWidth()*getCaptureDesiredFinalHeight()*3)/2; pmt->SetSubtype(&WMMEDIASUBTYPE_I420); break; } } // Now adjust some parameters that are the same for all formats pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pvi->bmiHeader.biWidth = getCaptureDesiredFinalWidth(); pvi->bmiHeader.biHeight = getCaptureDesiredFinalHeight(); pvi->bmiHeader.biPlanes = 1; if(pvi->bmiHeader.biSizeImage == 0) pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader); // calculates the size for us, after we gave it the width and everything else we already chucked into it pmt->SetSampleSize(pvi->bmiHeader.biSizeImage); // use the above size pvi->bmiHeader.biClrImportant = 0; pvi->AvgTimePerFrame = m_rtFrameLength; // from our config or default SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered. SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle pmt->SetType(&MEDIATYPE_Video); pmt->SetFormatType(&FORMAT_VideoInfo); pmt->SetTemporalCompression(FALSE); // Work out the GUID for the subtype from the header info. if(*pmt->Subtype() == GUID_NULL) { const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader); pmt->SetSubtype(&SubTypeGUID); } return NOERROR; } // GetMediaType
// the default child constructor... CPushPinDesktop::CPushPinDesktop(HRESULT *phr, CPushSourceDesktop *pFilter) : CSourceStream(NAME("Push Source CPushPinDesktop child/pin"), phr, pFilter, L"Capture"), m_bReReadRegistry(0), m_bDeDupe(0), m_iFrameNumber(0), pOldData(NULL), m_bConvertToI420(false), m_pParent(pFilter), m_bFormatAlreadySet(false), hRawBitmap(NULL), m_bUseCaptureBlt(false), previousFrameEndTime(0) { // Get the device context of the main display, just to get some metrics for it... globalStart = GetTickCount(); m_iHwndToTrack = (HWND) read_config_setting(TEXT("hwnd_to_track"), NULL, false); if(m_iHwndToTrack) { LocalOutput("using specified hwnd no decoration: %d", m_iHwndToTrack); hScrDc = GetDC(m_iHwndToTrack); // using GetDC here seemingly allows you to capture "just a window" without decoration m_bHwndTrackDecoration = false; } else { m_iHwndToTrack = (HWND) read_config_setting(TEXT("hwnd_to_track_with_window_decoration"), NULL, false); if(m_iHwndToTrack) { LocalOutput("using specified hwnd with decoration: %d", m_iHwndToTrack); hScrDc = GetWindowDC(m_iHwndToTrack); m_bHwndTrackDecoration = true; } else { int useForeGroundWindow = read_config_setting(TEXT("capture_foreground_window_if_1"), 0, true); if(useForeGroundWindow) { LocalOutput("using foreground window %d", GetForegroundWindow()); hScrDc = GetDC(GetForegroundWindow()); } else { // the default, just capture desktop // hScrDc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL); // possibly better than GetDC(0), supposed to be multi monitor? // LocalOutput("using the dangerous CreateDC DISPLAY\n"); // danger, CreateDC DC is only good as long as this particular thread is still alive...hmm...is it better for directdraw hScrDc = GetDC(NULL); } } } //m_iScreenBitDepth = GetTrueScreenDepth(hScrDc); ASSERT_RAISE(hScrDc != 0); // 0 implies failure... [if using hwnd, can mean the window is gone!] // Get the dimensions of the main desktop window as the default m_rScreen.left = m_rScreen.top = 0; m_rScreen.right = GetDeviceCaps(hScrDc, HORZRES); // NB this *fails* for dual monitor support currently... but we just get the wrong width by default, at least with aero windows 7 both can capture both monitors m_rScreen.bottom = GetDeviceCaps(hScrDc, VERTRES); // now read some custom settings... WarmupCounter(); if(!m_iHwndToTrack) { reReadCurrentStartXY(0); } else { LocalOutput("ignoring startx, starty since hwnd was specified"); } int config_width = read_config_setting(TEXT("capture_width"), 0, false); ASSERT_RAISE(config_width >= 0); // negatives not allowed... int config_height = read_config_setting(TEXT("capture_height"), 0, false); ASSERT_RAISE(config_height >= 0); // negatives not allowed, if it's set :) if(config_width > 0) { int desired = m_rScreen.left + config_width; //int max_possible = m_rScreen.right; // disabled check until I get dual monitor working. or should I allow off screen captures anyway? //if(desired < max_possible) m_rScreen.right = desired; //else // m_rScreen.right = max_possible; } else { // leave full screen } m_iCaptureConfigWidth = m_rScreen.right - m_rScreen.left; ASSERT_RAISE(m_iCaptureConfigWidth > 0); if(config_height > 0) { int desired = m_rScreen.top + config_height; //int max_possible = m_rScreen.bottom; // disabled, see above. //if(desired < max_possible) m_rScreen.bottom = desired; //else // m_rScreen.bottom = max_possible; } else { // leave full screen } m_iCaptureConfigHeight = m_rScreen.bottom - m_rScreen.top; ASSERT_RAISE(m_iCaptureConfigHeight > 0); m_iStretchToThisConfigWidth = read_config_setting(TEXT("stretch_to_width"), 0, false); m_iStretchToThisConfigHeight = read_config_setting(TEXT("stretch_to_height"), 0, false); m_iStretchMode = read_config_setting(TEXT("stretch_mode_high_quality_if_1"), 0, true); // guess it's either stretch mode 0 or 1 ASSERT_RAISE(m_iStretchToThisConfigWidth >= 0 && m_iStretchToThisConfigHeight >= 0 && m_iStretchMode >= 0); // sanity checks m_bUseCaptureBlt = read_config_setting(TEXT("capture_transparent_windows_including_mouse_in_non_aero_if_1_causes_annoying_mouse_flicker"), 0, true) == 1; m_bCaptureMouse = read_config_setting(TEXT("capture_mouse_default_1"), 1, true) == 1; // default 30 fps...hmm... int config_max_fps = read_config_setting(TEXT("default_max_fps"), 30, false); // TODO allow floats [?] when ever requested ASSERT_RAISE(config_max_fps > 0); // m_rtFrameLength is also re-negotiated later... m_rtFrameLength = UNITS / config_max_fps; if(is_config_set_to_1(TEXT("track_new_x_y_coords_each_frame_if_1"))) { m_bReReadRegistry = 1; // takes 0.416880ms, but I thought it took more when I made it off by default :P } if(is_config_set_to_1(TEXT("dedup_if_1"))) { m_bDeDupe = 1; // takes 10 or 20ms...but useful to me! :) } m_millisToSleepBeforePollForChanges = read_config_setting(TEXT("millis_to_sleep_between_poll_for_dedupe_changes"), 10, true); wchar_t out[10000]; swprintf(out, 10000, L"default/from reg read config as: %dx%d -> %dx%d (%d top %d bottom %d l %d r) %dfps, dedupe? %d, millis between dedupe polling %d, m_bReReadRegistry? %d hwnd:%d \n", m_iCaptureConfigHeight, m_iCaptureConfigWidth, getCaptureDesiredFinalHeight(), getCaptureDesiredFinalWidth(), m_rScreen.top, m_rScreen.bottom, m_rScreen.left, m_rScreen.right, config_max_fps, m_bDeDupe, m_millisToSleepBeforePollForChanges, m_bReReadRegistry, m_iHwndToTrack); // warmup the debugging message system __int64 measureDebugOutputSpeed = StartCounter(); LocalOutput(out); LocalOutput("writing a large-ish debug itself took: %.02Lf ms", GetCounterSinceStartMillis(measureDebugOutputSpeed)); set_config_string_setting(L"last_init_config_was", out); }
// sets fps, size, (etc.) maybe, or maybe just saves it away for later use... HRESULT STDMETHODCALLTYPE CPushPinDesktop::SetFormat(AM_MEDIA_TYPE *pmt) { CAutoLock cAutoLock(m_pFilter->pStateLock()); // I *think* it can go back and forth, then. You can call GetStreamCaps to enumerate, then call // SetFormat, then later calls to GetMediaType/GetStreamCaps/EnumMediatypes will all "have" to just give this one // though theoretically they could also call EnumMediaTypes, then SetMediaType, and not call SetFormat // does flash call both? what order for flash/ffmpeg/vlc calling both? // LODO update msdn // "they" [can] call this...see msdn for SetFormat // NULL means reset to default type... if(pmt != NULL) { if(pmt->formattype != FORMAT_VideoInfo) // same as {CLSID_KsDataTypeHandlerVideo} return E_FAIL; // LODO I should do more here...http://msdn.microsoft.com/en-us/library/dd319788.aspx I guess [meh] // LODO should fail if we're already streaming... [?] VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pmt->pbFormat; if(CheckMediaType((CMediaType *) pmt) != S_OK) { return E_FAIL; // just in case :P [did skype get here once?] } // for FMLE's benefit, only accept a setFormat of our "final" width [force setting via registry I guess, otherwise it only shows 80x60 whoa!] // flash media live encoder uses setFormat to determine widths [?] and then only displays the smallest? oh man that is messed up if( pvi->bmiHeader.biWidth != getCaptureDesiredFinalWidth() || pvi->bmiHeader.biHeight != getCaptureDesiredFinalHeight()) { return E_INVALIDARG; } // ignore other things like cropping requests for now... // now save it away...for being able to re-offer it later. We could use SetMediaType but we're just being lazy and re-using m_mt for many things I guess m_mt = *pmt; // The frame rate at which your filter should produce data is determined by the AvgTimePerFrame field of VIDEOINFOHEADER if(pvi->AvgTimePerFrame) m_rtFrameLength = pvi->AvgTimePerFrame; // allow them to set whatever fps they request, i.e. if it's less than the max default. VLC command line can specify this, for instance... // also setup scaling here, as WFMLE and ffplay and VLC all get here... m_rScreen.right = m_rScreen.left + pvi->bmiHeader.biWidth; // allow them to set whatever "scaling size" they want [set m_rScreen is negotiated right here] m_rScreen.bottom = m_rScreen.top + pvi->bmiHeader.biHeight; } IPin* pin; ConnectedTo(&pin); if(pin) { IFilterGraph *pGraph = m_pParent->GetGraph(); HRESULT res = pGraph->Reconnect(this); if(res != S_OK) // LODO check first, and then just re-use the old one? return res; // else return early...not really sure how to handle this...since we already set m_mt...but it's a pretty rare case I think... // plus ours is a weird case... } else { // graph hasn't been built yet... // so we're ok with "whatever" format they pass us, we're just in the setup phase... } // success of some type if(pmt == NULL) { m_bFormatAlreadySet = false; } else { m_bFormatAlreadySet = true; } return S_OK; }
// the default child constructor... CPushPinDesktop::CPushPinDesktop(HRESULT *phr, CPushSourceDesktop *pFilter) : CSourceStream(NAME("Push Source CPushPinDesktop child/pin"), phr, pFilter, L"Capture"), m_FramesWritten(0), m_bReReadRegistry(0), m_bDeDupe(0), m_iFrameNumber(0), pOldData(NULL), m_bConvertToI420(false), //m_nCurrentBitDepth(32), // negotiated later... m_pParent(pFilter), m_bFormatAlreadySet(false), hRawBitmap(NULL) { // Get the device context of the main display, just to get some metrics for it... globalStart = GetTickCount(); m_iHwndToTrack = (HWND) read_config_setting(TEXT("hwnd_to_track"), NULL); hScrDc = GetDC(m_iHwndToTrack); m_iScreenBitDepth = GetTrueScreenDepth(hScrDc); ASSERT(hScrDc != 0); GdiSetBatchLimit(1); // disable any GDI...just in case this helps anybody... // Get the dimensions of the main desktop window as the default m_rScreen.left = m_rScreen.top = 0; m_rScreen.right = GetDeviceCaps(hScrDc, HORZRES); // NB this *fails* for dual monitor support currently... but we just get the wrong width by default, at least with aero windows 7 both can capture both monitors m_rScreen.bottom = GetDeviceCaps(hScrDc, VERTRES); // now read some custom settings... WarmupCounter(); reReadCurrentPosition(0); int config_width = read_config_setting(TEXT("capture_width"), 0); ASSERT(config_width >= 0); // negatives not allowed... int config_height = read_config_setting(TEXT("capture_height"), 0); ASSERT(config_height >= 0); // negatives not allowed, if it's set :) if(config_width > 0) { int desired = m_rScreen.left + config_width; //int max_possible = m_rScreen.right; // disabled check until I get dual monitor working. or should I allow off screen captures anyway? //if(desired < max_possible) m_rScreen.right = desired; //else // m_rScreen.right = max_possible; } else { // leave full screen } m_iCaptureConfigWidth = m_rScreen.right - m_rScreen.left; ASSERT(m_iCaptureConfigWidth > 0); if(config_height > 0) { int desired = m_rScreen.top + config_height; //int max_possible = m_rScreen.bottom; // disabled, see above. //if(desired < max_possible) m_rScreen.bottom = desired; //else // m_rScreen.bottom = max_possible; } else { // leave full screen } m_iCaptureConfigHeight = m_rScreen.bottom - m_rScreen.top; ASSERT(m_iCaptureConfigHeight > 0); m_iStretchToThisConfigWidth = read_config_setting(TEXT("stretch_to_width"), 0); m_iStretchToThisConfigHeight = read_config_setting(TEXT("stretch_to_height"), 0); m_iStretchMode = read_config_setting(TEXT("stretch_mode_high_quality_if_1"), 0); ASSERT(m_iStretchToThisConfigWidth >= 0 && m_iStretchToThisConfigHeight >= 0 && m_iStretchMode >= 0); // sanity checks // default 30 fps...hmm... int config_max_fps = read_config_setting(TEXT("default_max_fps"), 30); // TODO allow floats [?] when ever requested ASSERT(config_max_fps >= 0); // m_rtFrameLength is also re-negotiated later... m_rtFrameLength = UNITS / config_max_fps; if(is_config_set_to_1(TEXT("track_new_x_y_coords_each_frame_if_1"))) { m_bReReadRegistry = 1; // takes 0.416880ms, but I thought it took more when I made it off by default :P } if(is_config_set_to_1(TEXT("dedup_if_1"))) { m_bDeDupe = 1; // takes 10 or 20ms...but useful to me! :) } m_millisToSleepBeforePollForChanges = read_config_setting(TEXT("millis_to_sleep_between_poll_for_dedupe_changes"), 10); wchar_t out[1000]; swprintf(out, 1000, L"default/from reg read config as: %dx%d -> %dx%d (%dtop %db %dl %dr) %dfps, dedupe? %d, millis between dedupe polling %d, m_bReReadRegistry? %d \n", m_iCaptureConfigHeight, m_iCaptureConfigWidth, getCaptureDesiredFinalHeight(), getCaptureDesiredFinalWidth(), m_rScreen.top, m_rScreen.bottom, m_rScreen.left, m_rScreen.right, config_max_fps, m_bDeDupe, m_millisToSleepBeforePollForChanges, m_bReReadRegistry); LocalOutput(out); // warmup for the below debug :) __int64 measureDebugOutputSpeed = StartCounter(); LocalOutput(out); LocalOutput("writing a large-ish debug itself took: %.0Lf ms", GetCounterSinceStartMillis(measureDebugOutputSpeed)); // does this work with flash? set_config_string_setting(L"last_init_config_was", out); }