I'm writing a Media Foundation app to acquire and display 1920x1080 YUV2 images from a UVC camera at 60Hz.
My problem is that the ReadSample() callback only gets called erratically at a very very low rate (1 FPS or so), in burst of a few frames.
This happens on two laptops, but no desktop I have tried so far. I'm running Windows 10 and all machines I ran tests on are up to date.
But, I have noticed that if I keep the CPU busy with my app, then the callback gets called at 60Hz, as expected.
EDIT
Note: The callback rate also increases when the CPU is busy due to the antivirus kicking-in. Although not to the full 60Hz.
So, if I change my message loop from:
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
to:
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
then the FPS goes back to 60Hz; but of course the CPU usage is nearly 100%...
Ditto, moving the mouse over the window with the 1st message loop causes the FPS to increase a bit (~10FPS).
Dropping the frame rate of the camera to 30Hz causes the ReadSample() callbacks to occur at 30Hz at it should.
I've reproduced the same problem with the MFCaptureD3D example from the examples provided by Microsoft ('Windows-classic-samples').
Note, I slightly modified the example to measure the frame rate in the ReadSample() callback.
On my laptop, the example measures about 25FPS (so a lot of frames dropped). This is because the color space conversion is CPU-based and very inefficient (75% of one core). But, it still manages 25PFS!
Commenting out the conversion (no other code change), causes the frame rate to drop to nearly zero!.. with 0% CPU usage. So the callbacks don't occur.
In both app, the COM library is initialized in the main thread of the app (running the message loop of the window) as follows:
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
I copy the ReadSample() callback from Microsoft's example for convenience:
HRESULT CPreview::OnReadSample(HRESULT hrStatus, DWORD /* dwStreamIndex */, DWORD /* dwStreamFlags */, LONGLONG /* llTimestamp */, IMFSample *pSample)
{
HRESULT hr = S_OK;
IMFMediaBuffer *pBuffer = NULL;
EnterCriticalSection(&m_critsec);
if (FAILED(hrStatus))
hr = hrStatus;
if (SUCCEEDED(hr)) {
if (pSample) {
// Get the video frame buffer from the sample.
hr = pSample->GetBufferByIndex(0, &pBuffer);
// Draw the frame.
if (SUCCEEDED(hr))
hr = m_draw.DrawFrame(pBuffer);
}
rate.Inc();
}
// Request the next frame.
if (SUCCEEDED(hr))
hr = m_pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
NULL, // actual
NULL, // flags
NULL, // timestamp
NULL // sample
);
if (FAILED(hr))
NotifyError(hr);
SafeRelease(&pBuffer);
LeaveCriticalSection(&m_critsec);
return hr;
}
EDIT
Window procedure shown below (simplified it as much as possible); the MF objects are created in OnCreate() :
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Wondering if it's related to the COM threading model; I don't understand the note at the bottom of the page.
The multi-threaded apartment is intended for use by non-GUI threads. Threads in multi-threaded apartments should not perform UI actions. This is because UI threads require a message pump, and COM does not pump messages for threads in a multi-threaded apartment.
Does this mean I shouldn't create the COM objects in my UI thread?
I have tried creating them in another thread, with its own msg loop, but got exactly the same result.
EDIT Have run tests with VLC, which is based on DirectShow and seen none of those problems. It works fine under Win7 and FPS is 60Hz as expected. Have noticed that VLC drops the timer interrupt to 1ms (timeBeginPeriod()). Have tried doing the same in my, to no avail.
Running out of ideas here... looks like I may have to drop MF and write a DirectShow app if only to double check that it does work properly.