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. Spring Cloud微服务接口这么多怎么调试

    导读 我们知道在微服务架构下,软件系统会被拆分成很多个独立运行的服务,而这些服务间需要交互通信,就需要定义各种各样的服务接口.具体来说,在基于Spring Cloud的微服务模式中,各个微服务会基于S ...

  2. C# 用SoapUI调试WCF服务接口(WCF中包含用户名密码的验证)

    问题描述: 一般调试wcf程序可以直接建一个单元测试,直接调接口. 但是,这次,我还要测试在接口内的代码中看接收到的用户名密码是否正确,所以,单一的直接调用接口方法行不通, 然后就想办法通过soapU ...

  3. 《连载 | 物联网框架ServerSuperIO教程》- 12.服务接口的开发,以及与云端双向交互

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  4. 使用jMeter测试Solr服务接口

    之前一直用ab做简单的服务接口测试,ab功能强悍,使用简单,但是没有生成专题图和表格等功能,因此,我们决定使用jmeter来作为我们测试工具.接下来,我们将详细介绍jmeter使用的步骤,主要包括:j ...

  5. WCF服务接口多,客户端在引用时出错!报WCF The maximum nametable character count quota (16384) has been exceeded while reading XML data错误

    WCF服务接口多,客户端在引用时出错!报WCF The maximum nametable character count quota (16384) has been exceeded while ...

  6. 服务接口API限流 Rate Limit 续

    一.前言 上一篇文章中粗浅的介绍使用Redis和基于令牌桶算法进行对服务接口API限流,本文介绍另一种算法---漏桶算法的应用.Nginx想必大家都有所了解是一个高性能的 HTTP 和反向代理服务器, ...

  7. 服务接口API限流 Rate Limit

    一.场景描述 很多做服务接口的人或多或少的遇到这样的场景,由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统. 也就是面对大流量时,如何进行流量控制? 服务接口的流量 ...

  8. Web Service 一些对外公开的网络服务接口

    商业和贸易: 1.股票行情数据 WEB 服务(支持香港.深圳.上海基金.债券和股票:支持多股票同时查询) Endpoint: http://webservice.webxml.com.cn/WebSe ...

  9. Slickflow.NET 开源工作流引擎基础介绍(一) -- 引擎基本服务接口API介绍

    1. 工作流术语图示                                              图1 流程图形的BPMN图形元素表示 1) 流程模型定义说明流程(Process):是企 ...

随机推荐

  1. jquery 禁止页面滚动-移动端

    禁止 window.ontouchmove=function(e){        e.preventDefault && e.preventDefault();        e.r ...

  2. 定制个性化的FlashPaper生成的文件

    1:找到已安装FlashPaper目录下的子目录Interface下的文件DefaultViewer2.swf,在此swf文件的基础上实现自己的修改. 2:利用swf反编译工具,这里推荐 硕思闪客精灵 ...

  3. java中string与byte[]的转换

    1.string 转 byte[] byte[] midbytes=isoString.getBytes("UTF8"); //为UTF8编码 byte[] isoret = sr ...

  4. ReactNative学习实践--动画初探之加载动画

    学习和实践react已经有一段时间了,在经历了从最初的彷徨到解决痛点时的兴奋,再到不断实践后遭遇问题时的苦闷,确实被这一种新的思维方式和开发模式所折服,react不是万能的,在很多场景下滥用反而会适得 ...

  5. js验证身份证id

    function isCardNo(card) { // 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X var reg = /(^\d{1 ...

  6. Flex中NetConnection与NetStream的关系、及浏览器并发连接数测试[转]

    最近在做一个基于BS结构的视频会议系统,决定采用开源的FluorineFx.net与Flex结合的方法进行开发,前期开发都非常顺利,包括同步白板等.但到了实时视频传输的时候,原本设计是每个客户端可以显 ...

  7. boost.asio源码剖析(五) ---- 泛型与面向对象的完美结合

    有人说C++是带类的C:有人说C++是面向对象编程语言:有人说C++是面向过程与面向对象结合的语言.类似的评论网上有很多,虽然正确,却片面,是断章取义之言. C++是实践的产物,C++并没有为了成为某 ...

  8. ubuntu桌面版打开终端Terminal的几种方法

    1. Ctrl + Alt + T 快捷键直接打开2. 在Ubuntu左上角选择File/Open in Terminal 3. 快捷键alt+F2调出Run a Command,输入gnome-te ...

  9. 关于禁止Android scrollView 因内容变化而自动滚动 android:descendantFocusability

    出现这种情况是因为你让scrollview获得了焦点,所以它才会滚动.如果你百度了以后,你可能会发现有些博客会说让焦点停在固定的一个view中就可以了.这对于不存在刷新的页面确实是可以的,但是当你出现 ...

  10. mysql记录sql执行时间

    1.开启和关闭mysql> set profiling=1;mysql> set profiling=0; information_schema 的 database 会建立一个PROFI ...