Windows平台RTSP播放器/RTMP播放器设计需要考虑的几个点

简介: 我们在实现Windows平台RTSP播放器或RTMP播放器的时候,需要考虑的点很多,比如多实例设计、多绘制模式兼容、软硬解码支持、快照、RTSP下TCP-UDP自动切换等,以下就其中几个方面,做个大概的探讨。

我们在实现Windows平台RTSP播放器或RTMP播放器的时候,需要考虑的点很多,比如多实例设计、多绘制模式兼容、软硬解码支持、快照、RTSP下TCP-UDP自动切换等,以下就其中几个方面,做个大概的探讨。

1. 视频绘制模式

我们在实现Windows平台播放的时候,一般首选D3D,D3D不支持的情况下,考虑数据回上来,采用GDI模式,一般实现如下,先做D3D检测,以大牛直播SDK播放端为例(Github),调用NT_SP_IsSupportD3DRender(),检测是否支持D3D模式,如果支持的话,调用NT_SP_SetRenderWindow(), 然后,设置是否等比例缩放(调用NT_SP_SetRenderScaleMode())。

                bool is_support_d3d_render = false;
                Int32 in_support_d3d_render = 0;
                if (NT.NTBaseCodeDefine.NT_ERC_OK == NTSmartPlayerSDK.NT_SP_IsSupportD3DRender(player_handle_, playWnd.Handle, ref in_support_d3d_render))
                {
                    if (1 == in_support_d3d_render)
                    {
                        is_support_d3d_render = true;
                    }
                }
                if (is_support_d3d_render)
                {
                    is_gdi_render_ = false;
                    // 支持d3d绘制的话,就用D3D绘制
                    NTSmartPlayerSDK.NT_SP_SetRenderWindow(player_handle_, playWnd.Handle);
                    if (btn_check_render_scale_mode.Checked)
                    {
                        NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 1);
                    }
                    else
                    {
                        NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 0);
                    }
                }
                else
                {
                    is_gdi_render_ = true;
                    playWnd.Visible = false;
                    // 不支持D3D就让播放器吐出数据来,用GDI绘制
                    //video frame callback (YUV/RGB)
                    //format请参见 NT_SP_E_VIDEO_FRAME_FORMAT,如需回调YUV,请设置为 NT_SP_E_VIDEO_FRAME_FROMAT_I420
                    video_frame_call_back_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
                    NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, IntPtr.Zero, video_frame_call_back_);
                }

如果不支持D3D,设置RGB数据回调:

video_frame_call_back_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack);
                    NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, IntPtr.Zero, video_frame_call_back_);

数据处理如下:

        public void SetVideoFrameCallBack(IntPtr handle, IntPtr userData, UInt32 status, IntPtr frame)
        {
            if (frame == IntPtr.Zero)
            {
                return;
            }
            //如需直接处理RGB数据,请参考以下流程
            NT_SP_VideoFrame video_frame = (NT_SP_VideoFrame)Marshal.PtrToStructure(frame, typeof(NT_SP_VideoFrame));
            NT_SP_VideoFrame pVideoFrame = new NT_SP_VideoFrame();
            pVideoFrame.format_ = video_frame.format_;
            pVideoFrame.width_ = video_frame.width_;
            pVideoFrame.height_ = video_frame.height_;
            pVideoFrame.timestamp_ = video_frame.timestamp_;
            pVideoFrame.stride0_ = video_frame.stride0_;
            pVideoFrame.stride1_ = video_frame.stride1_;
            pVideoFrame.stride2_ = video_frame.stride2_;
            pVideoFrame.stride3_ = video_frame.stride3_;
            Int32 argb_size = video_frame.stride0_ * video_frame.height_;
            pVideoFrame.plane0_ = Marshal.AllocHGlobal(argb_size);
            CopyMemory(pVideoFrame.plane0_, video_frame.plane0_, (UInt32)argb_size);
            if (playWnd.InvokeRequired)
            {
                BeginInvoke(set_video_frame_call_back_, status, pVideoFrame);
            }
            else
            {
                set_video_frame_call_back_(status, pVideoFrame);
            }
        }

在OnPaint()绘制即可:

        private void SmartPlayerForm_Paint(object sender, PaintEventArgs e)
        {
            if (player_handle_ == IntPtr.Zero || !is_gdi_render_ || !is_playing_)
            {
                return;
            }
            if (cur_video_frame_.plane0_ == IntPtr.Zero)
            {
                return;
            }
            Bitmap bitmap = new Bitmap(cur_video_frame_.width_, cur_video_frame_.height_, cur_video_frame_.stride0_,
                 System.Drawing.Imaging.PixelFormat.Format32bppRgb, cur_video_frame_.plane0_);
            int image_width = cur_video_frame_.width_;
            int image_height = cur_video_frame_.height_;
            Graphics g = e.Graphics;    //获取窗体画布
            g.SmoothingMode = SmoothingMode.HighSpeed;
            int limit_w = this.Width - 60;
            int limit_h = this.Height - playWnd.Top - 60;
            if (btn_check_render_scale_mode.Checked)
            {
                int d_w = 0, d_h = 0;
                int left_offset = 0;
                int top_offset = 0;
                Brush brush = new SolidBrush(Color.Black);
                g.FillRectangle(brush, playWnd.Left, playWnd.Top, limit_w, limit_h);
                GetRenderRect(limit_w, limit_h, image_width, image_height, ref left_offset, ref top_offset, ref d_w, ref d_h);
                g.DrawImage(bitmap, playWnd.Left + left_offset, playWnd.Top + top_offset, d_w, d_h);   //在窗体的画布中绘画出内存中的图像
            }
            else
            {
                g.DrawImage(bitmap, playWnd.Left, playWnd.Top, limit_w, limit_h);   //在窗体的画布中绘画出内存中的图像
            }
        }

2. 特定机型硬解码

Windows平台硬解码,主要适用于性能偏弱的PC端,或者有多路播放诉求的场景,一般建议在软解性能没问题的情况下,尽量软解,具体处理如下,先检测系统是否支持硬解,如果支持,再做硬解设置,这样的好处在于如果系统不支持硬解,可以继续软解播放,具体设置如下,在调用NT_SP_Open()之前,做检测,因为NT_SP_Open()每个句柄对应一个player实例,多个实例只需要做一次判断即可:

            is_support_h264_hardware_decoder_ = NT.NTBaseCodeDefine.NT_ERC_OK == NT.NTSmartPlayerSDK.NT_SP_IsSupportH264HardwareDecoder();
            is_support_h265_hardware_decoder_ = NT.NTBaseCodeDefine.NT_ERC_OK == NT.NTSmartPlayerSDK.NT_SP_IsSupportH265HardwareDecoder();
            if (player_handle_ == IntPtr.Zero)
            {
                player_handle_ = new IntPtr();
                UInt32 ret_open = NTSmartPlayerSDK.NT_SP_Open(out player_handle_, IntPtr.Zero, 0, IntPtr.Zero);
                if (ret_open != 0)
                {
                    player_handle_ = IntPtr.Zero;
                    MessageBox.Show("调用NT_SP_Open失败..");
                    return;
                }
            }

播放之前,设置硬解码:

            if (checkBox_hardware_decoder.Checked)
            {
                NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(player_handle_, is_support_h264_hardware_decoder_ ? 1 : 0, 0);
                NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(player_handle_, is_support_h265_hardware_decoder_ ? 1 : 0, 0);
            }
            else
            {
                NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(player_handle_, 0, 0);
                NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(player_handle_, 0, 0);
            }

3. 只解码关键帧

只解关键帧的场景,也是用于多路播放诉求,比如一般的监控场景,考虑到多路的场景,一般关键帧间隔不大(如1-2秒一个),平台可对现场场景有个宏观了解,如需重点关注某几路画面的时候,再实时取消这个选项,实现全帧播放,所以,只解关键帧一定要做成实时调用的接口才更有设计意义。

            // 设置是否只解码关键帧
            if (btn_check_only_decode_video_key_frame.Checked)
            {
                NTSmartPlayerSDK.NT_SP_SetOnlyDecodeVideoKeyFrame(player_handle_, 1);
            }
            else
            {
                NTSmartPlayerSDK.NT_SP_SetOnlyDecodeVideoKeyFrame(player_handle_, 0);
            }

4. 视频view旋转

好多现场的开发人员有这样的困惑,有些设备,在安装时,可能没调整好角度,导致拍出来的角度倒立等,看着很不方便,这时候,如果现场设备比较多的话,不可能每台设备都到现场重新安装,实时view旋转,就体现了价值,具体如下:

    /*
         * 设置旋转,顺时针旋转
         * degress: 设置0, 90, 180, 270度有效,其他值无效
         * 注意:除了0度,其他角度播放会耗费更多CPU
         * 接口调用成功返回NT_ERC_OK
     */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_SetRotation(IntPtr handle, Int32 degress);

视频view选择,会消耗一定的CPU。

5. 实时快照

实时快照功能不表,是一个好的RTSP播放器和RTMP播放器必备的功能,实时快照是把解码后的yuv数据重新编码成png,所以有一定的CPU消耗,不建议过于频繁操作,具体实现如下:

            if ( String.IsNullOrEmpty(capture_image_path_) )
          {
            MessageBox.Show("请先设置保存截图文件的目录! 点击截图左边的按钮设置!");
            return;
          }
          if ( player_handle_ == IntPtr.Zero )
          {
            return;
          }
          if ( !is_playing_)
          {
                MessageBox.Show("请在播放状态下截图!");
            return;
          }
            String name = capture_image_path_ + "\\" +  DateTime.Now.ToString("hh-mm-ss") + ".png";
            byte[] buffer1 = Encoding.Default.GetBytes(name);
            byte[] buffer2 = Encoding.Convert(Encoding.Default, Encoding.UTF8, buffer1, 0, buffer1.Length);
            byte[] buffer3 = new byte[buffer2.Length + 1];
            buffer3[buffer2.Length] = 0;
            Array.Copy(buffer2, buffer3, buffer2.Length);
            IntPtr file_name_ptr = Marshal.AllocHGlobal(buffer3.Length);
            Marshal.Copy(buffer3, 0, file_name_ptr, buffer3.Length);
            capture_image_call_back_ = new SP_SDKCaptureImageCallBack(SDKCaptureImageCallBack);
            UInt32 ret = NTSmartPlayerSDK.NT_SP_CaptureImage(player_handle_, file_name_ptr, IntPtr.Zero, capture_image_call_back_);
            Marshal.FreeHGlobal(file_name_ptr);
            if (NT.NTBaseCodeDefine.NT_ERC_OK == ret)
          {
            // 发送截图请求成功
          }
            else if ((UInt32)NT.NTSmartPlayerDefine.SP_E_ERROR_CODE.NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS == ret)
          {
            // 通知用户延时
            MessageBox.Show("Too many capture image requests!");
          }
          else
          {
            // 其他失败
          }
        public void SDKCaptureImageCallBack(IntPtr handle, IntPtr userData, UInt32 result, IntPtr file_name)
        {
            if (file_name == IntPtr.Zero)
                return;
            int index = 0;
            while (true)
            {
                if (0 == Marshal.ReadByte(file_name, index))
                    break;
                index++;
            }
            byte[] file_name_buffer = new byte[index];
            Marshal.Copy(file_name, file_name_buffer, 0, index);
            byte[] dst_buffer = Encoding.Convert(Encoding.UTF8, Encoding.Default, file_name_buffer, 0, file_name_buffer.Length);
            String image_name = Encoding.Default.GetString(dst_buffer, 0, dst_buffer.Length);
            if (playWnd.InvokeRequired)
            {
                BeginInvoke(set_capture_image_call_back_, result, image_name);
            }
            else
            {
                set_capture_image_call_back_(result, image_name);
            }
        }

后续,我们将针对RTSP和RTMP播放器设计过程中的其他点,做更进一步的探讨。

相关文章
|
2月前
|
XML C# 数据格式
掌握了在Windows平台上查看DLL依赖的方法
掌握了在Windows平台上查看DLL依赖的方法
321 4
|
2月前
|
NoSQL Shell MongoDB
Windows 平台安装 MongoDB
10月更文挑战第10天
66 0
Windows 平台安装 MongoDB
|
3月前
|
监控 C# 块存储
Windows平台RTSP|RTMP播放器如何叠加OSD文字
做Windows平台RTSP|RTMP播放器的时候,特别是多路播放场景下,开发者希望可以给每一路RTSP或RTMP流添加个额外的OSD台标,以区分不同的设备信息(比如添加摄像头所在位置),本文主要探讨,如何动态添加OSD台标。
Windows平台RTSP|RTMP播放器如何叠加OSD文字
|
2月前
|
并行计算 开发工具 异构计算
在Windows平台使用源码编译和安装PyTorch3D指定版本
【10月更文挑战第6天】在 Windows 平台上,编译和安装指定版本的 PyTorch3D 需要先安装 Python、Visual Studio Build Tools 和 CUDA(如有需要),然后通过 Git 获取源码。建议创建虚拟环境以隔离依赖,并使用 `pip` 安装所需库。最后,在源码目录下运行 `python setup.py install` 进行编译和安装。完成后即可在 Python 中导入 PyTorch3D 使用。
292 0
|
1月前
|
网络安全 Windows
Windows server 2012R2系统安装远程桌面服务后无法多用户同时登录是什么原因?
【11月更文挑战第15天】本文介绍了在Windows Server 2012 R2中遇到的多用户无法同时登录远程桌面的问题及其解决方法,包括许可模式限制、组策略配置问题、远程桌面服务配置错误以及网络和防火墙问题四个方面的原因分析及对应的解决方案。
|
1月前
|
监控 安全 网络安全
使用EventLog Analyzer日志分析工具监测 Windows Server 安全威胁
Windows服务器面临多重威胁,包括勒索软件、DoS攻击、内部威胁、恶意软件感染、网络钓鱼、暴力破解、漏洞利用、Web应用攻击及配置错误等。这些威胁严重威胁服务器安全与业务连续性。EventLog Analyzer通过日志管理和威胁分析,有效检测并应对上述威胁,提升服务器安全性,确保服务稳定运行。
|
1月前
|
监控 安全 网络安全
Windows Server管理:配置与管理技巧
Windows Server管理:配置与管理技巧
84 3
|
1月前
|
存储 安全 网络安全
Windows Server 本地安全策略
由于广泛使用及历史上存在的漏洞,Windows服务器成为黑客和恶意行为者的主要攻击目标。这些系统通常存储敏感数据并支持关键服务,因此组织需优先缓解风险,保障业务的完整性和连续性。常见的威胁包括勒索软件、拒绝服务攻击、内部威胁、恶意软件感染等。本地安全策略是Windows操作系统中用于管理计算机本地安全性设置的工具,主要包括用户账户策略、安全选项、安全设置等。实施强大的安全措施,如定期补丁更新、网络分段、入侵检测系统、数据加密等,对于加固Windows服务器至关重要。
|
2月前
|
边缘计算 安全 网络安全