zl程序教程

您现在的位置是:首页 >  Java

当前栏目

RTSP、RTMP播放器拉到的视频图像角度不对怎么办?

2023-02-18 16:41:00 时间

我们在做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<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());
      }
    }
  }
}

有了这些数据接口的加持,播放端对数据处理非常方便。