StreamSDK接入
StreamSDK主要处理和图片视频相关的功能,主要包含直播、回放、云端视频录制、语音对讲、视频通话、抓图、上报报警信息等功能。
StreamSDK-API 列表
StreamSDK 相关的 API 功能说明如下:
API 接口 | 功能说明 |
---|---|
xs_stream_Init | SDK 初始化 |
xs_stream_unInit | SDK 反初始化 |
xs_stream_setDeviceParams | 设置设备参数,主设备在 xs_stream_start 之前就要调用 |
xs_stream_start | 启动流相关服务 |
xs_stream_stop | 停止流相关服务 |
xs_stream_createStream | 创建媒体流 |
xs_stream_inputVideo | 输入视频流 |
xs_stream_inputAudio | 输入音频流 |
xs_stream_destroyStream | 销毁媒体流 |
xs_stream_inputIotMsg | 处理外部消息(IoTSDK 回调的消息) |
xs_stream_postIntelligentAlarm | 上报 AI 报警事件 |
xs_stream_sleep | 休眠接口,低功耗设备使用 |
xs_stream_postTriggerPicture | 异步抓图回复 |
xs_stream_sendCmd | 发送控制命令 |
xs_stream_wakeup | 唤醒接口 ,低功耗设备使用 |
功能详述
初始化
初始化函数必须在其他函数之前调用,初始化的时候需要设置初始化参数以及回调函数指针列表
/**
* @brief 初始化函数,在下面所有的接口调用之前调用
* @param [IN] initConfig: 初始化参数配置
* @param [IN] cbFun: 回调函数指针列表
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_Init(xs_streamConfig_st* initConfig, xs_stream_callbackFun_st* cbFun);
关于初始化参数中缓存大小(initConfig->streamBufferSize)的说明如下(重要):
- 如果设备内存比较紧张(并且上层有缓存),那么可以不设置缓存给设备SDK(streamBufferSize=0),此时设备启动后不要主动塞流,等待SDK的回调来控制是否塞流(xs_startPushLiveStream/xs_stopPushLiveStream),并且开始塞流的时候,根据SDK回调的预录秒数提前塞数据(因为事件发生的时候,视频录制需要预录一部分视频)
- 如果设备内存充足,建议设置缓存给SDK(不小于1M),此时设备启动后可以保持一直塞流,当事件发生或者有人播放时,SDK内部控制事件的视频预录功能。上层应用处理起来会比较简单一些
初始化过程中,需要设置回调函数列表,设备需要处理的回调函数说明:
回调函数 | 说明 |
---|---|
xs_streamLogCallback | SDK日志回调 |
xs_streamMsgCallback | sdk内部消息的回调函数 [设备侧收到此回调之后,直接将参数转给IoTSDK处理即可(xs_iot_handleExternalMsg),不需要关心参数内容] |
xs_triggerPictureCallback | 抓图回调,同步接口,主要用于客户端图片展示、以及其他用途,详情参考 [图片服务]章节 |
xs_triggerPictureAsyncCallback | 抓图回调,异步接口 |
xs_asyncResultCallback | SDK异步处理结果通知,主要用来通知报警图片上传结果 |
xs_talkCallback_st | 音频对讲、视频对讲相关的回调函数 |
xs_playbackCallback_st | 回放本地录像,查询本地录像列表,报警列表相关的回调,详情参考 [本地回放] 章节 |
xs_liveStreamCallback_st | 实时流相关的回调,详情参考 [实时推流]章节 |
xs_statusCallback_st | 状态回调,主要包括设备休眠状态,直播状态等回调 |
设置参数
初始化之后,通过下面接口设置设备参数信息
/**
* @brief 设置设备参数,主设备在 xs_stream_start 之前就要调用
* @param [IN] devAuth: 设备认证信息
* @param [IN] devParam: 设备参数信息
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_setDeviceParams(xs_deviceAuth_st* devAuth, xs_DeviceParams_st* devParam);
音视频服务
SDK 主要提供了音视频的直播、对讲、云存储、本地回放等功能,不负责音视频的本地存储。
实时流
1.创建流资源
使用音视频服务之前必须先调用如下接口先创建音视频资源(有几条流就创建几条),然后启动流媒体服务:
/**
* @brief 创建音视频流对象
* @param [IN] devAuth: 设备认证信息
* @param [IN] lensId: 镜头id,从0开始(用于双目或多目设备)
* @param [IN] streamId: 码流id,从0开始(主码流为0,子码流为1)
* @param [IN] videoParam: 视频参数
* @param [IN] audioParam: 音频参数
* @notice 开始推流之前需要调用此API
* @return 句柄,用于后续视频流操作,返回值=0 为成功,否则为失败的错误码
*/
xs_int32 xs_stream_createStream(xs_deviceAuth_st* devAuth, xs_uint32 lensId, xs_uint32 streamId, const xs_videoEncodeParam_st* videoParam, const xs_audioEncodeParam_st* audioParam);
/**
* @brief 启动服务
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_start();
2.等待SDK回调推流
音视频资源创建成功后,设备等待 SDK 回调控制是否推流:
/**
* @brief 开始推送实时流,收到此回调后,应用层调用 xs_stream_inputVideo 和 xs_stream_inputAudio 接口将实时数据塞入SDK
* @param [IN] streamIndex: 请求流信息
* @param [IN] serviceId:服务ID
*
* @return 0:成功,其他值:失败
*/
typedef xs_int32 (*xs_startPushLiveStream)(const xs_streamIndex_st* streamIndex, xs_int32 serviceId, const PushParams_st* param);
/**
* @brief 停止推送实时流
*
* @param [IN] serviceId:
*
* @return 0:成功,其他值:失败
*/
typedef xs_int32 (*xs_stopPushLiveStream)(xs_int32 serviceId);
注意:设备创建流资源之后,也可以保持持续推流(serviceId 为xs_stream_createStream返回的句柄值),此时就无需响应xs_startPushLiveStream和xs_stopPushLiveStream
3.开始推流
收到xs_startPushLiveStream回调后,调用推流接口塞入实时流,注意 serviceId 要填写正确(此塞流接口和回放数据塞流接口是复用的)
/**
* @brief 输入编码后的的视频帧(直播或者回放),收到播放回调:xs_startPushLiveStream/xs_startPlaybackByTime/xs_startPlaybackByFileName 之后,一直调用此接口塞入视频
* @param [IN] serviceId: 播放回调函数参数中的service id
* @param [IN] videoFrame:视频帧
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_inputVideo(xs_int32 serviceId, const xs_videoInfo_st* videoFrame);
/**
* @brief 输入编码后的的音频帧(直播或者回放),收到播放回调:xs_startPushLiveStream/xs_startPlaybackByTime/xs_startPlaybackByFileName 之后,一 直调用此接口塞入音频
* @param [IN] serviceId: 播放回调函数参数中的service id
* @param [IN] audioFrame:音频帧
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_inputAudio(xs_int32 serviceId, const xs_audioInfo_st* audioFrame);
4.停止推流
收到 xs_stopPushLiveStream 回调后 ,设备停止推送实时流
5.销毁流资源
当设备处于关闭状态后(标准物模型中的powerstate可以控制设备关闭状态),需要停止推流并销毁流资源:
/**
* @brief 销毁音视频流对象
* @param [IN] hdl: xs_stream_createVideoStream 返回的句柄
* @return 0:成功, 其他值:错误码
*/
xs_int32 xs_stream_destroyStream(xs_int32 hdl);
6.示例代码
参考 demo 中音视频推流的流程,demo 中的 PushAVDataProc 函数:
- 从本地私有格式录制的媒体文件中逐帧读取音视频数据,将其通过 xs_stream 相关接口传入 SDK
- demo 中的此部分代码和本地存储的回放是共用的
音视频推流注意点:
- 推流端需要保证传入的音视频时间戳(pts)是同步的,并且是按帧率均匀增长的,误差尽量控制在 150ms 以内
- 视频格式支持 h264/h265, 音频目前支持 g711a(8K)/opus
demo推流示例代码
/*独立线程,循环读取本地视频文件,调用sdk接口推流*/
void *PushAVDataProc(void* args)
{
char threadName[20]={0};
StreamResource_st* ctx = (StreamResource_st*)args;
MediaInfo_st frameheader;
xs_uint64 vpts = 0, apts = 0;
xs_uint32 vseq = 0, aseq = 0;
char filepath[256] = {0};
xs_int32 fd_stream = -1;
xs_uint32 readsize = 0;
unsigned char *framedata = NULL;
sprintf(threadName, "demoPushProc-%d", ctx->serviceId);
pthread_setname_np(pthread_self(), threadName);
/* 读取私有格式录制的测试文件,模拟音视频输入。 测试文件中视频为h265格式,帧率15fps, 音频为g711a格式, 帧率25fps*/
sprintf(filepath, "./resource/media.raw");
LOG_PRINT("[stream:%d-%d-%d][service:%d]start streaming demo proc, path %s\n", ctx->streamIdx.devId,ctx->streamIdx.lensId, ctx->streamIdx.streamId, ctx->serviceId, filepath);
fd_stream = open(filepath, O_RDONLY);
if(fd_stream < 0) {
LOG_PRINT("open file failed,path:%s\n",filepath);
goto CLEAN_UP;
}
framedata = (unsigned char *)malloc(512*1024);
if (!framedata) {
goto CLEAN_UP;
}
while(ctx->streamLoop) {
readsize = read(fd_stream, &frameheader, sizeof(MediaInfo_st));
//printf("readsize:%d\n", readsize);
if(readsize < sizeof(MediaInfo_st)) {
lseek(fd_stream, 0, SEEK_SET);
LOG_PRINT("readsize:%d, reopen file again\n", readsize);
continue;
}
readsize = read(fd_stream, framedata, frameheader.data_size);
//LOG_PRINT("read data, encode type:%d\n",frameheader.media_type);
if (frameheader.media_type == MEDIA_TYPE_VIDEO) {
vpts += 66; //测试文件中,视频帧率15fps
xs_videoInfo_st vinfo={0};;
vinfo.encode = XS_VIDEO_TYPE_H265;
vinfo.frame_type = (frameheader.frame_type == VIDEO_FRAME_TYPE_IFRAME)?XS_VIDEO_FRAME_I:XS_VIDEO_FRAME_P;
vinfo.payload = framedata;
vinfo.payload_size = frameheader.data_size;
vinfo.pts = vpts;
vinfo.utcms = ctx->isPlayback? (ctx->startTime + vpts):currentTimeMillisecond(); //回放流将时间戳utc时间戳特殊处理了一下
vinfo.seq = ++vseq;
if (VIDEO_FRAME_TYPE_IFRAME == frameheader.frame_type) {
LOG_PRINT("[stream:%d-%d-%d][service:%d]push key frame| size:%d | pts:%lld | seq:%d\n",
ctx->streamIdx.devId,ctx->streamIdx.lensId, ctx->streamIdx.streamId,
ctx->serviceId, frameheader.data_size, vpts, vseq);
}
/*输入视频数据*/
if (xs_stream_inputVideo(ctx->serviceId, &vinfo) != XSIOT_EC_SUCCESS) {
LOG_PRINT("input video frame failed\n");
}
usleep(66000);
} else if (frameheader.media_type == MEDIA_TYPE_AUDIO) {
apts += 40; //音频帧率25fps
xs_audioInfo_st ainfo={0};;
ainfo.encode = XS_AUDIO_TYPE_G711A;
ainfo.payload = framedata;
ainfo.payload_size = frameheader.data_size;
ainfo.pts = apts;
ainfo.seq = ++aseq;
ainfo.utcms = ctx->isPlayback? (ctx->startTime + apts):currentTimeMillisecond();//回放流将时间戳utc时间戳特殊处理了一下
/*输入音频数据*/
if (xs_stream_inputAudio(ctx->serviceId, &ainfo) != XSIOT_EC_SUCCESS) {
LOG_PRINT("input audio frame failed\n");
}
}
}
CLEAN_UP:
if (framedata){
free(framedata);
}
if (fd_stream!= -1){
close(fd_stream);
}
LOG_PRINT("[stream:%d-%d-%d] quit streaming demo proc\n",ctx->streamIdx.devId,ctx->streamIdx.lensId, ctx->streamIdx.streamId);
return NULL;
}
直播/云存
设备只需要正确响应 SDK 拉取实时流的回调(xs_startPushLiveStream 和 xs_stopPushLiveStream)即可,直播请求的响应和云存由SDK内部处理。
设备云存储支持事件存储和全天存储的模式,录制时长由平台下发的套餐信息决定, SDK 内部处理。
本地回放
本地回放功能是指APP远程回看摄像头本地存储保存的历史视频。
SDK 不负责本地存储,音视频的本地存储功能由设备厂商自行实现。
SDK提供查询和回放的相关接口回调,设备端需要实现这些回调,以实现本地录像的查询、回放、Seek 等操作。
/*SD卡回放相关的回调*/
typedef struct {
xs_startPlaybackByTime startPlaybackByTime;
xs_startPlaybackByFileName startPlaybackByName;
xs_seekPlaybackByTime seekPlayback;
xs_stopPlayback stopPlayback;
xs_playbackCtrl playbackCtrl;
xs_queryRecordTimeSection queryRecordTimeSection;
xs_queryRecordFileList queryRecordFileList;
xs_queryMonthRecord queryMonthRecord;
xs_queryAlarmEventList queryEventList;
xs_getPlaybackProperty getPlaybackProperty;
} xs_playbackCallback_st;
SDK中关于本地回放相关的功能主要包括:
功能 | 说明 | 接口名称 |
---|---|---|
按时间回放 | 从指定时间开始回放本地存储数据 | xs_startPlaybackByTime |
按文件名回放 | 按照文件名开始回放本地存储数据 | xs_startPlaybackByFileName |
停止回放 | 停止回放 | xs_stopPlayback |
回放seek | 回放开始之后,切换回放的时间点 按照开始时间回放本地存储数据后,seek的时间点为绝对时间 按照文件名开始回放本地存储数据后,seek的时间点为相对时间(相对文件开始位置的时间偏移量) | xs_seekPlaybackByTime |
回放速率控制 | 控制设备按照指定倍速进行回放 | xs_playbackCtrl |
查询录像时间列表 | 根据起止时间点查询指定时间段内的录像片段时间段信息 | xs_queryRecordTimeSection |
查询报警列表 | 根据起止时间点查询指定时间段内的报警片段时间段信息 | xs_queryAlarmEventList |
查询录像文件列表 | 根据起止时间点查询指定时间段内的录像文件名称列表信息 | xs_queryRecordFileList |
查询录像天数 | 根据指定月份查询当月有录像的天数信息 | xs_queryMonthRecord |
获取回放能力 | 获取回放能力相关信息(目前只有并发路数) | xs_getPlaybackProperty |
播放完毕通知 | 本地回放数据播放完毕(后面没有数据了),通知客户端 | xs_stream_sendCmd |
回放过程的 StreamSDK 和摄像头应用程序(CameraAPP)交互流程图如下:
录像查询
录像查询,目前支持获取SD卡录像时间段列表、SD卡录像文件列表、SD卡录像事件列表、录像天数信息。对应的回调函数如下:
/**
* @brief 查询本地录像时间列表信息,返回指定时间段内的录像片段时间段信息
*
* @param [IN] devId: 设备ID
* @param [IN] queryParam 查询参数
* @param [OUT] result 查询结果
*
* @return 0:成功,其他值:失败
* @note 设备端查询录像片段时,应该从回调参数 queryParam.endTime 所对应的时间往前查询,也即优先查询并返回最新的录像片段(文件),每次最多返回 queryParam.pageSize 条
*/
typedef xs_int32 (*xs_queryRecordTimeSection)(xs_int32 devId, const xs_queryRecord_st *queryParam, xs_recordTimeList_st* result);
/**
* @brief 查询本地录像报警事件列表信息
*
* @param [IN] devId: 设备ID
* @param [IN] queryParam 查询参数
* @param [OUT] result 查询结果
*
* @return 0:成功,其他值:失败
* @note 设备端查询录像片段时,应该从回调参数 queryParam.endTime 所对应的时间往前查询,也即优先查询并返回最新的录像片段(文件),每次最多返回 queryParam.pageSize 条
*/
typedef xs_int32 (*xs_queryAlarmEventList)(xs_int32 devId, const xs_queryEvent_st *queryParam, xs_eventTimeList_st* result);
/**
* @brief 查询本地录像文件列表信息
*
* @param [IN] devId: 设备ID
* @param [IN] queryParam 查询参数
* @param [OUT] result 查询结果
*
* @return 0:成功,其他值:失败
*/
typedef xs_int32 (*xs_queryRecordFileList)(xs_int32 devId, const xs_queryRecord_st *queryParam, xs_recordFileList_st* result);
/**
* @brief 查询某月有录像的天数信息
*
* @param [IN] devId: 设备ID
* @param [IN] queryParam 查询参数
* @param [OUT] result 查询结果
*
* @return 0:成功,其他值:失败
*/
typedef xs_int32 (*xs_queryMonthRecord)(xs_int32 devId, const xs_queryMonth_st *queryParam, xs_recordMonth_st* result);
注意:查询列表的时候,应该从回调参数 queryParam.endTime 所对应的时间往前查询,也即优先查询并返回最新的录像片段(文件),每次最多返回 queryParam.pageSize 条 (查询录像时间段列表和查询事件列表,每次的pagesize最多不超过500个,查询录像文件列表,每次pagesize不超过100个)