Flickering video when using Qt widget and Directshow

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

    Attempts to override QWidget :: paintEvent are not possible because Qt fills in (https://qt-project.org/doc/qt-4.8/qwidget.html#autoFillBackground-prop) before QWidget :: paintEvent.
    An attempt to override 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, I will give a solution below how I got 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 the QWidget. Schematically, in the player’s window, this can be represented as follows:

    First, create a window handler. He will take care of registering the window class and forwarding messages to widgets.
    You must also prevent 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 start, I wrote the WindowProcMapper class in order to map 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 m_wnds;
    	};
    	template
    	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 *instance()
    		{ return self; }
    	private:
    		QHash 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 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 redefine the resizeEvent and paintEvent functions. They will send events to the native widget when the QWidget changes.
    The private class will take on the responsibility of managing the native widget. It accepts messages using the windowsEvent method and passes them to NativeWidget :: nativeWinEvent, which can be easily overridden in derived 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 the video widget, it is necessary to intercept the WM_PAINT and WM_ERASEBACKGROUND messages.
    An example implementation is given 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.

    Also popular now: