EasyPusher推流類庫的.NET調用說明

EasyPusher推流類庫的.NET調用說明

如下內容基於在使用EasyPusher過程當中遇到的問題,以及相應的注意事項。
本文主要是基於對C++類庫的二次封裝(便於調試發現問題)以供C#調用以及對一些方法使用.NET實現。html


1. C++類庫的二次封裝

較少接觸C+ +在直接調用C+ +類庫的狀況下發生錯誤會容易出現很差定位錯誤的狀況,在部門同事的提醒下使用C+ +對原有的類庫進行了二次封裝,這樣就可使用C+ +調用C+ +也就能夠方便的調試(eg:查看.NET傳遞的參數是否符合預期)
具體的對C++類庫的封裝及調試可參考博客:C+ +建立DLL並用C#調用且同時實現對DLL的調試
注意事項c++

項目的VC編譯選項要設置爲」多線程(/MT )」,否則可能會出現服務器上運行時找不到DLL的問題
參考連接 用VS2010編寫的C++程序,在其餘電腦上沒法運行,提示缺乏mfc100.dll的解決辦法web

2. 使用說明

因爲二次封裝僅僅是便於調試方便,未對原有類庫的方法進行新的整合,故而使用方法同原生類庫的使用方法是一致的。
該推流模塊主要適用於已經存在音視頻數據流的狀況下對音視頻數據流進行推送。
以海康設備爲例編程

  1. 使用海康SDK獲取音視頻數據
  2. 使用工具函數對每一幀數據進行處理[判斷數據幀類型/數據轉換]
  3. 使用EasyPusher_PushFrame逐幀推送數據到遠程服務器
代碼附錄
  • DLL C#調用
/// <summary>
    /// 推流SDK方法封裝
    /// </summary>
    public class EasyPusherSDK
    {
        public EasyPusherSDK() { }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        public struct EASY_AV_Frame
        {
            public uint u32AVFrameFlag;    /* 幀標誌 視頻 or 音頻 */
            public uint u32AVFrameLen;     /* 幀的長度 */
            public uint u32VFrameType;     /* 視頻的類型,I幀或P幀 */
            public IntPtr pBuffer;           /* 數據 */
            public uint u32TimestampSec;   /* 時間戳(秒)*/
            public uint u32TimestampUsec;    /* 時間戳(微秒) */
        }

        public enum EASY_PUSH_STATE_T
        {
            EASY_PUSH_STATE_CONNECTING = 1,         /* 鏈接中 */
            EASY_PUSH_STATE_CONNECTED,              /* 鏈接成功 */
            EASY_PUSH_STATE_CONNECT_FAILED,         /* 鏈接失敗 */
            EASY_PUSH_STATE_CONNECT_ABORT,          /* 鏈接異常中斷 */
            EASY_PUSH_STATE_PUSHING,                /* 推流中 */
            EASY_PUSH_STATE_DISCONNECTED,           /* 斷開鏈接 */
            EASY_PUSH_STATE_ERROR
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        public struct EASY_MEDIA_INFO_T
        {
            /// <summary>
            /// 視頻編碼類型
            /// </summary>
            public uint u32VideoCodec;

            /// <summary>
            /// 視頻幀率
            /// </summary>
            public uint u32VideoFps;

            /// <summary>
            /// 音頻編碼類型
            /// </summary>
            public uint u32AudioCodec;

            /// <summary>
            /// 音頻採樣率
            /// </summary>
            public uint u32AudioSamplerate;

            /// <summary>
            /// 音頻通道數
            /// </summary>
            public uint u32AudioChannel;

            /// <summary>
            /// 音頻採樣精度
            /// </summary>
            public uint u32AudioBitsPerSample;

            /// <summary>
            /// 視頻sps幀長度
            /// </summary>
            public uint u32H264SpsLength;

            /// <summary>
            /// 視頻pps幀長度
            /// </summary>
            public uint u32H264PpsLength;

            /// <summary>
            /// 視頻sps幀內容
            /// </summary>
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 128)]
            public char[] u8H264Sps;

            /// <summary>
            /// 視頻sps幀內容
            /// </summary>
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 36)]
            public char[] u8H264Pps;
        }


        [DllImport(@"Lib\RTPusher.dll", CallingConvention = CallingConvention.Cdecl)]

        // -1, /* 無效Key */
        // -2, /* 時間錯誤 */
        // -3, /* 進程名稱長度不匹配 */
        // -4, /* 進程名稱不匹配 */
        // -5, /* 有效期校驗不一致 */
        //-6, /* 平臺不匹配 */
        // -7, /* 受權使用商不匹配 */
        // 0, /* 激活成功 */
        public static extern int RTPusher_Activate(string license);

        [DllImport(@"Lib\RTPusher.dll")]
        /* 建立推送句柄 返回爲句柄值 */
        public static extern IntPtr RTPusher_Create();

        [DllImport(@"Lib\RTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
        /* 釋放推送句柄 */
        public static extern uint RTPusher_Release(IntPtr pushPtr);

        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public delegate int RTPusher_Callback(int _id, EASY_PUSH_STATE_T _state, ref EASY_AV_Frame _frame, IntPtr _userptr);

        [DllImport(@"Lib\RTPusher.dll"
               , CallingConvention = CallingConvention.Cdecl)]
        /* 設置流傳輸事件回調 userptr傳輸自定義對象指針*/
        public static extern uint RTPusher_SetEventCallback(IntPtr handle, RTPusher_Callback callback, int id, IntPtr userptr);

        /* 開始流傳輸 serverAddr:流媒體服務器地址、port:流媒體端口、streamName:流名稱<xxx.sdp>、username/password:推送攜帶的用戶名密碼、pstruStreamInfo:推送的媒體定義、bufferKSize:以k爲單位的緩衝區大小<512~2048之間,默認512> bool createlogfile:建立日誌文件*/
        [DllImport(@"Lib\RTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern uint RTPusher_StartStream(IntPtr handle, string serverAddr, uint port, string streamName, string username, string password, ref EASY_MEDIA_INFO_T pstruStreamInfo, uint bufferKSize, bool createlogfile);

        /// <summary>
        /// 關閉推流,並釋放資源.
        /// </summary>
        /// <param name="pushPtr">The push PTR.</param>
        /// <returns>System.UInt32.</returns>
        [DllImport(@"Lib\RTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
        /* 中止流傳輸 */
        public static extern uint RTPusher_StopStream(IntPtr pushPtr);

        [DllImport(@"Lib\RTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
        /* 推流 frame:具體推送的流媒體幀 */
        public static extern uint RTPusher_PushFrame(IntPtr pushPtr, ref EASY_AV_Frame frame);
    }
  • 工具方法
/// <summary>
        /// Determines whether [is i frame] [the specified buf].
        /// </summary>
        /// <param name="buf">The buf.</param>
        /// <returns><c>true</c> if [is i frame] [the specified buf]; otherwise, <c>false</c>.</returns>
        public static bool IsIFrame(byte[] buf) {
            int naltype = (buf[4] & 0x1F);
            switch (naltype)
            {
                case 7: //sps
                case 8: // pps
                case 6: // i
                case 5: //idr
                    return true;
                case 1: // slice
                case 9: // unknown ???
                default:
                    return false;
            }
        }


        /// <summary>
        /// Gets the H246 from ps.
        /// </summary>
        /// <param name="pBuffer">PS 流數據</param>
        /// <param name="pH264">轉換後的H264流數據(音視頻)</param>
        /// <param name="bVideo">if set to <c>true</c> [b video].</param>
        /// <param name="bAudio">if set to <c>true</c> [b audio].</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
        public static bool GetH246FromPS(byte[] pBuffer, ref byte[] pH264, out bool bVideo, out bool bAudio) {
            var _nBufLenth = (int)pBuffer.Length;
            if (pBuffer == null || _nBufLenth <= 0)
            {
                bVideo = bAudio = false;
                return false;
            }
            int nHerderLen = 0;

            if (pBuffer != null
                && pBuffer[0] == 0x00
                && pBuffer[1] == 0x00
                && pBuffer[2] == 0x01
                && pBuffer[3] == 0xE0)//E==視頻數據(此處E0標識爲視頻)
            {
                bVideo = true;
                bAudio = false;
                nHerderLen = 9 + (int)pBuffer[8];//9個爲固定的數據包頭長度,pBuffer[8]爲填充頭部分的長度

                var nH264Lenth = _nBufLenth - nHerderLen;
                if (pH264 == null)
                {
                    pH264 = new byte[nH264Lenth];
                }
                if (pH264 != null && nH264Lenth > 0)
                {
                    pH264 = pBuffer.Skip(nHerderLen).Take(nH264Lenth).ToArray();
                }
                return true;
            }
            else if (pBuffer != null && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xC0) //C==音頻數據? {
                pH264 = null;
                bVideo = false;
                bAudio = true;
                var nH264Lenth = _nBufLenth - nHerderLen;
                nHerderLen = 9 + (int)pBuffer[8];//9個爲固定的數據包頭長度,pBuffer[8]爲填充頭部分的長度

                if (pH264 == null)
                {
                    pH264 = new byte[nH264Lenth];
                }
                if (pH264 != null && nH264Lenth > 0)
                {
                    pH264 = pBuffer.Skip(nHerderLen).Take(nH264Lenth).ToArray();
                }
                return true;
            }
            else if (pBuffer != null && pBuffer[0] == 0x00 && pBuffer[1] == 0x00 && pBuffer[2] == 0x01 && pBuffer[3] == 0xBA)//視頻流數據包 包頭 {
                bVideo = true;
                bAudio = false;
                pH264 = null;
                return false;
            }
            bVideo = bAudio = false;
            return false;
        }
參考連接

Update :數組

1. 再使用了靜態編譯選項後,仍然有DLL找不到的問題,可考慮使用Dependency Walker查看DLL的依賴是否缺失,通常狀況下是缺乏系統C++運行庫服務器

2.若是不容易捕捉到異常信息,可查看事件管理器看一看系統有沒有異常事件產生,可能對發現問題會有幫助

更新:20171024多線程

/// <summary>
        /// Gets the H246 from ps.
        /// </summary>
        /// <param name="pBuffer">流數據.</param>
        /// <param name="existBuffer">The exist buffer.</param>
        /// <param name="action">解析後數據、是否須要下一個流數據拼接、是不是視頻,是不是音頻.</param>
        public static void GetH246FromPS(byte[] pBuffer, List<byte> existBuffer, Action<byte[], bool, bool, bool> action)
        {
            List<byte> pH264 = new List<byte>();
            byte[] searchBytes = new byte[3] { 0x00, 0x00, 0x01 };
            int freamHeaderLength = 0;
            int freamLength = 0;
            if (pBuffer == null || pBuffer.Length <= 0)
                return;
            if (existBuffer.Count > 0)
            {
                existBuffer.AddRange(pBuffer);
                pBuffer = existBuffer.ToArray();
                existBuffer.Clear();
            }
            while (true)
            {
                begin:
                if (pBuffer.Count() == 0)
                    break;
                if (pBuffer.Count() < 9)
                {
                    action(pBuffer, true, false, false);
                    break;
                }
                pH264.Clear();

                if (pBuffer[0] == 0x00
                    && pBuffer[1] == 0x00
                    && pBuffer[2] == 0x01
                    && pBuffer[3] == 0xBA/*ps_header*/)
                {
                    //拋棄數據
                    if (pBuffer.Count() < 20)
                    {
                        action(pBuffer.ToArray(), true, false, false);
                        break;
                    }
                    pBuffer = pBuffer.Skip(20).ToArray();
                    goto begin;
                }

                if (pBuffer[0] == 0x00
                     && pBuffer[1] == 0x00
                     && pBuffer[2] == 0x01
                     && (pBuffer[3] == 0xBD/*私有包頭*/|| pBuffer[3] == 0xBC/*psm_header*/))
                {
                    //拋棄數據
                    freamHeaderLength = 9 + (int)pBuffer[8];
                    freamLength = BitConverter.ToUInt16(new byte[2] { pBuffer[5], pBuffer[4] }, 0);
                    var nH264Lenth = freamLength + 6 - freamHeaderLength;
                    if (pBuffer.Count() < freamHeaderLength + nH264Lenth)
                    {
                        action(pBuffer.ToArray(), true, false, false);
                        break;
                    }
                    pBuffer = pBuffer.Skip(6 + freamLength).ToArray();
                    goto begin;
                }

                if (pBuffer[0] == 0x00
                   && pBuffer[1] == 0x00
                   && pBuffer[2] == 0x01
                   && (pBuffer[3] >= 0xC0 && pBuffer[3] <= 0xDF))//pes_audio_header
                {
                    freamHeaderLength = 9 + (int)pBuffer[8];
                    freamLength = BitConverter.ToUInt16(new byte[2] { pBuffer[5], pBuffer[4] }, 0);
                    var nH264Lenth = freamLength + 6 - freamHeaderLength;
                    if (pBuffer.Count() < freamHeaderLength + nH264Lenth)
                    {
                        action(pBuffer.ToArray(), true, false, false);
                        break;
                    }
                    pH264 = pBuffer.Skip(freamHeaderLength).Take(nH264Lenth).ToList();
                    action(pH264.ToArray(), false, false, true);

                    pBuffer = pBuffer.Skip(6 + freamLength).ToArray();
                    goto begin;
                }

                if (pBuffer[0] == 0x00
                    && pBuffer[1] == 0x00
                    && pBuffer[2] == 0x01
                    && (pBuffer[3] >= 0xE0 && pBuffer[3] <= 0xEF))//pes_video_header
                {
                    freamHeaderLength = 9 + (int)pBuffer[8];//9個爲固定的數據包頭長度,pBuffer[8]爲填充頭部分的長度

                    var beginIndex = IndexOf(pBuffer, searchBytes, freamHeaderLength + 2);

                    if (beginIndex != -1)
                    {
                        if (pBuffer[beginIndex - 1] == 0)//0x00000001
                        {
                            beginIndex -= 1;
                        }
                        var nH264Lenth = beginIndex - freamHeaderLength;

                        pH264 = pBuffer.Skip(freamHeaderLength).Take(nH264Lenth).ToList();
                        action(pH264.ToArray(), false, true, false);
                        pBuffer = pBuffer.Skip(beginIndex).ToArray();
                        goto begin;
                    }
                    else
                    {
                        action(pBuffer.ToArray(), true, false, false);
                        break;
                    }
                }

                if (pBuffer[0] == 0x00
                    && pBuffer[1] == 0x00
                    && pBuffer[2] == 0x00
                    && pBuffer[3] == 0x01)//幀數據
                {
                    freamHeaderLength = 0;

                    var beginIndex = IndexOf(pBuffer, searchBytes, freamHeaderLength + 2);

                    if (beginIndex != -1)
                    {
                        if (pBuffer[beginIndex - 1] == 0)//0x00000001
                        {
                            beginIndex -= 1;
                        }
                        var nH264Lenth = beginIndex - freamHeaderLength;

                        pH264 = pBuffer.Skip(freamHeaderLength).Take(nH264Lenth).ToList();
                        action(pH264.ToArray(), false, true, false);
                        pBuffer = pBuffer.Skip(beginIndex).ToArray();
                        goto begin;
                    }
                    else
                    {
                        action(pBuffer.ToArray(), true, false, false);
                        break;
                    }
                }
                break;
            }
        }

        /// <summary>  
        /// 報告指定的 System.Byte[] 在此實例中的第一個匹配項的索引。  
        /// </summary>  
        /// <param name="srcBytes">被執行查找的 System.Byte[]。</param>  
        /// <param name="searchBytes">要查找的 System.Byte[]。</param>  
        /// <returns>若是找到該字節數組,則爲 searchBytes 的索引位置;若是未找到該字節數組,則爲 -1。若是 searchBytes 爲 null 或者長度爲0,則返回值爲 -1。</returns>  
        private static int IndexOf(byte[] srcBytes, byte[] searchBytes, int startIndex = 0)
        {
            if (srcBytes == null) { return -1; }
            if (searchBytes == null) { return -1; }
            if (srcBytes.Count() == 0) { return -1; }
            if (searchBytes.Length == 0) { return -1; }
            if (srcBytes.Count() < searchBytes.Length) { return -1; }
            for (int i = startIndex; i < srcBytes.Count() - searchBytes.Length + 1; i++)
            {
                if (srcBytes[i] == searchBytes[0])
                {
                    if (searchBytes.Length == 1) { return i; }
                    bool flag = true;
                    for (int j = 1; j < searchBytes.Length; j++)
                    {
                        if (srcBytes[i + j] != searchBytes[j])
                        {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) { return i; }
                }
            }
            return -1;
        }
View Code
相關文章
相關標籤/搜索