博客一:转自:http://blog.csdn.net/u011039529/article/details/70832857
大家好,本人刚毕业程序猿一枚。受人所托,第一次写博客,如有错误之处敬请谅解。本文主要讲解:如何在C#中封装以及调用C++编写的函数,通过对EasyDarwin开源流媒体的EasyPusher为例讲解。
首先,我个人不喜欢复杂的调用,比如很多C#中Marshal类相关的操作。我更愿意把c++代码封装成对象,以对象void*到IntPtr的相互转换来实现。 当然,这种方法对于需要传递复杂的数据结构来说,显然还是有其缺点的,比如:在opencv中,我就不知道如何传递mat图片。
本文将用极少的代码来说明。有源码哦!
//----------------------------------------------------华丽的分割线---------------------------------------------------
用过EasyPusher的人都知道,在一个main函数中,实现了推流。调用十分简单!!!而我们要做的:
第一步:将EasyPusher的main函数构成封装成一个类,暴露可供调用的函数即可,如RTSPPusher类,实现函数见源码。
- <span style="font-size:14px;">class RTSPPusher
- {
- private:
- void* rtspHandle;//rtsp源拉流句柄
- void* pusherHandle;//推流句柄
- char* PusherServerIP;//推送的服务器IP
- int PusherServerPort;//推送的服务器端口
- char* PusherTailName;//推送的rtsp的后缀名
- char* srcRtspAddr; //源rtsp流地址
- EASY_MEDIA_INFO_T* fSourceMediaInfo;//媒体信息
- public:
- RTSPPusher(const char* pushServerIP, int pushServerPort, const char* tailName, const char* sourceRtsp);
- virtual ~RTSPPusher();
- bool OnCreatePusher();
- bool OnClosePusher();
- public:
- //rtsp对象拉流回调
- void OnRtspSrcCall( int _mediatype, char *pbuf, RTSP_FRAME_INFO *frameinfo);
- //推送器回馈消息
- void OnPusherCall(int _id, EASY_PUSH_STATE_T _state, EASY_AV_Frame *_frame);
- };</span>
第二步:建立一个c++的管理方法,其实就是导出函数,用来让外部调用(c++、c#调用)。此处只贴头文件,实现见源码。
- <span style="font-size:14px;">#pragma once
- #include <Windows.h>
- extern "C"
- {
- #define LIB_PUSH_API __declspec(dllexport)
- ///创建并且开启推流
- LIB_PUSH_API void* CreateStartPush(const char* pushServerIP, int pushServerPort, const char* tailName, const char* sourceRtsp);
- //停止推流
- LIB_PUSH_API bool ClosePush(void* senderObjPtr);
- }
- </span>
由此,我们只要调用void* CreateStartPush(const char* pushServerIP, int pushServerPort, const char* tailName, const char* sourceRtsp);函数就能创建一个推流对象,实现推流,并返回此对象的地址void*。在c#中用Intptr保存。至此,c++对EasyPusher的封装就算完成了。当然,如果觉得封装太简单,那就是仁者见仁了,*修改就行啦。
第三步:如何把第二步的c++函数定义代码转换成C#的呢?在此提供一个工具点击打开链接
拷贝代码到C#中,把调用的dll名字改下,如下:(注意:函数我添加了一句:CallingConvention = CallingConvention.Cdecl),自己百度一下吧。
- <span style="font-size:14px;">using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Text;
- namespace ConsolePusher
- {
- public class PusherSDK
- {
- const string dllName = "libPushCplus.dll";
- /// Return Type: void*
- ///pushServerIP: char*
- ///pushServerPort: int
- ///tailName: char*
- ///sourceRtsp: char*
- [System.Runtime.InteropServices.DllImportAttribute(dllName, EntryPoint = "CreateStartPush", CallingConvention = CallingConvention.Cdecl)]
- public static extern System.IntPtr CreateStartPush([System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] string pushServerIP, int pushServerPort, [System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] string tailName, [System.Runtime.InteropServices.InAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] string sourceRtsp);
- /// Return Type: boolean
- ///senderObjPtr: void*
- [System.Runtime.InteropServices.DllImportAttribute(dllName, EntryPoint = "ClosePush", CallingConvention = CallingConvention.Cdecl)]
- [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.I1)]
- public static extern bool ClosePush(System.IntPtr senderObjPtr);
- }
- }
- </span>
第四步:到此,我们封装好了c++,并且得到了c#掉用c++导出函数的方法。我们只需编程就好啦。具体见c#源码的main函数。实现源码点击打开链接
总结:第一次写博客,大家别笑话哈。如过大家觉得还行,我也试试博客,偶尔写写一些平时的积累。这篇博客比较简单,主要点在EasyPusher的使用;导出函数的声明方法;c++函数如何转换为c#。
注:本博客中的代码仅供学习交流使用,如有侵权,请及时删除。还有就是由于工作原因,如有疑问可能无法回复。
博客二:转自:http://www.cnblogs.com/wipphj/p/6114762.html
EasyPusher推流类库的.NET调用说明
以下内容基于在使用EasyPusher过程中遇到的问题,以及相应的注意事项。
本文主要是基于对C++类库的二次封装(便于调试发现问题)以供C#调用以及对一些方法使用.NET实现。
1. C++类库的二次封装
较少接触C+ +在直接调用C+ +类库的情况下发生错误会容易出现不好定位错误的情况,在部门同事的提醒下使用C+ +对原有的类库进行了二次封装,这样就可以使用C+ +调用C+ +也就可以方便的调试(eg:查看.NET传递的参数是否符合预期)
具体的对C++类库的封装及调试可参考博客:C+ +创建DLL并用C#调用且同时实现对DLL的调试
注意事项
参考链接用VS2010编写的C++程序,在其他电脑上无法运行,提示缺少mfc100.dll的解决办法
2. 使用说明
由于二次封装仅仅是便于调试方便,未对原有类库的方法进行新的整合,故而使用方法同原生类库的使用方法是一致的。
该推流模块主要适用于已经存在音视频数据流的情况下对音视频数据流进行推送。
以海康设备为例
- 使用海康SDK获取音视频数据
- 使用工具函数对每一帧数据进行处理[判断数据帧类型/数据转换]
- 使用EasyPusher_PushFrame逐帧推送数据到远程服务器
代码附录
- DLL C#调用
<code class="cs" data-origin=""/// <summary>" id="PreCode" style="margin: 0px 2px; padding: 0px 5px; border: 1px solid rgb(204, 204, 204); display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-weight: bold; white-space: pre; border-radius: 3px; word-wrap: break-word; font-size: 1em; letter-spacing: -1px;"> /// <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); }
- <code class="cs" data-origin=""
工具方法
<code class="cs" data-origin=""<code class="cs" data-origin=""/// <summary>" id="PreCode" style="margin: 0px 2px; padding: 0px 5px; border: 1px solid rgb(204, 204, 204); display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-weight: bold; white-space: pre; border-radius: 3px; word-wrap: break-word; font-size: 1em; letter-spacing: -1px;"> /// <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; }
参考链接
获取更多信息
EasyPusher直播推送技术交流群:465901074(相关代码见群共享文件)
Copyright © EasyDarwin.org 2012-2017