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. webstorm 主题设置 皮肤设置

    推荐个编辑器主题下载的一个网站. Color Themes    网址:http://color-themes.com [点这里直接跳转] 但是,只支持几个编辑器. 各种颜色搭配的主题,随你选择!我个 ...

  2. myeclipse 10 载入新的项目报错Cannot return from outside a function or method

    myeclipse 10 载入新的项目报错Cannot return from outside a function or method 解决方法: 方法一: window -->prefere ...

  3. [置顶] ios 网页中图片点击放大效果demo

    demo功能:点击网页中的图片,图片放大效果的demo.iphone6.1 测试通过. demo说明:通过webview的委托事件shouldStartLoadWithRequest来实现. demo ...

  4. Codeforces Round #337 (Div. 2) C. Harmony Analysis 构造

    C. Harmony Analysis 题目连接: http://www.codeforces.com/contest/610/problem/C Description The semester i ...

  5. CircleWaveProgressBar

    https://github.com/eltld/CircleWaveProgressBar

  6. [Express] Level 1: First Step

    Installing Express Let's start building our new Express application by installing Express. Type the ...

  7. 4K加速普及,8K近在咫尺,下一个是?

        从铺天盖地的厂商宣传到亲戚朋友家的客厅.不可否认4K时代已全面到来------业内人士估计2015年是4K电视的一个突破年.知名市场调查机构StrategyAnalytics对4K电视进行了调 ...

  8. 好记心不如烂笔头之jQuery学习,第一章

    jQuery对象和DOM对象的转换: 1.jquery对象是对象数组,于是乎: var $cr = $('#cr'); var cr = $cr[0]; 2.使用jquery的自带函数: var $c ...

  9. oc-23-static

    #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * ...

  10. iOS开发,hook系统Objective-C的函数

    我们都知道在windows下可以通过API轻松的hook很多消息,IOS也可以实现hook的功能. 建立一个 TestHookObject类 // // TestHookObject.m // Tes ...