Windows平台基于RTMP实现一对一互动直播

目前市面上大多一对一互动都是基于WebRTC,缺点如下:

  1. 服务器部署非常复杂,不利于私有部署,在一些私密性高的场景下,无法使用,如*、市政等体系;
  2. 传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量;
  3. 难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景;
  4. 整个框架体系不够灵活,代码复杂度高,行话说的好:从demo到实用,中间还差1万个WebRTC

RTMP一对一互动技术特点:

  •  基于现有RTMP推拉流体系,产品稳定度高,整体延迟低;
  •  加入噪音抑制、回音消除、自动增益控制等特性,确保通话效果;
  •  采用通用的RTMP和RTSP服务器,如nginx、SRS,更有利于私有部署;
  •  支持H.264的扩展SEI消息发送机制;
  •  支持H.265编码和H.264可变码率设定;
  •  支持H.265解码,直播播放器支持的功能,一对一互动模块都可以有选择的支持;
  •  适用于应急指挥、教育培训等领域;
  •  支持RTMP扩展AES/SM4加解密,确保音视频数据安全性。

废话不多说,上封装代码:

基于 https://github.com/daniulive/SmarterStreaming/ 拉流端封装的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using NT;

namespace SmartEchoCancellationDemo
{
    public delegate void DelGetPlayerEventMsg(String msg);
    public delegate void DelGetVideoSize(String size);

    class nt_player_wrapper : IDisposable
    {
        [DllImport("kernel32", EntryPoint = "CopyMemory")]
        static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

        private bool disposed_ = false;

        private IntPtr player_handle_ = IntPtr.Zero;
        private System.Windows.Forms.Control render_wnd_ = null;
        private System.Windows.Forms.PaintEventHandler render_wnd_paint_event_ = null;

        private bool is_playing_ = false;
        private bool is_mute_ = false;
        private int  play_buffer_ = 100;

        private NT_SP_VideoFrame cur_video_frame_ = new NT_SP_VideoFrame();

        private WeakReference sync_invoke_ = null;

        //分辨率信息回调
        delegate void ResolutionNotifyCallback(Int32 width, Int32 height);
        ResolutionNotifyCallback resolution_notify_callback_;
        SP_SDKVideoSizeCallBack video_size_call_back_;

        //视频数据回调
        SP_SDKVideoFrameCallBack video_frame_call_back_;
        delegate void VideoFrameCallBack(UInt32 status, NT_SP_VideoFrame frame);
        VideoFrameCallBack set_video_frame_call_back_;

        //event事件回调
        //拉流端事件
        SP_SDKEventCallBack pull_event_call_back_;
        delegate void SetPullEventCallBack(UInt32 event_id,
                Int64 param1,
                Int64 param2,
                UInt64 param3,
                [MarshalAs(UnmanagedType.LPStr)] String param4,
                [MarshalAs(UnmanagedType.LPStr)] String param5,
                IntPtr param6);
        SetPullEventCallBack set_pull_event_call_back_;

        private UInt32 connection_status_ = 0;
        private UInt32 buffer_status_ = 0;
        private Int32 buffer_percent_ = 0;
        private Int32 download_speed_ = -1;

        public event DelGetPlayerEventMsg EventGetPlayerEventMsg;
        public event DelGetVideoSize EventGetVideoSize;

        public nt_player_wrapper(System.Windows.Forms.Control render_wnd, System.ComponentModel.ISynchronizeInvoke sync_invoke)
        {
            render_wnd_ = render_wnd;
            sync_invoke_ =  new WeakReference(sync_invoke);
            set_pull_event_call_back_ = new SetPullEventCallBack(PullEventCallBack);

            if (render_wnd_ != null)
            {
                render_wnd_paint_event_ = new System.Windows.Forms.PaintEventHandler(this.OnRenderWindowPaint);
                render_wnd_.Paint += render_wnd_paint_event_;
            }
        }

        public void Dispose()
        {
            Dispose(true);

            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            // GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed_)
            {
                if (disposing)
                {
                }

                if (IsPlaying())
                {
                   StopPlay(false);
                }

                if (render_wnd_ != null && render_wnd_paint_event_ != null)
                {
                    render_wnd_.Paint -= render_wnd_paint_event_;
                }

                render_wnd_paint_event_ = null;

                if (cur_video_frame_.plane0_ != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(cur_video_frame_.plane0_);
                    cur_video_frame_.plane0_ = IntPtr.Zero;
                }
             
                // Note disposing has been done.
                disposed_ = true;
            }
        }

 
        ~nt_player_wrapper()
        {
            Dispose(false);
        }

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

            if (video_frame.format_ != (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32)
                return;

            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_;

            if (sync_invoke_ != null)
            {
                System.ComponentModel.ISynchronizeInvoke sync_invoke_target = sync_invoke_.Target as System.ComponentModel.ISynchronizeInvoke;

                if (sync_invoke_target != null)
                {
                    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 (sync_invoke_target.InvokeRequired)
                    {
                        sync_invoke_target.BeginInvoke(set_video_frame_call_back_, new object[] { status, pVideoFrame });
                    }
                    else
                    {
                        set_video_frame_call_back_(status, pVideoFrame);
                    }
                }
            }
        }

        public void SDKVideoFrameCallBack(UInt32 status, NT_SP_VideoFrame frame)
        {
            if (cur_video_frame_.plane0_ != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(cur_video_frame_.plane0_);
                cur_video_frame_.plane0_ = IntPtr.Zero;
            }

            cur_video_frame_ = frame;

            if (render_wnd_ != null)
            {
                render_wnd_.Invalidate();
            }
        }

        public void SDKPullEventCallBack(IntPtr handle, IntPtr user_data,
            UInt32 event_id,
            Int64 param1,
            Int64 param2,
            UInt64 param3,
            [MarshalAs(UnmanagedType.LPStr)] String param4,
            [MarshalAs(UnmanagedType.LPStr)] String param5,
            IntPtr param6)
        {
            if (sync_invoke_ != null)
            {
                System.ComponentModel.ISynchronizeInvoke sync_invoke_target = sync_invoke_.Target as System.ComponentModel.ISynchronizeInvoke;

                if (sync_invoke_target  != null )
                {
                    if (sync_invoke_target.InvokeRequired)
                    {
                        sync_invoke_target.BeginInvoke(set_pull_event_call_back_, new object[] { event_id, param1, param2, param3, param4, param5, param6 });
                    }
                    else
                    {
                        set_pull_event_call_back_(event_id, param1, param2, param3, param4, param5, param6);
                    }
                }
            }
        }

        private void PullEventCallBack(UInt32 event_id,
            Int64 param1,
            Int64 param2,
            UInt64 param3,
            [MarshalAs(UnmanagedType.LPStr)] String param4,
            [MarshalAs(UnmanagedType.LPStr)] String param5,
            IntPtr param6)
        {
            if (!is_playing_)
            {
                return;
            }

            String show_str = "";

            if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS == event_id)
            {
                StopPlay();
                return;
            }
            else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_RTSP_STATUS_CODE == event_id)
            {
                int status_code = (int)param1;
                show_str = "RTSP incorrect status code received: " + status_code.ToString() + ", 请确保用户名/密码正确";
            }

            if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTING == event_id
                || (UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTION_FAILED == event_id
                || (UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTED == event_id
                || (UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_DISCONNECTED == event_id)
            {
                connection_status_ = event_id;
            }

            if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_START_BUFFERING == event_id
                || (UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_BUFFERING == event_id
                || (UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_STOP_BUFFERING == event_id)
            {
                buffer_status_ = event_id;

                if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_BUFFERING == event_id)
                {
                    buffer_percent_ = (Int32)param1;
                }
            }

            if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_DOWNLOAD_SPEED == event_id)
            {
                download_speed_ = (Int32)param1;
            }

            if (connection_status_ != 0)
            {
                show_str += "连接状态: ";

                if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTING == connection_status_)
                {
                    show_str += "连接中";
                }
                else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTION_FAILED == connection_status_)
                {
                    show_str += "连接失败";
                }
                else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_CONNECTED == connection_status_)
                {
                    show_str += "连接成功";
                }
                else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_DISCONNECTED == connection_status_)
                {
                    show_str += "断开连接";
                }
            }

            if (download_speed_ != -1)
            {
                String ss = "  下载速度: " + (download_speed_ * 8 / 1000).ToString() + "kbps " + (download_speed_ / 1024).ToString() + "KB/s";

                show_str += ss;
            }

            if (buffer_status_ != 0)
            {
                show_str += "  缓冲状态: ";

                if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_START_BUFFERING == buffer_status_)
                {
                    show_str += "开始缓冲";
                }
                else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_BUFFERING == buffer_status_)
                {
                    String ss = "缓冲中 " + buffer_percent_.ToString() + "%";
                    show_str += ss;
                }
                else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_STOP_BUFFERING == buffer_status_)
                {
                    show_str += "结束缓冲";
                }
            }

            if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_NEED_KEY == event_id)
            {
                show_str = "RTMP加密流,请设置播放需要的Key..";
            }
            else if ((UInt32)NTSmartPlayerDefine.NT_SP_E_EVENT_ID.NT_SP_E_EVENT_ID_KEY_ERROR == event_id)
            {
                show_str = "RTMP加密流,Key错误,请重新设置..";
            }

            EventGetPlayerEventMsg(show_str);
        }

        public void SetMute(bool is_mute)
        {
            is_mute_ = is_mute;

	        if ( !is_playing_ )
		        
上一篇:hdu1698 Just a hook 线段树区间更新


下一篇:C#协变和逆变