Windows平台RTMP|RTSP播放器为什么要兼容GDI绘制

202 阅读2分钟

为什么要支持GDI

先说结论,Windows平台播放渲染这块,一般来说99%以上的机器都是支持D3D的,实现GDI模式绘制,除了为了好的兼容性外,在远程连接的场景下,D3D创建不成功,需要使用GDI模式。

简单来说,Windows平台的RTMP播放器或RTSP播放器,设计如果系统支持D3D,优先D3D,如果检测到不支持D3D,数据回调上来,GDI模式绘制。

在之前的博客,我们提到过:D3D绘制出来的图像效果更细腻,绘制效率也更高,CPU占用相对GDI更低

上图以1920*1080分辨率、30帧、固定码率(采集屏幕左侧区域)为例,通过大牛直播SDK ( github) 的Windows平台SmartPublisherDemo.exe工具推送到内网nginx服务器,然后分别以D3D模式和GDI模式拉流(播放端缓冲设置为0)。

可以看到:

D3D模式,CPU占用只有2.7%,延迟:249-156 = 93ms;

GDI模式,CPU占用19.5%,延迟249-73 = 176ms。

无论是从延迟和CPU占用上看,D3D模式都占优。

实现思路:

以C++的demo为例:

1. 先检测系统是否支持D3D模式:

		if ( NT_ERC_OK == player_api_.IsSupportD3DRender(player_handle_,
			wrapper_render_wnd_.RenderWnd(), &in_support_d3d_render))
		{
			if ( 1 == in_support_d3d_render )
			{
				is_support_d3d_render = true;
			}
		}

2. 如不支持D3D,数据回到上层,做绘制:

		if ( is_support_d3d_render )
		{
			is_gdi_render_ = false;

			// 支持d3d绘制的话,就用D3D绘制
			player_api_.SetRenderWindow(player_handle_, wrapper_render_wnd_.RenderWnd());

			player_api_.SetRenderScaleMode(player_handle_, btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);
		}
		else
		{
			is_gdi_render_ = true;

			// 不支持D3D就让播放器吐出数据来,用GDI绘制

			wrapper_render_wnd_.SetRenderScaleMode(btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);

			player_api_.SetVideoFrameCallBack(player_handle_, NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,
				GetSafeHwnd(), SM_SDKVideoFrameHandle);
		}
extern "C" NT_VOID NT_CALLBACK SM_SDKVideoFrameHandle(NT_HANDLE handle, NT_PVOID userData, NT_UINT32 status,
	const NT_SP_VideoFrame* frame)
{
	/*if (frame != NULL)
	{
	std::ostringstream ss;
	ss << "Receive frame time_stamp:" << frame->timestamp_ << "ms" << "\r\n";
	OutputDebugStringA(ss.str().c_str());
	}*/

	if ( frame != NULL )
	{
		if ( NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 == frame->format_
			&& frame->plane0_ != NULL
			&& frame->stride0_ > 0
			&& frame->height_ > 0 )
		{
			std::unique_ptr<nt_rgb32_image > pImage(new nt_rgb32_image());

			pImage->size_ = frame->stride0_* frame->height_;
			pImage->data_ = new NT_BYTE[pImage->size_];

			memcpy(pImage->data_, frame->plane0_, pImage->size_);

			pImage->width_  = frame->width_;
			pImage->height_ = frame->height_;
			pImage->stride_ = frame->stride0_;

			HWND hwnd = (HWND)userData;
			if ( hwnd != NULL && ::IsWindow(hwnd) )
			{
				::PostMessage(hwnd, WM_USER_SDK_RGB32_IMAGE, (WPARAM)handle, (LPARAM)pImage.release());
			}
		}
	}
}

具体绘制代码:

LRESULT CSmartPlayerDlg::OnSDKRGB32Image(WPARAM wParam, LPARAM lParam)
{
	nt_rgb32_image* pImage = (nt_rgb32_image*)(lParam);
	if (pImage == NULL)
		return S_OK;

	std::shared_ptr<nt_rgb32_image> sp_image(pImage);

	if ( is_gdi_render_ )
	{
		wrapper_render_wnd_.OnRGB32Image(sp_image);
	}

	return S_OK;
}
void nt_render_wnd::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	// TODO: Add your message handler code here
	// Do not call CWnd::OnPaint() for painting messages

	if ( IsIconic() )
	{
		return;
	}

	// 先绘制一个黑色的背景
	CRect rc_client(0, 0, 0, 0);
	GetClientRect(rc_client);

	if ( rc_client.IsRectNull()
		|| rc_client.IsRectEmpty() )
	{
		return;
	}

	auto mem_dc = ::CreateCompatibleDC(dc.GetSafeHdc());
	if ( mem_dc == NULL )
		return;

	auto mem_bitmap = ::CreateCompatibleBitmap(dc.GetSafeHdc(), rc_client.Width(), rc_client.Height());
	if ( mem_bitmap == NULL )
	{
		::DeleteDC(mem_dc);
		return;
	}

	::SelectObject(mem_dc, mem_bitmap);

	HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
	::FillRect(mem_dc, &rc_client, brush);
	::DeleteObject(brush);

	if ( rgb32_image_ )
	{
		if ( player_api_.GDIDrawRGB32 != NULL
			&& player_handle_ != NULL )
		{
			auto render_rc = GetRenderRect(rc_client, rgb32_image_->width_, rgb32_image_->height_);

			player_api_.GDIDrawRGB32(player_handle_, mem_dc,
				render_rc.left, render_rc.top,
				render_rc.Width(), render_rc.Height(),
				0, 0,
				rgb32_image_->width_, rgb32_image_->height_,
				rgb32_image_->data_, rgb32_image_->size_,
				rgb32_image_->width_, rgb32_image_->height_,
				rgb32_image_->stride_);

			if (logo_ && logo_->data_ != nullptr)
			{
				player_api_.GDIDrawARGB(mem_dc,
					render_logo_left_, render_logo_top_,
					render_logo_width_, render_logo_height_,
					0, 0,
					logo_->width_, logo_->height_,
					logo_->data_.get(), logo_->stride_,
					logo_->width_, logo_->height_
					);
			}
		}
	}

	::BitBlt(dc.GetSafeHdc(),
		0, 0,
		rc_client.Width(), rc_client.Height(),
		mem_dc,
		0, 0,
		SRCCOPY);

	::DeleteObject(mem_bitmap);

	::DeleteDC(mem_dc);
}

目前来看,不支持D3D的机器少之又少,在环境具备的情况下,优先建议考虑D3D模式绘制,不支持的情况下,同时兼容GDI绘制是个不错的选择。