EasyPusher推流類庫的.NET調用說明
以下內容基於在使用EasyPusher過程中遇到的問題,以及相應的注意事項。
本文主要是基於對C++類庫的二次封裝(便於調試發現問題)以供C#調用以及對一些方法使用.NET實現。
1. C++類庫的二次封裝
較少接觸C+ +在直接調用C+ +類庫的情況下發生錯誤會容易出現不好定位錯誤的情況,在部門同事的提醒下使用C+ +對原有的類庫進行了二次封裝,這樣就可以使用C+ +調用C+ +也就可以方便的調試(eg:查看.NET傳遞的參數是否符合預期)
具體的對C++類庫的封裝及調試可參考博客:C+ +創建DLL並用C#調用且同時實現對DLL的調試
注意事項
項目的VC編譯選項要設置為”多線程(/MT )”,不然可能會出現服務器上運行時找不到DLL的問題
參考鏈接 用VS2010編寫的C++程序,在其他電腦上無法運行,提示缺少mfc100.dll的解決辦法
2. 使用說明
由於二次封裝僅僅是便於調試方便,未對原有類庫的方法進行新的整合,故而使用方法同原生類庫的使用方法是一致的。
該推流模塊主要適用於已經存在音視頻數據流的情況下對音視頻數據流進行推送。
以海康設備為例
- 使用海康SDK獲取音視頻數據
- 使用工具函數對每一幀數據進行處理[判斷數據幀類型/數據轉換]
- 使用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; }