最近有个需求是将海康摄像头在浏览器上进行实时显示。
然而海康目前的web接口无法满足我们拥有的设备,主要提到我们的设备不支持websocket取流,故不支持无插件开发,需要用到非常旧的chrome版本,方可使用它们的开发包,而且是有插件开发包。
有技术不求人,自己动手自己做。于是本项目立项,自己取流然后进行显示。*
小主出于napi为官方维护,且对node版本可以自动适配,故选之。
anyway, 本博客主要是简单介绍取流实现这一块用到的napi技术要点。
开发流程简介
接下来你将看到
napi 官网的接口说明: http://nodejs.cn/api/n-api.html
无论何时进行开发,错误的出现是必然的,可能是一时疏忽,亦或是对参数的理解不到位都有可能造成错误的出现,所以获取和分析错误信息的能力非常重要。 根据官方提供的接口 napi_get_last_error_info, 由于每次代码相同,小主对其进一步定义成一个宏定义方便使用。
#define GET_NAPI_ERROR() const napi_extended_error_info* errorInfo; \
status = napi_get_last_error_info(env, &errorInfo); \
if (status == napi_ok) { \
printf("last error info is %s\n", errorInfo->error_message); \
} \
else { \
printf("get last error eror\n"); \
}
如上宏定义,使用时,只需插入 GET_NAPI_ERROR()
即可打印出出错信息。
其中的status,一般是函数开头定义好,专门用来接收napi接口状态的变量。
napi_status status;
获取js参数, 我这边以C++实现的实时预览接口为例,简单介绍一下这一个接口的实现, 一共需要用到的有两个参数, 一个参数作为播放通道号进行传入,另一个参数作为视频流的返回方法——回调函数。
napi_value RealPlay(napi_env env, napi_callback_info info){// napi 标准头
napi_value napi_ret;
napi_status status;
// 变量声明
char strTemp[2] = {}; // 用于接收通道参数
int channelNum = 0; // 用于存储视频通道号
napi_value js_callback; // 用于存储回调函数
size_t argc = 2; // 用于napi接口的传入(我要取多少个参数)传出(实际有多少个参数)参数
napi_value argv[2]; // 得到js传来的所有参数,均为napi_value类型,之后在逐个进行解析
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
assert(status == napi_ok);
if (argc < 2) {
napi_create_int32(env, 302, &napi_ret); // 传入参数过少,直接返回
return napi_ret;
}
// 至此获取到了传入的所有参数列表 argv.
// 解析传入的int参数 begin
status = napi_get_value_string_utf8(env, argv[0], strTemp, 2, nullptr); // 这里我直接使用 napi_get_value_int32接口获取不到通道号参数。故转了一下,先获取为char*,再进行char* to int 的转换。
channelNum = atoi(strTemp);
// 解析传入的int参数 end
// 保存获取到的回调函数
js_callback = argv[1];
// 如果需要直接调用回调函数返回数据,则调用下面的语句
// napi_value arg_ret;
// status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, arg_ret);
// assert(status == napi_ok);
// napi_value global;
// status = napi_get_global(env, &global);
// assert(status == napi_ok);
// status = napi_call_function(env, global, js_callback, 1, arg_ret, nullptr);
// assert(status == napi_ok);
// ... 其他一些执行语句
return napi_ret;
}
js 如何多次调用回调函数呢,基于上面获取到的js_callback
变量进行代码讲述。
接口1:实际调用js函数的执行函数, 具体函数参考如下函数指针进行实现。该函数的参数由napi内部自动传入,相当于napi内部的一个回调。
接口2: napi_call_threadsafe_function该函数在任何需要调用回调的地方调用, 本质上相当于Qt发出调用js函数的信号,告诉接口1,我需要调用js函数,并把data
数据作为js函数的参数返回.
其中参数napi_threadsafe_function func
由接口3提供。
接口3: 根据已经得到的回调函数js_callback
创建napi_threadsafe_function
函数, 创建后就可以在接口2调用了。
首先实现接口1的那个函数,以备后用。
napi_call_function
void realPlayThreadSafe_func_callJs(napi_env env, napi_value js_callback, void* context, void* data) {
napi_status status;
napi_value argv[2];
napi_value undefined;
status = napi_get_undefined(env, &undefined);
// 如没必要对data进行处理,则可以直接调用js函数,并传入data参数
status = napi_call_function(env, undefined, js_callback, 2, data, nullptr);
if (status != napi_ok) {
printf("call js call back function failed\n");
GET_NAPI_ERROR();
}
}
之后创建全局js_cb函数。
// 由于需要在其他函数中对js_cb进行调用, 故声明为全局定义.
napi_threadsafe_function realPlayCallbackFunc = nullptr;
napi_value RealPlay(napi_env env, napi_callback_info info){
// ... 一些其他语句
js_callback = argv[1];
// 开始创建本地 threadsafe 回调函数
napi_value srcCbName; // napi接口需要,暂未明确其目的??,非可选参数
if (napi_create_string_utf8(env, "realpCb", NAPI_AUTO_LENGTH, &srcCbName) == napi_ok) {
printf("create callbak source success\n");
}
status = napi_create_threadsafe_function(env,
js_callback, // napi_value 回调函数
nullptr,
srcCbName,
0,
1,
nullptr,
nullptr,
nullptr,
realPlayThreadSafe_func_callJs, // 实际的执行单元, 接口1
&realPlayCallbackFunc); // 全局threadsafe函数
if (status == napi_ok) {
printf("threadsafe function create success \n");
}
else {
printf("threadsafe function create failed, ret=%d \n", status);
GET_NAPI_ERROR();
}
}
至此准备工作完成,找到合适的地方开始调用。
void i_need_call_js( ){
napi_status status;
// ...
int data = 666;
status = napi_call_threadsafe_function(realPlayCallbackFunc, &data, napi_tsfn_nonblocking); // 调用后,将会自动调用接口1的实现方法进一步调用js回调函数。
assert(status == napi_ok);
// ...
}
本次经验分享到此结束,由于网上该部分资料尚未完善,结构基本由博主组织试验而成,如有不足之处还请海涵, 并可在评论区指出以示后人避坑。
有些术语可能不太准确,姑且先这样理解吧。