我们在做RTSP、RTMP播放器的时候,遇到这样的诉求:特别是RTSP,有些摄像头安装可能倒置或者旋转了90°亦或270°,拉取到图像,势必需要对视频图像做一定的处理,确保显示正常。
为此,我们提供了以下接口:视频数据水平反转、垂直反转、设置旋转角度。
好多开发者搞不清楚特别是水平反转和垂直反转,以下我们以图例的形式,做个效果展示。
先看原始图像:
水平反转后:
垂直反转后:
按照设定角度旋转(90°、180°、270°):
以C++的接口为例,设计如下:
/*
*上下反转(垂直反转)
*is_flip: 1:表示反转, 0:表示不反转
*/
NT_UINT32(NT_API *SetFlipVertical)(NT_HANDLE handle, NT_INT32 is_flip);
/*
*水平反转
*is_flip: 1:表示反转, 0:表示不反转
*/
NT_UINT32(NT_API *SetFlipHorizontal)(NT_HANDLE handle, NT_INT32 is_flip);
/*
设置旋转,顺时针旋转
degress: 设置0, 90, 180, 270度有效,其他值无效
注意:除了0度,其他角度播放会耗费更多CPU
接口调用成功返回NT_ERC_OK
*/
NT_UINT32(NT_API* SetRotation)(NT_HANDLE handle, NT_INT32 degress);
以上接口设计,考虑到图像出来后,才可以知道要怎么调整,设计成了可实时调用的接口模式。
具体调用逻辑非常简单:
player_api_.SetFlipVertical(player_handle_, BST_CHECKED == btn_check_flip_vertical_.GetCheck() ? 1 :0 );
player_api_.SetFlipHorizontal(player_handle_, BST_CHECKED == btn_check_flip_horizontal_.GetCheck() ? 1 : 0);
player_api_.SetRotation(player_handle_, rotate_degrees_);
旋转角度按钮逻辑:
void CSmartPlayerDlg::OnBnClickedButtonRotation()
{
rotate_degrees_ += 90;
rotate_degrees_ = rotate_degrees_ % 360;
if (0 == rotate_degrees_)
{
btn_rotation_.SetWindowText(_T("旋转90度"));
}
else if (90 == rotate_degrees_)
{
btn_rotation_.SetWindowText(_T("旋转180度"));
}
else if (180 == rotate_degrees_)
{
btn_rotation_.SetWindowText(_T("旋转270度"));
}
else if (270 == rotate_degrees_)
{
btn_rotation_.SetWindowText(_T("不旋转"));
}
if ( player_handle_ != NULL )
{
player_api_.SetRotation(player_handle_, rotate_degrees_);
}
}
总的来说,实现难度不大,此外,我们针对视频数据,还设计了只解关键帧、按照视频宽高scale显示图像,最大限度的方便用户使用。
/*
*设置只解码视频关键帧
*is_only_dec_key_frame: 1:表示只解码关键帧, 0:表示都解码, 默认是0
*成功返回NT_ERC_OK
*/
NT_UINT32(NT_API *SetOnlyDecodeVideoKeyFrame)(NT_HANDLE handle, NT_INT32 is_only_dec_key_frame);
/*
设置视频画面的填充模式,如填充整个绘制窗口、等比例填充绘制窗口,如不设置,默认填充整个绘制窗口
handle: 播放句柄
mode: 0: 填充整个绘制窗口; 1: 等比例填充绘制窗口, 默认值是0
成功返回NT_ERC_OK
*/
NT_UINT32 (NT_API *SetRenderScaleMode)(NT_HANDLE handle, NT_INT32 mode);
如果以上数据都还不满足开发者或终端用户的需求,我们还可以把数据(YUV/RGB)回调上来,用户自行处理。
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 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());
}
}
}
}
有了这些数据接口的加持,播放端对数据处理非常方便。