P2P直播SDK在智能门铃产品的集成说明 v1.1
pgLibLiveMulti P2P直播SDK在智能门铃产品的集成说明 v1.1
一. 概述
1. 目的
本文说明穿透科技的“pgLibLiveMulti P2P直播SDK”(下文简称P2P直播SDK)在智能门铃产品上应用时的集成方法。包括,P2P直播SDK的功能特性对智能门铃产品的符合度、手机APP端、门铃设备端的集成步骤样例。
2. P2P直播SDK的特性符合度
P2P直播SDK的以下功能可以应用到智能门铃产品中:
1) 视频流传输:手机APP观看门铃设备的实时视频流图像
2) 音频对讲:手机APP与门铃设备之间进行双向音频对讲
3) 消息传输:手机APP发送控制指令给门铃设备(双向自定义消息传输)
4) 视频截图:手机APP从门铃设备的实时视频流中抓取一张图片
5) 手机端视频录制:手机APP录制门铃设备的实时视频流和音频流到视频文件中
6) 手机端视频文件回放:手机APP回放录制的视频文件(P2P直播SDK提供了手机端的视频文件播放器功能)。
7) 设备发现连接:当没有外网时,手机APP和门铃设备在同一个局域网(或者手机WIFI连接到门铃设备的WIFI热点上),手机APP通过广播发现门铃设备,并与门铃设备建立连接。建立连接后,可以进行视频、音频和消息的通信。
8) 门铃端的视频录制:在门铃设备端把视频流录制到SD卡中(如果有SD卡的话,可选功能)
9) 门铃端的视频文件回放:手机APP观看门铃设备SD卡中的视频文件的回放视频流(如果有SD卡的话,可选功能)
3. P2P直播SDK的两端
P2P直播SDK包含“播放端”和“采集端”两套功能和API接口。在智能门铃产品中,手机APP使用P2P直播SDK的“播放端”功能和接口,门铃设备使用P2P直播SDK的“采集端”功能和接口。
二. 手机APP端(Android)的集成
1. SDK包的介绍
Android端的P2P直播SDK的目录结构:
1) Demo目录:Eclipse+ADT开发环境的demo程序
2) DemoForAS目录:Android Studio开发环境的demo程序
3) DemoApp目录:编译好的demo程序的apk
4) Doc目录:文档存放目录
5) Lib目录:Eclipse+ADT开发环境的SDK库工程(Android Studio开发环境的SDK库文件已经作为Module添加到demo程序的开发工程中)
2. 播放端API调用步骤顺序说明
1) API调用的主流程:
(a) 创建播放端实例对象:Live = new pgLibLiveMultiRender()
(b) 注册事件回调接口:Live.SetEventListener();
(c) 初始化播放端实例:Live.Initialize()
(d) 创建视频图像显示的View对象:Wnd = (SurfaceView)pgLibLiveMultiView.Get()
(e) 连接门铃设备端:Live.Connect()
(f) 与门铃设备之间进行消息通信:Live.MessageSend()
(g) 打开视频流播放、音频对讲:Live.VideoStart() 和 Live.AudioStart()
(h) 关闭视频流播放、音频对讲:Live.VideoStop() 和 Live.AudioStop()
(i) 步骤(g)到(h),可以重复操作多次……
(j) 断开与门铃设备的连接:Live.Disconnect()
(k) 步骤(e)到(j),可以重复操作多次……
(l) 释放视频图像显示的View对象:pgLibLiveMultiView.Release(Wnd)
(m) 清理释放播放端实例:Live.Clean()
2) 消息传输Live.MessageSend(),视频流播放Live.VideoStart()和Live.VideoStop(),音频对讲Live.AudioStart()和Live.AudioStop(),这个三个功能是相互独立的。在建立连接Live.Connect()之后和断开连接Live.Disconnect()之前,它们可以单独调用。
3) 局域网的设备发现搜索Live.LanScanStart(),在实例初始化Live.Initialize()之后,且实例清理Live.Clean()之前,可以随时调用。
4) 抓拍视频流的照片Live.VideoCamera(),在视频流开始播放Live.VideoStart(),且收到有效的视频关键帧(VideoStatus统计事件的keyfrmcount参数大于0)之后,才能调用。
5) 录制视频流Live.RecordStart()和Live.RecordStop(),在视频流开始播放Live.VideoStart()之后,且播放停止Live.VideoStop()之前,才能调用。
3. 播放端API调用的代码样例
完整的样例代码,请参考SDK包中的demo程序pgLiveMultiRender。
1) 创建P2P直播实例对象:
pgLibLiveMultiRender m_Live = new pgLibLiveMultiRender();
SurfaceView m_Wnd = null; // 显示视频图像的SurfaceView对象
2) 注册事件上报回调接口:
// 定义事件上报回调接口
private pgLibLiveMultiRender.OnEventListener m_OnEvent = new pgLibLiveMultiRender.OnEventListener() {
@Override
public void event(String sAct, String sData, String sCapID) {
if (sAct.equals(“VideoStatus”)) {
// 上报视频流统计信息
}
else if (sAct.equals(“Connect”)) {
// 连接门铃设备成功
}
else if (sAct.equals(“Message”)) {
// 接收到门铃设备发送的消息
}
else if (sAct.equals(“LanScanResult”)) {
// 接收到局域网设备发现搜索的结果
// 在sData参数中包含搜索到的门铃设备的P2P ID和局域网IP地址、
}
else if ( . . . ) {
处理其他事件的分支 ….
}
}
}
// 在APP初始化或Activity初始化的时候,注册事件上报回调
protected void onCreate(Bundle savedInstanceState) {
. . .
// 注册事件上报回调
m_Live.SetEventListener(m_OnEvent);
. . .
}
3) 初始化P2P直播实例:
protected void onCreate(Bundle savedInstanceState) {
. . .
// 注册事件上报回调
m_Live.SetEventListener(m_OnEvent);
// 初始化播放端实例,其中x.x.x.x为服务器的IP地址
int iErr = m_Live.Initialize(“ANDROID_DEMO”, “1111”, “x.x.x.x:7781”, “”, 1, “(Debug){1}”, this);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.Initialize failed! iErr=” + iErr);
return;
}
// 创建显示视频图像的SurfaceView
// 如果需要创建多个SurfaceView时,多次调用pgLibLiveMultiView.Get()传入的View名称必须不同。
m_Wnd = (SurfaceView)pgLibLiveMultiView.Get(“view0”);
// 添加SurfaceView到Layout布局中。
m_View = (LinearLayout)findViewById(R.id.layoutVideo);
m_View.addView(m_Wnd);
m_Wnd.setVisibility(View.VISIBLE);
. . .
}
4) 连接门铃设备端:
// 连接门铃设备,传入参数“device001”为门铃设备的P2P ID。
// 连接成功后,事件上报回调会上报sAct为 “Connect” 的事件。
iErr = m_Live.Connect(“device001”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.Connect failed! iErr=” + iErr);
}
5) 发送自定义消息给门铃设备:
// 发送自定义消息给门铃设备,传入参数“device001”为门铃设备的P2P ID。
iErr = m_Live.MessageSend(“device001”, “The message string”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.MessageSend failed! iErr=” + iErr);
}
6) 播放门铃设备的视频流:
// 连接并播放门铃设备的视频流
// 传入参数为:门铃设备的P2P ID,视频流ID,以及m_Wnd为显示视频图像的SurfaceView对象
iErr = m_Live.VideoStart(“device001”, 0, “”, m_Wnd);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.VideoStart failed! iErr=” + iErr);
}
7) 打开与门铃设备的音频对讲:
// 打开音频对讲
// 传入参数为:门铃设备的P2P ID,音频流ID
iErr = m_Live.AudioStart(“device001”, 0, “”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.AudioStart failed! iErr=” + iErr);
}
// 设置音频和视频延时同步
// 传入参数为:门铃设备的P2P ID,音频流ID,需要同步的视频流ID
m_Live.AudioSyncDelay(“device001”, 0, 0);
8) 从视频流中抓一张照片:
// 从视频流中抓一张照片
// 传入参数为:门铃设备的P2P ID,视频流ID,保存照片的文件路径
// 必须在VideoStart()之后,而且在“VideoStatus”事件的参数keyfrmcount大于0时调用
iErr = m_Live.VideoCamera(“device001”, 0, “/sdcard/Download/liverender.jpg”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.VideoCamera failed! iErr=” + iErr);
}
9) 开始录制视频/音频流:
// 开始录制与门铃设备之间的视频流和音频流
// 传入参数为:门铃设备的P2P ID,保存媒体文件的文件路径,视频流ID,音频流ID
// 必须在VideoStart()和AudioStart()之后,VideoStop()和AudioStop() 之前调用
// 如果不想录制视频流或者音频流,视频流ID或音频流ID传入-1
iErr = m_Live.RecordStart(“device001”, “/sdcard/Download/liverender.mp4”, 0, 0);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.RecordStart failed! iErr=” + iErr);
}
10) 停止录制视频/音频流:
// 停止录制与门铃设备之间的视频流和音频流
// 传入参数为:门铃设备的P2P ID
// 必须在VideoStart()和AudioStart()之后,VideoStop()和AudioStop() 之前调用
iErr = m_Live.RecordStop(“device001”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.RecordStop failed! iErr=” + iErr);
}
11) 关闭视频流播放:
// 关闭与门铃设备的视频流
// 传入参数为:门铃设备的P2P ID,视频流ID
iErr = m_Live.VideoStop(“device001”, 0);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.VideoStop failed! iErr=” + iErr);
}
12) 关闭音频对讲:
// 关闭与门铃设备的音频对讲
// 传入参数为:门铃设备的P2P ID,音频流ID
iErr = m_Live.AudioStop(“device001”, 0);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.AudioStop failed! iErr=” + iErr);
}
13) 断开与门铃设备的连接:
// 断开门铃设备,传入参数“device001”为门铃设备的P2P ID。
// 断开后,事件上报回调会上报sAct为 “Disconnect” 的事件。
iErr = m_Live.Disconnect(“device001”);
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.Disconnect failed! iErr=” + iErr);
}
14) 清理释放播放端对象:
// 在APP退出前或者Activity销毁前,清理释放播放端对象。
public void onDestroy() {
. . .
// 释放显示视频的SurfaceView
if (m_Wnd != null) {
m_View.removeView(m_Wnd);
pgLibLiveMultiView.Release(m_Wnd);
m_View = null;
m_Wnd = null;
}
// 清理释放播放端对象
m_Live.Clean();
. . .
}
15) 触发局域网的设备发现搜索:
// 开始局域网的设备的发现搜索
// 必须在Initialize()之后和Clean()之前调用,搜索的结果在sAct为“LanScanResult”的事件回调中返回。
iErr = m_Live.LanScanStart();
if (iErr != pgLibLiveMultiError.PG_ERR_Normal) {
Log.d(“pgLiveRander”, “Live.LanScanStart failed! iErr=” + iErr);
}
三. 手机APP端(IOS)的集成
1. SDK包的介绍
IOS端的P2P直播SDK的目录结构:
1) Demo目录:demo程序
2) Doc目录:文档存放目录
3) Lib目录:framework库文件存在目录。其中pgDybLiveMulti.framework为模拟器和真机合并的库,pgDybLiveMulti_device.framework为只包含真机的库。开发者提交AppStore的时候,需要使用pgDybLiveMulti_device.framework进行编译打包。
2. 播放端API调用步骤顺序说明
1) API调用的主流程:
(b) 创建事件回调接口:LiveEvent = [[demoLiveEvent alloc] init:self]
(a) 创建播放端实例对象:Live = [[pgLibLiveMultiRender alloc] init:LiveEvent]
(c) 初始化播放端实例:[Live Initialize]
(d) 创建视频图像显示的View对象:View = [pgLibLiveMultiView Get]
(e) 连接门铃设备端:[Live Connect]
(f) 与门铃设备之间进行消息通信:[Live MessageSend]
(g) 打开视频流播放、音频对讲:[Live VideoStart] 和 [Live AudioStart]
(h) 关闭视频流播放、音频对讲:[Live VideoStop] 和 [Live AudioStop]
(i) 步骤(g)到(h),可以重复操作多次……
(j) 断开与门铃设备的连接:[Live Disconnect]
(k) 步骤(e)到(j),可以重复操作多次……
(l) 释放视频图像显示的View对象:[pgLibLiveMultiView Release:View]
(m) 清理释放播放端实例:[Live Clean]
2) 消息传输[Live MessageSend],视频流播放[Live VideoStart]和[Live VideoStop],音频对讲[Live AudioStart]和[Live AudioStop],这个三个功能是相互独立的。在建立连接[Live Connect]之后和断开连接[Live Disconnect]之前,它们可以单独调用。
3) 局域网的设备发现搜索[Live LanScanStart],在实例初始化[Live Initialize]之后,且实例清理[Live Clean]之前,可以随时调用。
4) 抓拍视频流的照片[Live VideoCamera],在视频流开始播放[Live VideoStart],且收到有效的视频关键帧(VideoStatus统计事件的keyfrmcount参数大于0)之后,才能调用。
5) 录制视频流[Live RecordStart()和[Live RecordStop],在视频流开始播放[Live VideoStart]之后,且播放停止[Live VideoStop]之前,才能调用。
3. 播放端API调用的代码样例
完整的样例代码,请参考SDK包中的demo程序pgLiveMultiRender。
1) 实现事件回调接口
@implementation demoLiveEvent
-(id)init:(AppDelegate*) App
{
if (self = [super init]) {
m_App = App;
}
return self;
}
-(void)OnEvent:(NSString *)sAction data:(NSString *)sData capid:(NSString *)sCapID
{
[m_App OnEventProc:sAction data:sData capID:sCapID];
}
@end
// 定义事件上报回调接口
-(void)OnEventProc:(NSString*)sAct data:(NSString*)sData capID:(NSString*)sCapID
{
if ([sAct isEqualToString:@”VideoStatus”]) {
// 上报视频流统计信息
}
else if ([sAct isEqualToString:@”Connect”]) {
// 连接门铃设备成功
}
else if ([sAct isEqualToString:@”Message”]) {
// 接收到门铃设备发送的消息
}
else if ([sAct isEqualToString:@”LanScanResult”]) {
// 接收到局域网设备发现搜索的结果
// 在sData参数中包含搜索到的门铃设备的P2P ID和局域网IP地址、
}
else if ( . . . ) {
处理其他事件的分支 ….
}
}
2) 创建P2P直播实例对象:
m_LiveEvent = [[demoLiveEvent alloc] init:self];
m_Live = [[pgLibLiveMultiRender alloc] init:m_LiveEvent];
3) 初始化P2P直播实例:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
. . .
// 初始化播放端实例,其中x.x.x.x为服务器的IP地址
// Create live view.
int iErr = [m_Live Initialize:@”ios_test” pass:@”” svrAddr:@”x.x.x.x:7781″ relayAddr:@”” p2pTryTime:1 initParam:@””];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”pgLibLiveMulti: initialize failed! iErr=%d”, iErr);
return NO;
}
. . .
// 创建显示视频图像的SurfaceView
// 如果需要创建多个SurfaceView时,多次调用[pgLibLiveMultiView Get]传入的View名称必须不同。
m_pView = [pgLibLiveMultiView Get:@”View0″];
. . .
// 添加SurfaceView到Layout布局中。
[m_pScrollView addSubview:m_pView]
. . .
}
4) 连接门铃设备端:
// 连接门铃设备,传入参数“device001”为门铃设备的P2P ID。
// 连接成功后,事件上报回调会上报sAct为 “Connect” 的事件。
iErr = [m_Live Connect:@”device001″];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.Connect failed! iErr=%d”, iErr);
}
5) 发送自定义消息给门铃设备:
// 发送自定义消息给门铃设备,传入参数“device001”为门铃设备的P2P ID。
iErr = [m_Live MessageSend:@”device001″ data:@”The message string”];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.MessageSend failed! iErr=%d”, iErr);
}
6) 播放门铃设备的视频流:
// 连接并播放门铃设备的视频流
// 传入参数为:门铃设备的P2P ID,视频流ID,以及m_Wnd为显示视频图像的SurfaceView对象
iErr = [m_Live VideoStart:@”device001″ videoID:0 param:@”” nodeView:m_pView];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.VideoStart failed! iErr=%d”, iErr);
}
7) 打开与门铃设备的音频对讲:
// 打开音频对讲
// 传入参数为:门铃设备的P2P ID,音频流ID
iErr = [m_Live AudioStart:@”device001″ audioID:0 param:@””];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.AudioStart failed! iErr=%d”, iErr);
}
// 设置音频和视频延时同步
// 传入参数为:门铃设备的P2P ID,音频流ID,需要同步的视频流ID
[m_Live AudioSyncDelay:@”device001″ audioID:0 videoID:0];
8) 从视频流中抓一张照片:
// 从视频流中抓一张照片
// 传入参数为:门铃设备的P2P ID,视频流ID,保存照片的文件路径
// 必须在VideoStart之后,而且在“VideoStatus”事件的参数keyfrmcount大于0时调用
// 获取document路径
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentDirectory = [directoryPaths objectAtIndex:0];
// 构造jpg文件的路径
NSString* jpgPath = [documentDirectory stringByAppendingPathComponent:@”pgliverender.jpg”];
iErr = [m_Live VideoCamera:@”device001″ videoID:0 jpgPath:jpgPath];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.VideoCamera failed! iErr=%d”, iErr);
}
9) 开始录制视频/音频流:
// 开始录制与门铃设备之间的视频流和音频流
// 传入参数为:门铃设备的P2P ID,保存媒体文件的文件路径,视频流ID,音频流ID
// 必须在VideoStart和AudioStart之后,VideoStop和AudioStop 之前调用
// 如果不想录制视频流或者音频流,视频流ID或音频流ID传入-1
// 获取document路径
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentDirectory = [directoryPaths objectAtIndex:0];
// 构造mp4文件的路径
NSString* mp4Path = [documentDirectory stringByAppendingPathComponent:@”liverender.mp4″];
iErr = [m_Live RecordStart:@”device001″ aviPath:mp4Path videoID:0 audioID:0];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.RecordStart failed! iErr=%d”, iErr);
}
10) 停止录制视频/音频流:
// 停止录制与门铃设备之间的视频流和音频流
// 传入参数为:门铃设备的P2P ID
// 必须在VideoStart 和AudioStart之后,VideoStop和AudioStop之前调用
iErr = [m_Live RecordStop:@”device001″];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.RecordStop failed! iErr=%d”, iErr);
}
11) 关闭视频流播放:
// 关闭与门铃设备的视频流
// 传入参数为:门铃设备的P2P ID,视频流ID
iErr = [m_Live VideoStop:@”device001″ videoID:0];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.VideoStop failed! iErr=%d”, iErr);
}
12) 关闭音频对讲:
// 关闭与门铃设备的音频对讲
// 传入参数为:门铃设备的P2P ID,音频流ID
iErr = [m_Live AudioStop:@”device001″ audioID:0];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.AudioStop failed! iErr=%d”, iErr);
}
13) 断开与门铃设备的连接:
// 断开门铃设备,传入参数“device001”为门铃设备的P2P ID。
// 断开后,事件上报回调会上报sAct为 “Disconnect” 的事件。
iErr = [m_Live Disconnect:@”device001″];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.Disconnect failed! iErr=%d”, iErr);
}
14) 清理释放播放端对象:
// 在APP退出前,清理释放播放端对象。
-(void)applicationWillTerminate:(UIApplication *)application
{
. . .
// 释放显示视频的SurfaceView
if (m_pView != nil) {
[m_pView removeFromSuperview];
[pgLibLiveMultiView Release:m_pView];
m_pView = nil;
}
// 清理释放播放端对象
[m_Live Clean];
. . .
}
15) 触发局域网的设备发现搜索:
// 开始局域网的设备的发现搜索
// 必须在Initialize之后和Clean之前调用,搜索的结果在sAct为“LanScanResult”的事件回调中返回。
iErr = [m_Live LanScanStart];
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
NSLog(@”Live.LanScanStart failed! iErr=%d”, iErr);
}
四. 门铃设备端的集成
1. SDK包的介绍
门铃设备端的P2P直播SDK的目录结构(以嵌入式Linux系统或LiteOS系统为例):
1) Demo目录:demo程序代码工程
3) DemoApp目录:编译好的demo程序可执行文件(LiteOS系统上没法编译单个独立的执行程序,必须把demo程序的代码合并入整个系统的代码工程,编译生成整个系统的程序映像)
4) Doc目录:文档的存放目录
5) Include目录:SDK的C/C++头文件的存放目录
6) Lib目录:SDK的库文件的存放目录
2. 采集端API调用步骤顺序说明
1) API调用的主流程:
(a) 注册日志输出接口和事件回调函数:pgLiveMultiCaptureSetCallback()
(b) 初始化采集端实例:pgLiveMultiCaptureInitialize()
(c) 打开视频流直播、音频对讲监听:pgLiveMultiCaptureVideoStart() 和 pgLiveMultiCaptureAudioStart()
(d) 关闭视频流直播、音频对讲监听:pgLiveMultiCaptureVideoStop() 和 pgLiveMultiCaptureAudioStop()
(e) 步骤(c)到(d),可以重复操作多次……
(f) 发送自定义消息给所有已连接的播放端:pgLiveMultiCaptureNotifySend()
(g) 发送自定义消息给指定已连接的播放端:pgLiveMultiCaptureMessageSend()
(h) 清理释放播放端实例:pgLiveMultiCaptureCleanup()
2) 视频流直播开始pgLiveMultiCaptureVideoStart(),在实例初始化成功后就可以调用。而且可以一直保持视频流的直播状态,等待播放端连接并播放视频流。也可以根据需要,随时调用pgLiveMultiCaptureVideoStop()关闭视频流直播,再调用pgLiveMultiCaptureVideoStart()重新打开。
3) 音频对讲监听开始pgLiveMultiCaptureAudioStart(),在实例初始化成功后就可以调用。而且可以一直保持音频对讲的监听状态,等待播放端连接并建立对讲。也可以根据需要,随时调用pgLiveMultiCaptureAudioStop()关闭音频对讲监听,再调用pgLiveMultiCaptureAudioStart()重新打开。
4) 在事件回调函数中,接收到sAction参数为“RenderJoin”的事件说明某个播放与本采集端建立了连接,而回调函数的sRenID参数就是此播放端的P2P ID。接收到sAction参数为“RenderLeave”的事件说明某个播放端与本采集端断开了连接,回调函数的sRenID参数就是此播放端的P2P ID。
5) 在实例初始化pgLiveMultiCaptureInitialize()之后,随时可以调用pgLiveMultiCaptureRenderEnum()列举当前已经连接到本采集端的所有播放端的P2P ID。
6) 在实例初始化pgLiveMultiCaptureInitialize()之后,随时可以调用pgLiveMultiCaptureNotifySend()给当前已经连接到本采集端的所有播放端发送自定义消息。
3. 采集端API调用的代码样例
完整的样例代码,请参考SDK包中的demo程序demoLiveMultiCapture。
1) Lite OS系统需要初始化C++的全局对象(Linux不需要此步骤):
// LiteOS系统的C++支持.
extern char __init_array_start__, __init_array_end__;
// 声明LOS_CppSystemInit(),也可以通过包含头文件 ‘los_cppsupport.h’来声明
#define NO_SCATTER 2
extern int LOS_CppSystemInit(unsigned long initArrayStart, unsigned long initArrayEnd, int flag);
// demo程序入口函数
int app_init()
{
// 初始化C++全局对象。
LOS_CppSystemInit((unsigned long)&__init_array_start__, (unsigned long)&__init_array_end__, NO_SCATTER);
. . .
}
2) 注册日志输出回调函数和事件上报回调函数:
// 日志输出回调函数
void LogOutput(unsigned int uLevel, const char* lpszOut)
{
printf(“%s\n”, lpszOut);
}
// 事件上报回调函数
void EventProc(unsigned int uInstID, const char* lpszAction, const char* lpszData, const char* lpszRenID)
{
if (strcmp(lpszAction, “VideoStatus”) == 0) {
// 上报视频流统计信息
}
else if (strcmp(lpszAction, “Message”) == 0) {
// 接收到手机APP发送的消息
}
else if (strcmp(lpszAction, “RenderJoin”) == 0) {
// 手机APP端连接上门铃设备
}
else if (strcmp(lpszAction, “RenderLeave”) == 0) {
// 手机APP端断开门铃设备
}
else if ( . . . ) {
处理其他事件的分支 ….
}
}
// demo程序入口函数
int app_init()
{
. . .
// 注册回调函数
pgLiveMultiCaptureSetCallback(LogOutput, EventProc)
. . .
}
3) 初始化采集端实例:
// 初始化采集端实例,传入参数为:门铃设备的P2P ID,P2P服务器的地址等。
// 初始化成功后,输出采集端实例的ID保存在变量uInstID中。
unsigned int uInstID = 0;
int iErr = pgLiveMultiCaptureInitialize(&uInstID, “device001”, “”, “x.x.x.x:7781”, “”, 3, “(Debug){1}”)
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“Init peergine module failed.\n”);
}
4) 发送自定义消息给所有连接的手机APP:
// 发送自定义消息给所有连接的手机APP
iErr = pgLiveMultiCaptureNotifySend(uInstID, “The message string”);
if (iErr !=PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureNotifySend, iErr=%d\n”, iErr);
}
5) 开始视频流直播:
// 构造视频流的参数字符串
// 指定参数:视频编码类型、分辨率模式、帧率(帧间隔)、码率、视频源编号、最大允许的播放端数量等
char szVideoParam[256] = { 0 };
sprintf(szVideoParam, “(Code){3}(Mode){3}(Rate){40}(BitRate){500}(CameraNo){0}(MaxStream){3}”);
// 开始视频流直播,传入参数为:采集端实例ID、视频流ID,视频流参数
iErr = pgLiveMultiCaptureVideoStart(uInstID, 0, szVideoParam, 0);
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureVideoStart, iErr=%d\n”, iErr);
}
6) 开始音频对讲侦听:
// 开始音频对讲侦听,传入参数为:采集端实例ID,音频流ID
iErr = pgLiveMultiCaptureAudioStart(uInstID, 0, “”);
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureAudioStart, iErr=%d\n”, iErr);
}
7) 停止视频流直播:
// 停止视频流直播,传入参数为:采集端实例ID,视频流ID
iErr = pgLiveMultiCaptureVideoStop(uInstID, 0);
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureVideoStop, iErr=%d\n”, iErr);
}
8) 停止音频对讲侦听:
// 停止音频对讲侦听,传入参数为:采集端实例ID,音频流ID
iErr = pgLiveMultiCaptureAudioStop(uInstID, 0)
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureAudioStop, iErr=%d\n”, iErr);
}
9) 列举所有已经连接的手机APP的P2P ID:
// 循环列举所有已经连接的手机APP的P2P ID。
char szRenID[128] = {0};
int iIndex = 0;
while (1) {
int iErr = pgLiveMultiCaptureRenderEnum(uInstID, iIndex, szRenID, sizeof(szRenID));
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
break;
}
printf(“pgLiveMultiCaptureRenderEnum, Index=%d, RenID=%s\n”, iIndex, szRenID);
iIndex++;
}
10) 强制断开与手机APP的连接:
// 强制断开手机APP,传入参数为:采集端实例ID,手机APP端的P2P ID。
iErr =pgLiveMultiCaptureRenderReject(uInstID, “ANDROID_DEMO”);
if (iErr != PG_LIVE_MULTI_ERR_Normal) {
printf(“pgLiveMultiCaptureRenderReject, iErr=%d\n”, iErr);
}
11) 清理释放播放端对象:
// 程序退出前,清理释放采集端对象。
pgLiveMultiCaptureCleanup(uInstID)
4. 视频流对接的细节
1) 视频流对接代码在demo程序的位置:
打开SDK包中的Demo/硬件方案类型/demoLiveMultiCapture目录,下图中蓝色框中的代码文件与视频流对接相关。
2) SDK的视频流输入接口:
SDK的视频流输入接口在SDK包的Include/pgLibDevVideoIn.h头文件中声明。
其中PG_DEV_VIDEO_IN_CALLBACK_S结构声明了控制视频源的回调接口:
在demoLiveMultiCapture程序的callbackVideo.cpp文件中实现了回调接口的函数,并把回调接口注册到SDK。
回调函数VideoInOpen() 打开摄像头的视频采集和编码。
回调函数VideoInClose() 关闭摄像头的视频采集和编码。
回调函数VideoInCtrl() 触发编码器立即产生一个视频关键帧(为了加快手机APP端的视频显示)
经过摄像头采集获取到视频帧数据,并经过编码后,调用pgDevVideoInCaptureProc()函数把视频帧数据输入到SDK里面。pgDevVideoInCaptureProc() 在Include/pgLibDevVideoIn.h头文件中声明:
在demoLiveMultiCapture程序的callbackVideo.cpp文件中,经过摄像头采集和编码得到视频帧数据后,输入到SDK里面。
回调函数VideoInOpen()的返回值与pgDevVideoInCaptureProc()函数的iDevID参数的值必须相等。
3) 几个API函数接口的参数的对应关系:
采集端在应用层面调用SDK的pgLiveMultiCaptureVideoStart()函数时,SDK调用VideoInOpen()回调函数。
采集端在应用层面调用SDK的pgLiveMultiCaptureVideoStop()函数时,SDK调用VideoInClose()回调函数。
播放端(手机APP)调用VideoStart()开始连接播放视频流时,SDK调用VideoInCtrl()回调函数,触发编码器立即生成一个关键帧,开发者需要把这个控制动作适配到编码器的接口上。
pgLiveMultiCaptureVideoStart()的sParam参数的视频源编号(CameraNo){}由SDK传递给VideoInOpen()的uDevNO参数。
pgLiveMultiCaptureVideoStart()的sParam参数的分辨率模式(Mode){}由SDK转换成所对应的分辨率的宽和高后,分别传递给VideoInOpen()的uWidth和uHeight参数。开发者通过调用pgDevVideoInCaptureProc()输入给SDK的视频帧的实际分辨率必须与此参数指定的一致。
pgLiveMultiCaptureVideoStart()的sParam参数的帧间隔时间(Rate){}由SDK传递给VideoInOpen()的uFrmRate参数。
pgLiveMultiCaptureVideoStart()的sParam参数的码率(BitRate){}由SDK传递给VideoInOpen()的uBitRate参数。
pgLiveMultiCaptureVideoStart()的sParam参数的(Code){}所指定的视频编码类型与pgDevVideoInCaptureProc()的uFormat指定的视频编码类型必须一致(两者所传入的参数值不一定相同,但其代表的编码类型必须一致)。这点需要开发者自行保证其一致性。而且,开发者通过调用pgDevVideoInCaptureProc()输入给SDK的视频帧的编码类型必须与此参数指定的一致。
4) 对输入到SDK的视频帧结构的要求:
P2P直播SDK对从外部输入到SDK的H.264、H.265视频帧的帧结构的要求如下:
调用pgDevVideoInCaptureProc()函数一次输入一个完整的帧数据。不能对帧进行分片,也不能一次输入多帧数据。
H.264编码的每个关键帧的图像SLICE最前必须包含SPS + PPS,例如:SPS + PPS + (SEI可选) + SLICE_IDR。
H.265编码的每个关键帧的图像SLICE最前面必须包含VPS + SPS + PPS,例如:VPS + SPS + PPS + (SLICE_BLA_W_LP或 SLICE_BLA_W_RADL或 SLICE_BLA_N_LP或 SLICE_IDR_W_RADL或 SLICE_IDR_N_LP或 SLICE_CRA)
每个SLICE前面必须有Start Code(00 00 01 或者 00 00 00 01)
有些编码器可以直接输出结构符合要求的关键帧(图像SLICE的前面都有SPS+PPS),就可以直接输入到SDK里面。但是,有些编码器只有在开始编码的时候输出一次SPS+PPS,后续的关键帧只输出图像SLICE,这就需要开发者在开始编码时保存SPS+PPS,后续每到关键帧的图像SLICE时,都把SPS+PPS拼接在关键帧的图像SLICE的前面,组合成一个符合输入SDK的帧结构。
demoLiveMultiCapture提供了一个拼接H.264关键帧的样例代码,在代码文件 h264_frame_proc.cpp和h264_frame_proc.h中。这份样例代码中实现了常见的H.264帧结构的拼接,开发者可以参考并使用。这份代码中没有包含的拼接帧的场景,则需要开发者自行实现。
5. 音频流对接的细节
1) 音频流对接代码在demo程序的位置:
打开SDK包中的Demo/硬件方案类型/demoLiveMultiCapture目录,下图中蓝色框中的代码文件与音频流对接相关。
2) SDK的音频流的输入和输出接口:
SDK的音频流输入接口在SDK包的Include/pgLibDevAudioIn.h头文件中声明。
其中PG_DEV_AUDIO_IN_CALLBACK_S结构声明了控制麦克风的回调接口:
SDK的音频流输出接口在SDK包的Include/pgLibDevAudioOut.h头文件中声明。
其中PG_DEV_AUDIO_OUT_CALLBACK_S结构声明了控制扬声器的回调接口:
在demoLiveMultiCapture程序的callbackAudio.cpp文件中实现了回调接口的函数,并把回调接口注册到SDK。
回调函数AudioInOpen() 打开麦克风采集音频。
回调函数AudioInClose() 关闭麦克风。
经过麦克风采集到的音频数据,先输入到callbackAudioQueue转换队列中。
在callbackAudioQueue.cpp中,同步线程从转换队列读出转换了采样率和重新分帧之后的音频采集数据,并调用pgDevAudioInRecordProc()输入到SDK里。pgDevAudioInRecordProc() 在Include/pgLibDevAudioIn.h头文件中声明。
回调函数AudioOutOpen() 打开扬声器播放音频。
回调函数AudioOutClose() 关闭扬声器。
回调函数AudioOutPlay() 给扬声器输出音频数据。
SDK调用AudioOutPlay() 回调函数输出音频播放数据,经过转换队列转换音频的采样率和重新分帧之后,写入到扬声器(声卡)进行播放。音频播放的处理进度需要调用pgDevAudioOutPlayedProc()函数反馈给SDK,以便SDK继续输出后续的音频播放数据。pgDevAudioOutPlayedProc() 在Include/pgLibDevAudioOut.h头文件中声明。
注意:不能在AudioOutPlay() 回调函数里面直接调用pgDevAudioOutPlayedProc(),这样会导致阻塞。需要在另外一个线程或者定时器事件处理里调用pgDevAudioOutPlayedProc()。
3) 几个API函数接口的参数的对应关系:
采集端在应用层面调用SDK的pgLiveMultiCaptureAudioStart()函数后,且播放端(手机APP)调用VideoStart()开始音频对讲时,SDK调用AudioInOpen()和AudioOutOpen() 回调函数打开音频的输入和输出。
播放端(手机APP)调用VideoStop()结束音频对讲时,SDK调用AudioInClose()和AudioOutClose() 回调函数关闭音频的输入和输出。
如果正在音频对讲中,采集端在应用层面调用SDK的pgLiveMultiCaptureAudioStop()函数时,则SDK结束音频对讲,并调用AudioInClose()和AudioOutClose() 回调函数关闭音频的输入和输出。
pgLiveMultiCaptureAudioStart()的sParam参数的视频源编号(MicNo){}由SDK传递给AudioInOpen()的uDevNO参数。
pgLiveMultiCaptureAudioStart()的sParam参数的视频源编号(SpeakerNo){}由SDK传递给AudioOutOpen()的uDevNO参数。
4) 音频数据转换队列的细节:
P2P直播SDK内部进行音频流处理(音量检测,静音抑制,回应消除)时,要求音频数据统一到一种格式上:16 bit采样,采样率为11025,每帧数据的采样点数为441(也就是40毫秒一个音频帧)。
如果设备硬件的音频输入和输出的格式与SDK要求的不一致时,需要对音频数据进行转换。SDK提供了转换队列用于转换音频数据的格式。
转换队列的API在Include/pgLibDevAudioConvert.h头文件中定义。音频数据转换分为输入的转换和输出的转换。
在callbackAudio.cpp的AudioInOpen() 中打开音频输入转换队列:
在callbackAudioQueue.cpp的AudioOutputThreadProc() 线程处理函数中完成音频输入转换处理:
在callbackAudio.cpp的AudioOutOpen() 中打开音频输出转换队列
在callbackAudio.cpp的AudioOutPlay() 执行音频输出转换处理:
5) 关于回音消除
P2P直播SDK包含了软件实现的回音消除功能,此功能默认是开启状态。如果门铃设备的硬件方案已经包含了硬件回音消除功能,则可以关闭SDK里的软件回音消除功能。
pgLiveMultiCaptureAudioStart()函数的sParam参数,增加传入(EchoCancel){0}选项,则可以关闭SDK里的软件回音消除功能(请参考《P2P 直播(多源)模块C语言开发手册vx.x.doc》)。