📜 ⬆️ ⬇️

Video flicker when using Qt widget and Directshow

I am currently developing a video player for Windows. And it “stuck” for a while over the task - after switching to Qt, the video in the player starts to blink and disappear (see video).

Attempts to redefine QWidget :: paintEvent are impossible because Qt fills (https://qt-project.org/doc/qt-4.8/qwidget.html#autoFillBackground-prop) before QWidget :: paintEvent.
An attempt to redefine WM_PAINT and WM_ERASEBACKGOUND in QWidget :: winEvent also failed, because paintEvent can be called not only from WM_PAINT, but also by other services using an algorithm unknown to me.
Therefore, below I will give a solution how to get out of this situation.


So, the solution:
I decided not to reinvent the wheel, but to use native widgets with Qt. The native widget itself will be inside a QWidget. Schematically in the player window it can be represented as follows:

First, create a handler for windows. It will take over the registration of the window class and forwarding messages to the widgets.
It is also necessary to prohibit QApplication from processing messages for native widgets. Our project uses libqxt, so you need to add a filter using QxtApplication :: installNativeEventFilter. Another option is to override QCoreApplication :: winEventFilter.

To begin, I wrote the WindowProcMapper class to map the HWND to an object.
nativewidgetimpl.h
namespace Native { class NativeWndFilter : public QxtNativeEventFilter { public: inline NativeWndFilter() { } inline void insert(HWND h) { m_wnds.insert(h); } inline void remove(HWND h) { m_wnds.remove(h); } inline bool contains(HWND h) { return m_wnds.contains(h); } bool winEventFilter(MSG * msg, long *result) override; private: QSet<HWND> m_wnds; }; template<typename T> class WindowProcMapper { public: WindowProcMapper(const wchar_t *className); ~WindowProcMapper(); inline T *getWindow(HWND hwnd) const; inline void insertWindow(HWND hwnd, T *ptr); inline void removeWindow(HWND hwnd); ATOM getRegisterResult(); bool registerWindowClass(); inline static WindowProcMapper<T> *instance() { return self; } private: QHash<HWND, T*> m_hash; LPCWSTR m_className; ATOM m_registerResult; static WindowProcMapper *self; static LRESULT CALLBACK WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); NativeWndFilter *m_filter; }; } 

Create a wrapper for the native widget:
nativewidget.h
 namespace Native { struct NativeWidgetPrivate; class NativeWidget : public QWidget { Q_OBJECT public: NativeWidget(QWidget *parent = nullptr); NativeWidget(const QString &wndName, QWidget *parent = nullptr); ~NativeWidget(); WId nativeHWND() const; HDC getNativeDC() const; void releaseNativeDC(HDC hdc) const; QString wndName() const; protected: NativeWidget(NativeWidgetPrivate *p, const QString &wndName, QWidget *parent = nullptr); void paintEvent(QPaintEvent *ev) override; void resizeEvent(QResizeEvent *ev) override; virtual bool nativeWinEvent(MSG *msg, long *result); QScopedPointer<NativeWidgetPrivate> d_ptr; private: void init(); Q_DECLARE_PRIVATE(NativeWidget); }; void NativeWidget::paintEvent(QPaintEvent *ev) { Q_D(NativeWidget); SendMessage(d->m_hwnd, WM_PAINT, 0, 0); ev->accept(); } void NativeWidget::resizeEvent(QResizeEvent *ev) { Q_D(NativeWidget); const QSize &sz = ev->size(); SetWindowPos(d->m_hwnd, NULL, 0, 0, sz.width(), sz.height(), 0); QWidget::resizeEvent(ev); } } 

')
For the behavior of the native widget we override the functions resizeEvent and paintEvent. They will send events to the native widget when the QWidget changes.
The private class will take over the responsibility of managing the native widget. It accepts messages using the windowsEvent method and passes them to NativeWidget :: nativeWinEvent, which can be easily redefined in descendant classes.
 namespace Native { struct NativeWidgetPrivate { NativeWidgetPrivate(NativeWidget *q); ~NativeWidgetPrivate(); inline HWND winID() const; HDC getDC() const; void releaseDC(HDC hdc) const; QRect windowPlacement() const; virtual bool createWindow(QWidget *parent); void sendEvent(QEvent *ev); inline bool isCreating() const { return m_creating; } LRESULT windowsEvent(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam); HWND m_hwnd; mutable HDC m_hdc; bool m_creating; void sendToWidget(uint msg, WPARAM wparam, LPARAM lparam); QString m_wndName; NativeWidget *const q_ptr; Q_DECLARE_PUBLIC(NativeWidget); }; } 

To implement a video widget, you need to intercept the WM_PAINT and WM_ERASEBACKGROUND messages.
An example implementation is shown below:
 bool MovieScreen::nativeWinEvent(MSG *msg, long *result) { Q_D(MovieScreen); //qDebug() << __FUNCTION__; switch (msg->message) { case WM_ERASEBKGND: case WM_PAINT: d->updateMovie(); break; case WM_SHOWWINDOW: { auto r = NativeWidget::nativeWinEvent(msg, result); if (msg->wParam == TRUE) d->updateMovie(); return r; } case WM_SIZE: case WM_MOVE: case WM_MOVING: case WM_SIZING: { auto r = NativeWidget::nativeWinEvent(msg, result); d->resizeVideo(); return r; } } return NativeWidget::nativeWinEvent(msg, result); } 

Sample implementation:
 IVMRWindowlessControl9 *m_pVideoRenderer9; MovieScreenPrivate:: updateMovie() { If (isPaused()) { HDC hdc = getHDC(); m_pVideoRenderer9->Repaint_Video(winID(), hdc); releaseHDC(hdc); } } MovieScreenPrivate::resizeVideo() { long lWidth, lHeight; HRESULT hr = m_pVideoRenderer9->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL); if (SUCCEEDED(hr)) { RECT rcSrc, rcDest; // Set the source rectangle. SetRect(&rcSrc, 0, 0, lWidth, lHeight); // Get the window client area. GetClientRect(winID(), &rcDest); // Set the destination rectangle. SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom); m_pVideoRenderer9->SetVideoPosition(&rcSrc, &rcDest); } } 

Well, in the end we get:

Sources can be downloaded here.

Source: https://habr.com/ru/post/172869/


All Articles