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. 使用说明

由于二次封装仅仅是便于调试方便,未对原有类库的方法进行新的整合,故而使用方法同原生类库的使用方法是一致的。
该推流模块主要适用于已经存在音视频数据流的情况下对音视频数据流进行推送。
以海康设备为例

  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[] { 0x00, 0x00, 0x01 };
int freamHeaderLength = ;
int freamLength = ;
if (pBuffer == null || pBuffer.Length <= )
return;
if (existBuffer.Count > )
{
existBuffer.AddRange(pBuffer);
pBuffer = existBuffer.ToArray();
existBuffer.Clear();
}
while (true)
{
begin:
if (pBuffer.Count() == )
break;
if (pBuffer.Count() < )
{
action(pBuffer, true, false, false);
break;
}
pH264.Clear(); if (pBuffer[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x01
&& pBuffer[] == 0xBA/*ps_header*/)
{
//抛弃数据
if (pBuffer.Count() < )
{
action(pBuffer.ToArray(), true, false, false);
break;
}
pBuffer = pBuffer.Skip().ToArray();
goto begin;
} if (pBuffer[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x01
&& (pBuffer[] == 0xBD/*私有包头*/|| pBuffer[] == 0xBC/*psm_header*/))
{
//抛弃数据
freamHeaderLength = + (int)pBuffer[];
freamLength = BitConverter.ToUInt16(new byte[] { pBuffer[], pBuffer[] }, );
var nH264Lenth = freamLength + - freamHeaderLength;
if (pBuffer.Count() < freamHeaderLength + nH264Lenth)
{
action(pBuffer.ToArray(), true, false, false);
break;
}
pBuffer = pBuffer.Skip( + freamLength).ToArray();
goto begin;
} if (pBuffer[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x01
&& (pBuffer[] >= 0xC0 && pBuffer[] <= 0xDF))//pes_audio_header
{
freamHeaderLength = + (int)pBuffer[];
freamLength = BitConverter.ToUInt16(new byte[] { pBuffer[], pBuffer[] }, );
var nH264Lenth = freamLength + - 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( + freamLength).ToArray();
goto begin;
} if (pBuffer[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x01
&& (pBuffer[] >= 0xE0 && pBuffer[] <= 0xEF))//pes_video_header
{
freamHeaderLength = + (int)pBuffer[];//9个为固定的数据包头长度,pBuffer[8]为填充头部分的长度 var beginIndex = IndexOf(pBuffer, searchBytes, freamHeaderLength + ); if (beginIndex != -)
{
if (pBuffer[beginIndex - ] == )//0x00000001
{
beginIndex -= ;
}
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[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x00
&& pBuffer[] == 0x01)//帧数据
{
freamHeaderLength = ; var beginIndex = IndexOf(pBuffer, searchBytes, freamHeaderLength + ); if (beginIndex != -)
{
if (pBuffer[beginIndex - ] == )//0x00000001
{
beginIndex -= ;
}
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 = )
{
if (srcBytes == null) { return -; }
if (searchBytes == null) { return -; }
if (srcBytes.Count() == ) { return -; }
if (searchBytes.Length == ) { return -; }
if (srcBytes.Count() < searchBytes.Length) { return -; }
for (int i = startIndex; i < srcBytes.Count() - searchBytes.Length + ; i++)
{
if (srcBytes[i] == searchBytes[])
{
if (searchBytes.Length == ) { return i; }
bool flag = true;
for (int j = ; j < searchBytes.Length; j++)
{
if (srcBytes[i + j] != searchBytes[j])
{
flag = false;
break;
}
}
if (flag) { return i; }
}
}
return -;
}

EasyPusher推流类库的.NET调用说明的更多相关文章

  1. EasyPusher推流服务接口的.NET导出

    本文是在使用由 EasyDarwin 团队开发的EasyPusher时导出的C++接口的.NET实现 public class EasyPushSDK { public EasyPushSDK() { ...

  2. 创建自己的java类库并加以调用方法

    第一次搞博客,心里有点发慌,记录一下:2018/2/1/   21:33 今天Think In Java第4版 中文版(英文看着可能很耗时),看到了6.1.3 定制工具库这一章节,之前作者调用自己的类 ...

  3. C++CLR类库封装Native类库并用C#调用 - 草稿

    1.创建Native类库 新建项目->其他语言->Visual C++->Win32控制台应用程序->DLL     添加头文件       添加源文件       选择生成路 ...

  4. Delphi 类库(DLL)动态调用与静态调用示例讲解

    在Delphi或者其它程序中我们经常需要调用别人写好的DLL类库,下面直接上示例代码演示如何进行动态和静态的调用方法: { ************************************** ...

  5. OWIN 自宿主模式WebApi项目,WebApi层作为单独类库供OWIN调用

    OWIN是Open Web Server Interface for .NET的首字母缩写,他的定义如下: OWIN在.NET Web Servers与Web Application之间定义了一套标准 ...

  6. [转]vs2010用 boost.python 编译c++类库 供python调用

    转自:http://blog.csdn.net/wyljz/article/details/6307952 VS2010建立一个空的DLL 项目属性中配置如下 链接器里的附加库目录加入,python/ ...

  7. JDK1.5新特性,基础类库篇,调用外部命令类(ProcessBuilder)用法

    一. 背景 ProcessBuilder类是用来创建操作系统进程的.与Runtime.exec相比,它提供了更加方便的方法以创建子进程. 每个ProcessBuilder实例管理着一个进程属性的集合. ...

  8. C#调用EasyPusher推送到EasyDarwin流媒体服务器直播方案及示例代码整理

    博客一:转自:http://blog.csdn.net/u011039529/article/details/70832857 大家好,本人刚毕业程序猿一枚.受人所托,第一次写博客,如有错误之处敬请谅 ...

  9. 从vs2010的UnitTestFramework类库提取私有方法反射调用的方法

    背景 年龄大点的程序员都知道在vs2010中创建单元测试非常的简单,鼠标定位在方法名字,右键创建单元测试,就会创建一个测试方法,即使是在私有方法上也可以创建测试方法. VS2010以后就没这么简单了, ...

随机推荐

  1. 4.client、offset、scroll系列

    client.offset.scroll系列 他们的作用主要与计算盒模型.盒子的偏移量和滚动有关 clientTop 内容区域到边框顶部的距离 ,说白了,就是边框的高度 clientLeft 内容区域 ...

  2. collections中的defaultdict

    用类型 用函数返回值 嵌套的dict from collections import defaultdict def tree(): return defaultdict(tree) c = defa ...

  3. Struts2框架里面action与前端jsp页面进行交互路径问题---》一个对话框里面有很多超链接,进行相应的跳转

    一个对话框里面有很多超链接,右边是点击超链接跳转到的相应页面(在一个页面上就相当于点击该超链接时候,就把该简短页面置顶):这个问题困扰我两天:还请大神给我解决,也没有解决,我仔细对比了相关路径,后面添 ...

  4. L01-RHEL6.5中部署NTP(ntp server + client)

    RHEL6.5集群中部署NTP NTP全称为Network Time Protocol,即网络时间协议.一般在Linux系统中用来同步集群中不同机器的时间. 本文描述的ntp服务部署框架如下图示 如上 ...

  5. JAVA普通内部类的用法

    内部类顾名思义就是定义在一个类的内部 内部类又有普通内部类.方法和域内的内部类.匿名内部类.嵌套内部类 普通内部类的基础用法 class MyClass{ class InnerClass1{ pub ...

  6. BZOJ - 2741 分块维护最大连续异或和

    题意:给定\(a[l...r]\),多次询问区间\([l,r]\)中的最大连续异或和\(a_i⊕a_{i+1}⊕...⊕a_{j},l≤i≤j≤r\) 一眼过去认为是不可做的,但题目给出\(n=1.2 ...

  7. centos文件查找命令

    在使用linux时,经常需要进行文件查找.其中查找的命令主要有find和grep.两个命令是有区的. 区别:(1)find命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访 ...

  8. vue学习(转载)

    vue.js库的基本使用 第一步:下载 官网地址:https://cn.vuejs.org/v2/guide/installation.html 第二步:放到项目的文件目录下 一般新建一个js文件夹, ...

  9. 迁移-Mongodb时间类数据比较的坑

    背景: 拦截件监控时,对于签收的数据需要比较签收时间和实际同步数据的时间来判断  同步时间是在签收前还是签收后.在比较时,用到同步时间syncTime和signTime, signTime从Q9查单获 ...

  10. shiyan 3

    //info.h#ifndef INFO_H #define INFO_H #include <string> using std::string; class Info { public ...