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(@"LibRTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
// -1, /* 无效Key */
// -2, /* 时间错误 */
// -3, /* 进程名称长度不匹配 */
// -4, /* 进程名称不匹配 */
// -5, /* 有效期校验不一致 */
//-6, /* 平台不匹配 */
// -7, /* 授权使用商不匹配 */
// 0, /* 激活成功 */
public static extern int RTPusher_Activate(string license);
[DllImport(@"LibRTPusher.dll")]
/* 创建推送句柄 返回为句柄值 */
public static extern IntPtr RTPusher_Create();
[DllImport(@"LibRTPusher.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(@"LibRTPusher.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(@"LibRTPusher.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(@"LibRTPusher.dll", CallingConvention = CallingConvention.Cdecl)]
/* 停止流传输 */
public static extern uint RTPusher_StopStream(IntPtr pushPtr);
[DllImport(@"LibRTPusher.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; }