设备端SDK(Linux)文档

更新时间:2019-07-31 18:52:13

概述

LinkVisual设备端SDK提供了网络摄像机(IPC)接入阿里云视频边缘智能服务的基础API,主要包括实时视频直播相关的接口、设备本地文件点播相关的接口、报警事件上报和图片上传的接口以及其他IPC控制相关的接口等,为物联网设备提供的视频直播、视频点播、图片上传的功能。

  • 资源占用情况如下:

    • 内存占用:1MB的码流的情况下,预估占用500K左右的内存
    • ROM占用:2MB
  • 平台支持:目前支持Linux平台,且需要C++11的支持(支持C++11的编译工具链版本在4.8.1之上)。

  • 依赖:LinkVisual SDK依赖了飞燕SDK。飞燕SDK提供了物联网通道能力,LinkVisual响应飞燕SDK的控制消息来进行流媒体业务处理。开发者需要同时获取LinkVisual SDK和飞燕SDK来完成设备的接入。

    • 飞燕 SDK主要是提供物联网控制通道能力,包括长连接、消息通知、事件上报等。
    • LinkVisual SDK主要是提供音视频流通道能力;

SDK Demo

准备开发环境

我们提供Ubuntu16.04系统上编译的x86 64位的LinkVisual SDK和Demo供快速体验, 在其它Linux版本上尚未验证过, 推荐安装与阿里一致的发行版以避免碰到兼容性方面的问题。使用下面命令安装一些必备的软件:

$ sudo apt-get install -y build-essential make git gcc g++ cmake

下载

编译运行

  • 内容确认

请将下载的后文件解压得到link_visual_ipc_v1.0.0文件夹,确认文件夹下的文件目录结构如下:

---- CMakeLists.txt
|
---- samples
|
---- sdk

切换到link_visual_ipc_v1.0.0文件夹下,执行如下命令。

$ mkdir -p build;建立一个build文件夹,用于归类编译产物
$ cd build
$ cmake ..;使用上层目录的CmakeLists.txt进行cmake
$ make;编译
$ make install;安装运行相关文件
  • 创建产品和设备

请根据物联网平台接入指引来创建产品,并获取设备的三元组。

  • 运行

在link_visual_ipc_v1.0.0文件夹下的build目录下,生成了对应的可执行程序。进入可执行程序目录下,运行如下的命令,其中product_namedevice_namedevice_secret需要替换为自己的设备三元组。

./link_visual_demo -p product_name -n device_name -s device_secret

SDK获取

LinkVisual SDK获取

LinkVisual设备端SDK以静态库的形式提供,我们可以编译SDK支持不同芯片平台的接入,请发送邮件到ray.wl@antfin.com进行SDK申请,并确保在邮件内容中,请包含如下信息:

  • 1.公司名称;

  • 2.联系人及联系方式;

  • 3.公司地址;

  • 4.应用场景描述。

我们的商务人员在收到邮件后,会主动与您联系获取芯片平台的交叉编译工具链。提供交叉编译工具链时,需要详细说明编译链的使用说明。根据使用说明,我们将编译好对应平台的SDK提供给您。

前置依赖项
请确认已经参照“LinkVisual对接技术说明”完成了飞燕平台的视频【产品创建】和【服务开通】环节,并且在该产品添加了所需的【标准功能】;

API列表

SDK生命周期

说明 接口
SDK初始化 lv_init
SDK停止 lv_destroy

说明 接口
Linkkit的MQTT消息注入 lv_linkkit_adapter

直播及点播服务

说明 接口
通知直播、点播服务已开启 lv_start_push_streaming_cb
通知直播、点播服务已结束 lv_stop_push_streaming_cb
推送视音频配置参数 lv_stream_send_config
推送视频数据 lv_stream_send_video
推送音频数据 lv_stream_send_audio
推流过程中命令控制(暂停等) lv_on_push_streaming_cmd_cb
点播的文件列表查询 lv_query_storage_record_cb
强制关闭一路链接 lv_stream_force_stop
录像文件播放结束 lv_post_file_close

图片服务

说明 接口
图片上传 lv_post_alarm_image
通知上传图片 lv_trigger_pic_capture_cb

语音对讲服务

说明 接口
通知开启服务 lv_start_voice_intercom_cb
通知结束服务 lv_stop_voice_intercom_cb
开启服务 lv_voice_intercom_start_service
停止服务 lv_voice_intercom_stop_service
发送音频 lv_voice_intercom_send_audio
接收音频 lv_voice_intercom_receive_data_cb
接收音频参数配置 lv_voice_intercom_receive_metadata_cb

对接指南

重要说明

请开发者根据编译说明中的步骤,完成demo的编译和运行后,在进行实际API的对接。

功能说明

获取到LinkVisual SDK后,开始进入对接阶段。以下列出了当前SDK能够支持的所有功能,开发者根据自身产品定义自行检查。

  • [ ] 配网功能(一键配网、零配等)

  • [ ] OTA功能

  • [ ] Linkkit的MQTT消息收发

  • [ ] Linkkit自定义物模型

  • [ ] 直播功能

  • [ ] 卡录像点播功能

  • [ ] PTZ功能

  • [ ] 图片上报功能(抓图功能和移动侦测报警功能)

  • [ ] 语音对讲功能

SDK变更

已对接过V1.1.x之前版本的开发者,请阅读V1.1.x版本变更说明:
1. 在linkkit增加了LinkVisual自定义消息ITE_LINK_VISUAL,使用方式具体见linkkit_adapter.cpp的实现
2. lv_voice_intercom_send_audio增加参数:@param [IN] timestamp: 音频帧时间戳,单位:ms
3. linkkit_adapter_tsl_service改为接口lv_linkkit_adapter,并增加一个枚举量入参,
LV_LINKKIT_ADAPTER_TYPE_TSL_SERVICE为原物模型属性,新增LV_LINKKIT_ADAPTER_TYPE_LINK_VISUAL用于处理linkvisual自定义消息,具体参考linkkit_adapter.cpp的实现
4. 视频流现支持H265(含加密)
5. 删除demo对库iniparser的依赖,不影响SDK的集成
6. lv_on_push_streaming_cmd_cb的时间戳参数由double改为unsigned int,单位为ms
7. 其他SDK内部的优化

SDK内容

将获取到的SDK解压后,可以看到以下内容:
1. version.txt:版本说明
2. CMakeLists.txt:基于cmake的编译的基础示例
3. LinkVisual:内含sdk和samples,分别包含库和头文件、示例代码
4. third_party:LinkVisual依赖的一些库信息

LinkVisual依赖库

  1. cJSON,为LinkVisual提供JSON解析的支持,使用版本为1.7.7
  2. linkkit-sdk-c,为阿里云提供的物联网接入sdk,LinkVisual使用版本v2.3.0。更多信息请访问官方网址官方文档

编译说明

环境配置

编译依赖cmake等一些相关软件,这里以ubuntu16.04下安装为例:

$ sudo apt-get install -y build-essential make git gcc g++ cmake tree

cJSON编译

在third_party目录里解压cJSON-1.7.7,并进入解压后的文件夹

$ cd third_party
$ tar -vxf cJSON-1.7.7.tar.gz;解压代码压缩包
$ cd cJSON-1.7.7

交叉编译准备:修改Makefile,在文件最开始加入工具链的声明(请替换成对应的交叉编译工具链)

CC        =   arm-linux-gcc
LD        =   arm-linux-ld
AR       =   arm-linux-ar

编译,确认是否生成了libcjson.a以及头文件cJSON.h是否存在

$ make

$ ls lib*.a;确认libcjson.a已生成
$ ls *.h

linkkit-sdk-c编译

在third_party目录里解压linkkit-sdk-c,并进入解压后的文件夹

$ cd third_party
$ tar -vxf linkkit-sdk-c.tar.gz;解压代码压缩包
$ cd linkkit-sdk-c

交叉编译准备:修改src/board/config.ubuntu.x86,在最后加上CROSS_PREFIX :=交叉编译工具链路径前缀(请替换成对应的交叉编译工具链)

CROSS_PREFIX :=arm-linux-

选择ubuntu编译和确认生成库libiot_tls.a/libiot_sdk.a/libiot_hal.a

$ make reconfig;这里选择ubuntu对应的数字

$ make;

$ ls output/lib/*.a;这里只需要有libiot_tls.a/libiot_sdk.a/libiot_hal.a

$ tree include;这里可以看到很多头文件,如iot_import.h等,全部都需要

整体编译

重新回到SDK解压后的文件夹根目录,准备开始整体编译。
修改CMakeLists.txt里的TOOLCHAINS_PREFIX设置,第二个参数设置为交叉编译工具链前缀(请替换成对应的交叉编译工具链)

set(TOOLCHAINS_PREFIX "arm-linux-" CACHE STRING "set the toolchain")

开始编译:

$ mkdir -p build;建立一个build文件夹,用于归类编译产物
$ cd build
$ cmake ..;使用上层目录的CmakeLists.txt进行cmake
$ make;编译
$ make install;安装运行相关文件

自行编译时,请参考以下链接顺序:link_visual_ipc iot_sdk cjson iot_hal iot_tls pthread rt。编译的CFLAGS,请添加-std=c++11。

运行设备demo

将build文件夹拷贝至板子上,进入build后执行以下命令(其中三元组需替换):

$ ./link_visual_demo -p productKey -n productName -s productSecret

飞燕SDK对接

SDK功能繁多,详细的SDK内容请参考文档:点击这里
当前LinkVisual主要依赖于Linkkit的MQTT功能,针对此功能,LinkVisual提供了该功能的使用要求。这些使用要求在sample/linkkit_adapter.cpp里有使用范例。其他如配网、OTA等功能,需要开发者根据文档开发。

配网功能

Linkkit提供配网功能和设备绑定功能,配网方式包括一键配网等,开发者根据自身需求选择对接。配网功能查看:
点击这里

OTA功能

Linkkit提供OTA功能。OTA功能对于设备后续版本的迭代更新有着至关重要的功能,要求开发者必须完成OTA功能更的对接。当前LinkVisual要求使用高级版接口的OTA功能。OTA功能:点击这里

Linkkit高级版接口

LinkVisual使用了Linkkit高级版接口,此处列出LinkVisual使用到的一些接口。

Linkkit生命周期

IOT_Linkkit_Open;打开一个linkkit句柄
IOT_Linkkit_Connect;连接服务器
IOT_Linkkit_Yield;用于维持心跳,建议其参数值为200ms,不建议自行修改,会导致MQTT消息往外抛出变慢。
IOT_Linkkit_Close;关闭linkkit连接和句柄

Linkkit消息订阅

IOT_RegisterCallback;用于订阅linkkit消息

LinkVisual要求必须要订阅的有:
ITE_SERVICE_REQUEST:物模型的服务类消息,消息回调中部分消息需要使用lv_linkkit_adapter交由LinkVisual处理。
ITE_LINK_VISUAL:LinkVisual自定义消息,消息回调中所有消息需要使用lv_linkkit_adapter交由LinkVisual处理。
具体参考LinkVisual SDK中的linkkit_adapter.cpp

Linkkit其他配置

IOT_SetLogLevel;用于设置Linkkit的打印级别,初始对接阶段建议设置为DEBUG。
CONFIG_MQTT_TX_MAXLEN;定义在iot_import_config.h,影响MQTT发送的最大值。
LinkVisual定义的卡录像列表查询功能中,由于需要查询的数量不同,导致最终查询的字符串长度可能超过默认MQTT的长度设置,
需要根据实际长度定义最大值。

Linkkit功能验收

对接LinkVisual功能前,要求Linkkit以下功能已经是可用状态:

  1. IOT_Linkkit_Connect连接成功
  2. IOT_RegisterCallback注册了ITE_SERVICE_REQUEST/ITE_LINK_VISUAL回调

以下Linkkit功能可在对接LinkVisual过程中完成:

  1. 配网功能
  2. OTA功能

LinkVisual SDK对接

  • SDK中的LinkVisual/sdk/link_visual_ipc.h和LinkVisual/sdk/link_visual_def.h是SDK的头文件。

  • SDK中的LinkVisual/samples/demo.cpp主要说明了LinkVisual SDK的对接示例,文档中的示例代码主要来自于demo.cpp和linkkit_adapter.cpp。

问题排查

LinkVisual以非源码形式提供功能,在对接过程中出现问题时,LinkVisual相当于黑盒子,不利于问题的排查。因此我们对问题排查提出了一定的要求,以提高问题排查的效率。

  1. 日志:所有问题都需要提供日志。日志中要求包含lv_init函数打印的SDK版本信息,要求lv_init中的log_level为DEBUG级别,要求问题发生的前后日志尽可能的详细。
  2. core:发生崩溃性问题时,除日志外,要求提供core文件的堆栈信息。
  3. tcpdump:部分问题可能要求提供tcpdump进行网络数据抓包。

SDK生命周期

lv_init

接口详情:int lv_init(const lv_config_s *config)
SDK初始化

  • 参数说明
参数 类型 说明
config lv_config_s* 配置参数结构体
  • 示例代码
//新建一个配置结构体,并置空
lv_config_s config;
memset(&config, 0, sizeof(lv_config_s));

//以下是设置具体的配置属性
//设备三元组
memcpy(config.product_key, product_key.c_str(), product_key.length());
memcpy(config.device_name, device_name.c_str(), device_name.length());
memcpy(config.device_secret, device_secret.c_str(), device_secret.length());

//SDK的日志配置
config.log_level = LV_LOG_DEBUG;
config.log_dest = LV_LOG_DESTINATION_STDOUT;
//config.log_dest_file;

//音视频推流服务
config.start_push_streaming_cb = startPushStreamingCallback;
config.stop_push_streaming_cb = stopPushStreamingCallback;
config.on_push_streaming_cmd_cb = onPushStreamingCmdCb;

//语音对讲服务
config.start_voice_intercom_cb = startVoiceIntercomCallback;
config.stop_voice_intercom_cb = stopVoiceIntercomCallback;
config.voice_intercom_receive_metadata_cb = voice_intercom_receive_metadata_cb;
config.voice_intercom_receive_data_cb = VoiceIntercomReceiveDataCallback;

//获取存储录像录像列表
config.query_storage_record_cb = queryStorageRecordCallback;

//触发设备抓图
config.trigger_pic_capture_cb = triggerPicCaptureCallback;

//触发设备录像
config.trigger_record_cb = triggerRecordCallback;

//初始化SDK,失败则退出
int ret = lv_init(&config);
if (ret < 0) {
    return -1;
}

lv_destroy

接口详情:int lvdestroy(void);
SDK销毁

  • 参数说明

无。

  • 示例代码
//运行结束后
lv_destroy();

其他说明

  1. lv_init会打印相关版本号信息,在寻找LinkVisual团队支持时,请开发者务必注明此版本信息,方便追溯问题。
  2. lv_init设备初始化不会因无网络而失败,失败的原因一般是入参不正确或者资源分配失败(一般不会)。
  3. lv_init注册了大量的回调函数,这些回调函数共用一个消息队列线程,意味着一个回调消息的阻塞将导致后续消息的延迟,一个回调的永久阻塞将导致所有回调无法抛出。因此建议开发者:不要在回调中做过于耗时的操作;认真检查回调函数中的代码是否会长时间或者永久阻塞。
  4. lv_init定义了日志类型log_level,建议对接初期将日志类型设置为LV_LOG_DEBUG。
  5. 推荐lv_init在IOT_Linkkit_Connect成功后调用。
  6. 推荐lv_destroy在IOT_Linkkit_Close调用之前调用。

Linkkit消息注入

LinkVisual需要接受linkkit下发的部分消息并代为处理,处理用接口为lv_linkkit_adapter,已被LinkVisual处理过的消息不再需要开发者处理。

lv_linkkit_adapter

接口详情:int lvlinkkit_adapter(lv_linkkit_adapter_type_s type, const lv_linkkit_adapter_property_s *in)
_SDK初始化。

_

  • 参数说明
参数 类型 说明
type lv_linkkit_adapter_type_s linkkit的消息适配类型,目前有linkkit物模型服务类消息和LinkVisual自定义消息。
in const lv_linkkit_adapter_property_s* linkkit的消息结构体
  • 示例代码
IOT_RegisterCallback(ITE_LINK_VISUAL, user_link_visual_handler);//订阅了LinkVisual自定义消息回调
IOT_RegisterCallback(ITE_SERVICE_REQUST, user_service_request_event_handler);//订阅了Linkkit物模型服务类消息回调

ITE_LINK_VISUAL

ITE_LINK_VISUAL是LinkVisual自定义的消息。由于Linkkit v2.3.0接口暂不支持自定义消息,因此LinkVisual修改了Linkkit部分代码,非LinkVisual专用的Linkkit版本将无法使用该功能。
自定义消息全部由LinkVisual处理业务逻辑:

//user_link_visual_handler为IOT_RegisterCallback使用ITE_LINK_VISUAL订阅的回调,
//在回调中,所有消息都使用lv_linkkit_adapter注入
static int user_link_visual_handler(const int devid, const char *payload,
                                    const int payload_len)
{
    //Linkvisual自定义的消息,直接全交由LinkVisual来处理
    if (payload == nullptr || payload_len == 0) {
        return 0;
    }

    lv_linkkit_adapter_property_s in = {0};
    in.request = payload;
    in.request_len = payload_len;
    int ret = lv_linkkit_adapter(LV_LINKKIT_ADAPTER_TYPE_LINK_VISUAL, &in);
    if (ret < 0) {
        EXAMPLE_TRACE("LinkVisual process service request failed, ret = %d", ret);
        return -1;
    }

    return 0;
}

ITE_SERVICE_REQUST

ITE_SERVICE_REQUST是Linkkit定义的物模型服务类消息,部分物模型消息需要由LinkVisual处理业务逻辑,此类消息需要使用lv_linkkit_adapter来处理。

//LinkVisual代为处理的服务类消息
static std::vector<std::string> link_visual_service = {
    "TriggerPicCapture",//触发设备抓图
    "StartVoiceIntercom",//开始语音对讲
    "StopVoiceIntercom",//停止语音对讲
    "StartVod",//开始录像观看
    "StartVodByTime",//开始录像按时间观看
    "StopVod",//停止录像观看
    "QueryRecordList",//查询录像列表
    "StartP2PStreaming",//开始P2P直播
    "StartPushStreaming",//开始直播
    "StopPushStreaming",//停止直播
};

//user_service_request_event_handler为IOT_RegisterCallback使用ITE_SERVICE_REQUST订阅的回调,
//在回调中,判断当前消息是否需要LinkVisual处理,如需要,则使用lv_linkkit_adapter注入
static int user_service_request_event_handler(const int devid, const char *id, const int id_len,
                                            const char *serviceid, const int serviceid_len,
                                            const char *request, const int request_len,
                                            char **response, int *response_len)
{
      /* 打印方式1 */
    printf("Service Request Received, Devid: %d, ID %.*s, Service ID: %.*s, Payload: %s \n",
                    devid, id_len, id, serviceid_len, serviceid, request);
    /* 打印方式2 */
    printf("Service Request Received, Devid: %d, ID %s, Service ID: %s, Payload: %s \n",
                    devid, id, serviceid, request);
      bool link_visual_process = false;
      //判断当前serviceid是否是LinkVisual需要处理的消息
    for (int i = 0; i < link_visual_service.size(); i++) {
        //这里需要根据字符串的长度来判断
        if (!strncmp(serviceid, link_visual_service[i].c_str(), link_visual_service[i].length())) {
            link_visual_process = true;
            break;
        }
    }

       //根据前文的判断,将需要LinkVisual处理的消息使用lv_linkkit_adapter注入
    if (link_visual_process) {
        //ISV将某些服务类消息交由LinkVisual来处理,不需要处理response
        lv_linkkit_adapter_property_s in = {0};
        in.dev_id = devid;
        in.id = id;
        in.id_len = id_len;
        in.service_id = serviceid;
        in.service_id_len = serviceid_len;
        in.request = request;
        in.request_len = request_len;
        int ret = lv_linkkit_adapter(LV_LINKKIT_ADAPTER_TYPE_TSL_SERVICE, &in);
        if (ret < 0) {
            EXAMPLE_TRACE("LinkVisual process service request failed, ret = %d", ret);
            return -1;
        }
    } else {
        //这里给一个非LinkVisual处理的消息示例
        if (!strncmp(serviceid, "PTZActionControl", (serviceid_len > 0)?serviceid_len:0)) {
            cJSON *root = cJSON_Parse(request);
            if (root == NULL) {
                EXAMPLE_TRACE("JSON Parse Error");
                return -1;
            }

            int action_type = 0;
            cJSON *child = cJSON_GetObjectItem(root, "ActionType");
            if (!child) {
                EXAMPLE_TRACE("JSON Parse Error");
                cJSON_Delete(root);
                return -1;
            }
            action_type = child->valueint;

            int step = 0;
            child = cJSON_GetObjectItem(root, "Step");
            if (!child) {
                EXAMPLE_TRACE("JSON Parse Error");
                cJSON_Delete(root);
                return -1;
            }
            step = child->valueint;

            cJSON_Delete(root);
            EXAMPLE_TRACE("PTZActionControl %d %d", action_type, step);
        }
    }

    return 0;
}

PTZ功能

LinkVisual定义了PTZ属性,但是这些PTZ消息,需要开发者自行处理。
ITE_SERVICE_REQUST的示例代码中已经给出了PTZ的解析示例代码,此处补充PTZ的定义。

PTZActionControl 物模型服务类标识符
ActionType 动作类型 0 - 左;1 - 右;2 - 上;3 - 下;4 - 上左;5 - 上右;6 - 下左;7 - 下右;8 - 放大;9 - 缩小;
Step 步进量 取值范围:0 ~ 10;步长:1

其他说明

  • 任何一个自定义消息或者物模型消息缺失,都将影响SDK的功能。若开发者不使用demo中的示例代码,请一定要仔细检查自写代码中消息是否成功注入了lv_linkkit_adapter。

  • 通过云产品的后台控制台的设备调试功能,云端可下发测试数据至设备;开发者可通过此方式进行消息注册的测试。

  • Linkkit的IOT_RegisterCallback注册的回调,共用一个消息队列线程,意味着一个回调消息的阻塞将导致后续消息的延迟,一个回调的永久阻塞将导致所有回调无法抛出。因此建议开发者:不要在回调中做过于耗时的操作;认真检查回调函数中的代码是否会长时间或者永久阻塞。

  • Linkkit的IOT_RegisterCallback注册的ITE_SERVICE_REQUST回调中,id、serviceid、request其实指向同一个字符串的不同位置(对比示例代码中打印方式1和打印方式2),因此对serviceid的比较时,需要带上 serviceid_len。

  • PTZ功能中,开发者不支持的动作类型可不做支持;单位步进量在设备的具体步进大小可由开发者自行定义;实现了画面翻转功能的开发者,PTZ方向的调整也需要翻转。

直播和卡录像点播

lv_start_push_streaming_cb

接口详情:typedef int (lv_start_push_streaming_cb)(int service_id, lv_stream_type_e type, const lv_stream_param_s param)
回调函数,通知直播或者卡录像点播链路已经建立成功,并附带一些配置信息。在此回调收到后,开发者应该根据配置信息初始化编码器,并开始推送音视频数据。

  • 参数说明
参数 类型 说明
service_id int 链路的ID
type lv_stream_type_e 链路的类型,分为直播和卡录像点播
param const lv_stream_param_s* 链路的参数,如卡录像要点播的文件名称
  • 示例代码
//demo,定义了回调函数startPushStreamingCallback作为lv_start_push_streaming_cb的实现
lv_start_push_streaming_cb = startPushStreamingCallback;

//demo,回调函数startPushStreamingCallback
int startPushStreamingCallback(int service_id, lv_stream_type_e cmd_type, const lv_stream_param_s *param)
{
        //忽略不合法的service_id
        if (service_id < 0) {
        return -1;
    }

    if (cmd_type == LV_STREAM_CMD_LIVE) {
          //使用lv_stream_send_video/lv_stream_send_audio推送音视频数据;
          //实际使用中建议新建线程进行数据发送
        ......
        return 0;
    } else if (cmd_type == LV_STREAM_CMD_STORAGE_RECORD_BY_FILE) {
       //使用lv_stream_send_video/lv_stream_send_audio推送音视频数据
       //实际使用中建议新建线程进行数据发送
       ......
       return 0;
    }

    return 0;
}

lv_stop_push_streaming_cb

接口详情:typedef int (*lv_stop_push_streaming_cb)(int service_id)
回调函数,通知直播或者卡录像点播链路已经断开。在此回调收到后,开发者应该停止推送音视频数据。

  • 参数说明
参数 类型 说明
service_id int 链路的ID
  • 示例代码
//demo,定义了回调函数stopPushStreamingCallback作为lv_start_push_streaming_cb的实现
lv_stop_push_streaming_cb = stopPushStreamingCallback;

//demo,回调函数stopPushStreamingCallback
int stopPushStreamingCallback(int service_id)
{
        //忽略不合法的service_id
    if (service_id < 0) {
        return -1;
    }

    if (service_id == g_live_service_id) {
        //开发者停止推流
    } else if (service_id == g_storage_record_service_id) {
       //开发者停止推流
    }

    return 0;
}

lv_on_push_streaming_cmd_cb

接口详情:typedef int (*lv_on_push_streaming_cmd_cb)(int service_id, lv_on_push_streaming_cmd_type_e cmd, unsigned int timestamp_ms)
回调函数,直播、卡录像点播链接建立期间,进行一些命令控制,例如要求发送I帧命令

  • 参数说明
参数 类型 说明
service_id int 链路的ID
cmd lv_on_push_streaming_cmd_type_e 控制命令,目前有开始播放、停止播放、暂停播放、恢复播放、定位、强制I帧
timestamp_ms unsigned int 卡录像点播的定位的时间戳
  • 示例代码
//demo,定义了回调函数stopPushStreamingCallback作为lv_start_push_streaming_cb的实现
on_push_streaming_cmd_cb = onPushStreamingCmdCb;

int onPushStreamingCmdCb(int service_id, lv_on_push_streaming_cmd_type_e cmd, unsigned int timestamp_ms)
{
    if (cmd == LV_STORAGE_RECORD_START) {
        //卡录像开始推流
    } else if (cmd == LV_STORAGE_RECORD_STOP) {
        //卡录像停止推流
    } else if (cmd == LV_STORAGE_RECORD_PAUSE) {
        //卡录像暂停推流
    } else if (cmd == LV_STORAGE_RECORD_UNPAUSE) {
        //卡录像恢复推流
    } else if (cmd == LV_STORAGE_RECORD_SEEK) {
        //卡录像定位到某一段
    } else if (cmd == LV_LIVE_REQUEST_I_FRAME) {
        //对于直播,需要强制生成一个I帧
    }

    return 0;
}

lv_stream_send_config

接口详情:int lv_stream_send_config(int service_id, unsigned int bitrate_kbps, double duration, const lv_video_param_s video_param, const lv_audio_param_s audio_param)
发送音视频的配置

  • 参数说明
参数 类型 说明
service_id int 链路的ID
bitrate_kbps unsigned int 链路的码流,设置的码流越大,内部音视频数据缓冲区越大
duration double 文件时长,卡录像点播有效
video_param const lv_video_param_s* 视频的参数集
audio_param const lv_audio_param_s* 音频的参数集
  • 示例代码
lv_video_param_s video_param;
memset(&video_param, 0, sizeof(lv_video_param_s));
video_param.format = LV_VIDEO_FORMAT_H264;
video_param.fps = 25;

lv_audio_param_s audio_param;
memset(&audio_param, 0, sizeof(lv_audio_param_s));
audio_param.format = LV_AUDIO_FORMAT_G711A;
audio_param.channel = LV_AUDIO_CHANNEL_MONO;
audio_param.sample_bits = LV_AUDIO_SAMPLE_BITS_16BIT;
audio_param.sample_rate = LV_AUDIO_SAMPLE_RATE_8000;

lv_stream_send_config(service_id, 1000, 0, &video_param, &audio_param);

lv_stream_send_video

接口详情:int lv_stream_send_video(int service_id, unsigned char* buffer, unsigned int buffer_len, bool key_frame, unsigned int timestamp_ms)
视频数据发送。

  • 参数说明
参数 类型 说明
service_id int 链路的ID
buffer unsigned char* 视频的数据
buffer_len unsigned int 视频的数据长度
key_frame bool 是否为关键帧。I帧为关键帧,P帧为非关键帧,不接受B帧
timestamp_ms unsigned int 视频的时间戳
  • 示例代码
//demo,这个函数回调输入音频帧数据
void handleVideoBuffer(unsigned char *buffer, unsigned int buffer_size,
    unsigned int present_time, int nal_type, int service_id) 
{
      if (nal_type == 5) {
        lv_stream_send_video(service_id, buffer, buffer_size, 1, present_time);
    } else if (nal_type == 1) {
        lv_stream_send_video(service_id, buffer, buffer_size, 0, present_time);
    }
}

lv_stream_send_audio

接口详情:int lv_stream_send_audio(int service_id, unsigned char* buffer, unsigned int buffer_len, unsigned int timestamp_ms)
音频数据发送。

  • 参数说明
参数 类型 说明
service_id int 链路的ID
buffer unsigned char* 音频的数据
buffer_len unsigned int 音频的数据长度
timestamp_ms unsigned int 音频的时间戳
  • 示例代码
//demo,这个函数回调输入音频帧数据
void handleAudioBuffer(unsigned char *buffer, unsigned int buffer_size,
        unsigned int present_time, int service_id) 
{
    lv_stream_send_audio(service_id, buffer, buffer_size, present_time);
}

lv_query_storage_record_cb

接口详情:typedef void (lv_query_storage_record_cb)(unsigned int start_time, unsigned int stop_time, int num, const char id, int (on_complete)(int num, const char id, const lv_query_storage_record_response_s *response))
回调函数,查询卡录像的文件信息

  • 参数说明
参数 类型 说明
start_time unsigned int 查询的录像的开始时间
stop_time unsigned int 查询的录像的结束时间
num int 查询的录像的数量 小于等于0的时候表示查全部的录像
id const char * 查询的会话ID,需要回传
on_complete 函数指针 将查询结果通过该函数同步返回
  • 示例代码
//demo,该函数是lv_query_storage_record_cb的实际实现
void queryStorageRecordCallback(unsigned int start_time,
                                          unsigned int stop_time,
                                          int num,
                                          const char *id,
                                          int (*on_complete)(int num, const char *id, const lv_query_storage_record_response_s *response))
{
    DEMO_TRACE("start_time:%d stop_time:%d num:%d", start_time, stop_time, num);

    if (num <= 0) {
        return ;
    }
    int answer_num = (num > 1)?(num - 1):1;

    static int index = 0;
    auto *response =  new lv_query_storage_record_response_s[num];
    memset(response, 0, sizeof(lv_query_storage_record_response_s) * answer_num);
    for (int i = 0; i < answer_num; i ++) {
        response[i].file_size = 15320990;
        response[i].record_type = (lv_storage_record_type_e)(i % 3);
        response[i].start_time = (int)start_time + index * 60;
        response[i].stop_time = (int)start_time + index * 60 + 59;
        response[i].file_size = 15320990;
        snprintf(response[i].file_name, 64, "%d.mp4", index);//注意不要溢出
        index ++;
    }
    int result = on_complete(answer_num, id, response);
    DEMO_TRACE("result:%d", result);
    delete [] response;
}

lv_stream_force_stop

接口详情:int lv_stream_force_stop(int service_id)
强制断开一路直播、卡录像点播。开发者在自身的功能出错等无法继续正常往一路流发送数据时,自行断开连接。

  • 参数说明
参数 类型 说明
service_id int 链路的ID
  • 示例代码
//demo,在收到推流通知的回调中,直接强制关闭该路链接
int startPushStreamingCallback(int service_id, lv_stream_type_e cmd_type, const lv_stream_param_s *param)
{
    DEMO_TRACE("startPushStreamingCallback:%d %d", service_id, cmd_type);
    if (service_id < 0) {
        return -1;
    }
      lv_stream_force_stop(service_id);
}

其他说明

  1. 强制I帧命令发出时,为了保证尽可能快速的发出I帧,SDK内部将清空缓冲区,并不再接受音频数据和视频的P帧数据,直到I帧到达。
  2. 强制I帧命令发出时,为了保证尽可能快速的发出I帧,开发者应尽可能快的产生I帧。若300ms内未收到I帧,SDK会重发若干次强制I帧请求。
  3. 强制I帧命名发出时,开发者需要重新调用lv_stream_send_config发送流配置
  4. 卡录像的定位操作发生时,为了保证尽可能快速的显示定位后的数据,开发者在定位操作后,应该尽可能快速的发出I帧;同时SDK在定位操作后也不在接收音频数据和视频的P帧数据,直到I帧到达。
  5. H264/H265的帧结构会有一定的要求,可以以打印I帧的前256个字节进行查看,打印代码为
for (int i = 0; i < ((buffer_size > 256)?256:buffer_size); i++) {
    printf("%02x ", buffer[i]);
    if ((i + 1) % 30 == 0) {
        printf("\n");
    }
}
printf("\n");

H264要求I帧为: 帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR,如图

0x000001或者0x00000001是帧分隔符,0x67是SPS的开始,0x68是PPS的开始,0x65是IDR的开始
H265要求I帧为: 帧分隔符+VPS+帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR,如图:

0x000001或者0x00000001是帧分隔符,0x40是VPS的开始,0x42是SPS的开始,0x44是PPS的开始,0x26是IDR的开始

  1. 音频目前支持G711A/G711U/LC-AAC,编码参数支持需要参考头文件link_visual_def.h中的宏定义值
  2. 同一路流切换视频码流时(如主码流切为子码流、修改码流分辨率等),请保证切换后的第一帧为I帧,否则会引发花屏等现象

图片服务

lv_trigger_pic_capture_cb

接口详情:typedef int (lv_trigger_pic_capture_cb)(const char id)
通知设备开始抓取图片

  • 参数说明
参数 类型 说明
id const char * 本次请求的ID,需要回传

lv_post_alarm_image

接口详情:int lv_post_alarm_image(const char buffer, int buffer_len, lv_event_type_e type, const char id)
发送图片和报警事件,适用于抓图回调后进行上报,或者设备主动发起上报

  • 参数说明
参数 类型 说明
buffer const char* 图片数据
buffer_len int 图片数据长度
type lv_event_type_e 上报类型,分为抓图回调后进行上报和设备主动发起上报
id const char * 抓图回调的ID回传,主动上报时传空字符串或者NULL。
  • 示例代码
//demo,读取一个图片文件并上传
void testPicUpload(lv_event_type_e type, const char *id)
{
    char *buf = new char[1024*1024];
    FILE *fp = nullptr;
    int ret = 0;
    if((fp = fopen("1.jpg", "r")) == nullptr) {
        DEMO_TRACE("Failed to open file.");
        return;
    }
    ret = fseek(fp, 0, SEEK_SET);
    if(ret != 0) {
        return ;
    }

    ret = fread(buf, 1, 1024*1024, fp);
    fclose(fp);

    lv_post_alarm_image(buf, ret, type, id);
    delete [] buf;
}

其他说明

  1. 图片需要有云储存权限才可上传
  2. 上传的最大图片大小为1M
  3. 图片上传的最小间隔为1S,频繁触发的图片将会被丢弃
  4. 图片会由SDK内部保存一份拷贝,并形成待发送的图片队列。图片队列的长度为5张,在网络差的情况下,图片队列可能会满,满队列后新生成的图片将会被丢弃
  5. SDK不限制图片的格式,只要云端或者从云端拉取图片的设备能够支持解析即可。推荐使用常见的图片格式,如JPEG。

语音对讲服务

lv_start_voice_intercom_cb

接口详情:typedef int (*lv_start_voice_intercom_cb)(int service_id)
回调函数,通知一路语音对讲服务已可以建立

  • 参数说明
参数 类型 说明
service_id int 链路的ID

lv_voice_intercom_start_service

接口详情:int lv_voice_intercom_start_service(int service_id, const lv_audio_param_s *audio_param)
建立一路语音对讲链路,在接收到语音对讲开始回调后调用

  • 参数说明
参数 类型 说明
service_id int 链路的ID
audio_param const lv_audio_param_s * 设备的音频参数结构体
  • 示例代码
//demo,startVoiceIntercomCallback是lv_start_voice_intercom_cb的实际实现
lv_start_voice_intercom_cb = startVoiceIntercomCallback;

int startVoiceIntercomCallback(int service_id)
{
        //收到开始语音对讲请求时,主动开启对讲服务
    lv_audio_param_s audio_param;
    memset(&audio_param, 0, sizeof(lv_audio_param_s));
    audio_param.format = LV_AUDIO_FORMAT_G711A;
    audio_param.channel = LV_AUDIO_CHANNEL_MONO;
    audio_param.sample_bits = LV_AUDIO_SAMPLE_BITS_16BIT;
    audio_param.sample_rate = LV_AUDIO_SAMPLE_RATE_8000;
    int ret = lv_voice_intercom_start_service(service_id, &audio_param);

    return 0;
}

lv_stop_voice_intercom_cb

接口详情:typedef int (*lv_stop_voice_intercom_cb)(int service_id)
回调函数,通知一路语音对讲服务已可以断开

  • 参数说明
参数 类型 说明
service_id int 链路的ID

lv_voice_intercom_stop_service

接口详情:int lv_voice_intercom_stop_service(int service_id)
断开一路语音对讲链路,在接收到语音对讲结束回调后调用

  • 参数说明
参数 类型 说明
service_id int 链路的ID
//demo,stopVoiceIntercomCallback是lv_start_voice_intercom_cb的实际实现
lv_stop_voice_intercom_cb = stopVoiceIntercomCallback;

int stopVoiceIntercomCallback(int service_id)
{
    lv_voice_intercom_stop_service(service_id);
}

lv_voice_intercom_receive_metadata_cb

接口详情:typedef int (lv_voice_intercom_receive_metadata_cb)(int service_id, const lv_audio_param_s audio_param)
回调函数,通知语音对讲时,APP发送的音频参数格式

  • 参数说明
参数 类型 说明
service_id int 链路的ID
audio_param const lv_audio_param_s * APP发送的音频格式结构体

lv_voice_intercom_receive_data_cb

接口详情:typedef void (lv_voice_intercom_receive_data_cb)(const char buffer, unsigned int buffer_len)
回调函数,语音对讲时,APP发送的音频数据

  • 参数说明
参数 类型 说明
buffer const char* APP发送的音频数据
buffer_len int APP发送的音频数据长度

lv_voice_intercom_send_audio

接口详情:int lv_voice_intercom_send_audio(int service_id, const char* buffer, int buffer_len , unsigned int timestamp_ms)
往一路语音对讲链路发送音频数据

  • 参数说明
参数 类型 说明
service_id int 链路的ID
buffer const char* 设备音频数据
buffer_len int 设备音频数据长度
timestamp_ms unsigned int 设备音频时间戳
  • 示例代码
//这个回调输入音频参数
void handlerVoiceIntercomBuffer(unsigned char *buffer, unsigned int buffer_size,
        unsigned int present_time, int service_id) 
{
    lv_voice_intercom_send_audio(service_id, (const char *)buffer, buffer_size, present_time);
}

自验收标准

1.首帧加载 95%的请求 控制在1.2s以内;
2.推流延迟控制在1s以内;音视频同步应控制在300ms以内
3.设备端支持强制I帧指令,且两次I帧的最小间隔控制在100ms以内;
4.TF卡列表查询一天的录像文件,返回索引时间控制在1s以内;
5.TF卡视频播放,Seek指令的响应延迟在500ms以内;且持续Seek 100 次稳定可用;
6.语音对讲:收到G711A之后的解码耗时要小于音频文件的实际时间;
7.PTZ:云台转到底之后,收到该方向的指令需忽略指令,避免指令堆积;保持对后续指令的快速响应;
8.抓图:产生图片的延迟在300MS以内;直播时抓图,直播画面帧率无明显受影响;
9. 7 * 24 挂机,正常使用设备不崩溃
10. 设备支持静默定期重启机制;建议每五天自动静默重启一次;可提供用户设置;
11.设备需支持OTA
12.如采用二维码配网,则二维码扫描成功时间应控制在1.5s以内;
13.语音对讲: 设备端必须要支持AEC,确保设备端采集的音频数据没有回声

results matching ""

    No results matching ""