Мерцание видео при использовании Qt widget и Directshow

в 8:31, , рубрики: directshow, qt, WinAPI, видео, видеоплеер, Программирование, Работа с видео, метки: , , , ,

Занимаюсь сейчас разработкой проигрывателя видео под Windows. И «завис» на некоторое время над задачей — после перехода на Qt, видео в проигрывателе начинает моргать и исчезать (см. видео).

Попытки переопределить QWidget::paintEvent невозможны из-за того, что Qt выполняет заливку на (https://qt-project.org/doc/qt-4.8/qwidget.html#autoFillBackground-prop) перед QWidget::paintEvent.
Попытка переопределить WM_PAINT и WM_ERASEBACKGOUND в QWidget::winEvent тоже не удалась, т.к. paintEvent может вызываться не только из WM_PAINT, но и другими сервисами по неизвестному мне алгоритму.
Поэтому ниже приведу решение как выходил из этой ситуации.

Итак, решение:
Решил не изобретать велосипед, а использовать нативные виджеты вместе с Qt. Сам нативный виджет будет находиться внутри QWidget. Схематично в окне плеера это можно представить так:
Мерцание видео при использовании Qt widget и Directshow
Сначала создаем обработчик для окон. Он возьмет на себя регистрацию класса окна и пересылку сообщений виджетам.
Также необходимо запретить QApplication обрабатывать сообщения для нативных виджетов. У нас в проекте используется libqxt, поэтому необходимо добавить фильтр с помощью QxtApplication:: installNativeEventFilter. Другой вариант – переопределить QCoreApplication::winEventFilter.

Для начала я написал класс WindowProcMapper для того, чтобы сопоставить HWND с объектом.
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;
	};
}

Создаем обертку для нативного виджета:
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);
	}

 
}

Для поведения нативного виджета переопределяем функции resizeEvent и paintEvent. Они будут пересылать события в нативный виджет при изменении QWidget.
Приватный класс возьмет на себя обязанности по управлению нативным виджетом. Он принимает сообщения с помощью метода windowsEvent и, передает их NativeWidget:: nativeWinEvent, который может быть легко переопределен в классах-наследниках.

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

Для реализации виджета с видео необходимо перехватить сообщения WM_PAINT и WM_ERASEBACKGROUND.
Примерная реализация приведена ниже:

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

Примерная реализация:

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

}

Ну вот, в итоге получаем:

Исходники можно скачать здесь

Автор: Santori

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js