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. ACM-ICPC 2018青岛网络赛-H题 Traveling on the Axis

    题目:略(不知道怎么从ZOJ搬题) 地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4054 把这题的每个点分成两种情况 ...

  2. Jupyter Notebook远程服务器配置[转]

    首先要生成密码,打开python终端. In [1]: from IPython.lib import passwd In [2]: passwd() Enter password: Verify p ...

  3. Postman使用手册1——导入导出和发送请求查看响应

    导读: 现在的web和移动开发,常常会调用服务器提供restful接口进行数据请求,为了调试,一般会先用工具进行测试,通过测试后才开始在开发中使用.这里介绍一下如何在chrome浏览器利用postma ...

  4. [转]IOS UIView 之属性篇

    [转载自:IOS UIView 之属性篇 From CSDN] UIView 继承于UIResponder             所遵守的协议有 NSCoding .UIAppearance. UI ...

  5. php获取随机字符串的几种方法

    方法一:shuffle函数(打乱数组)和mt_rand函数(生成随机数,比rand速度快四倍) /** * 获得随机字符串 * @param $len 需要的长度 * @param $special ...

  6. leetcode-551-Student Attendance Record I(判断是否出现连续几个相同字符)

    题目描述: You are given a string representing an attendance record for a student. The record only contai ...

  7. css样式之标签的查找

    css的组成部分:选择器和声明 css的注释: /*这是注释*/ <!DOCTYPE html> <html lang="zh-CN"> <head& ...

  8. Ubuntu16.04安装视觉SLAM环境(ceres-solver)

    1.先在github上下载ceres-solver git clone https://github.com/ceres-solver/ceres-solver.git 2.安装ceres-solve ...

  9. rest-assured之如何指定请求数据(Specifying Request Data 包括请求参数、请求头、cookie等)

    我们除了可以为一个请求指定请求参数之外,还可以指定请求头(header).cookies.请求体(body)以及请求内容类型(content-type)等,下面我们就来一一介绍一下: 一.请求HTTP ...

  10. windows使用putty向远程服务器传送文件

    一.问题产生 对于远程服务器,我习惯把文件写好后直接上传到服务器,也有很多方法可以做到,我现在比较习惯使用putty自带的pscp直接去传. 二.解决办法 1.首先在本地下载的putty文件下看是否有 ...