281 lines
5.1 KiB
JavaScript
281 lines
5.1 KiB
JavaScript
![]() |
/**
|
|||
|
* 相机设备操控
|
|||
|
* @alphaair
|
|||
|
*
|
|||
|
* 20220802 created.
|
|||
|
* 20220906 帧增加时间戳描述。
|
|||
|
* 20230208 增加takePhoto()方法。
|
|||
|
* 20230224 修复采集帧率计算。
|
|||
|
* 20230316 修化帧数据传递,不过早的初始化Buffer。
|
|||
|
* 20230603 优化stop()方法。
|
|||
|
* 20230810 修复帧没有currentTime字段。
|
|||
|
**/
|
|||
|
|
|||
|
const utils = require("../../utils/utils");
|
|||
|
|
|||
|
|
|||
|
class CameraDevice {
|
|||
|
|
|||
|
/**
|
|||
|
* 当前相机实际取帧率
|
|||
|
*/
|
|||
|
fps = 0
|
|||
|
|
|||
|
/**
|
|||
|
* 目标流控帧率
|
|||
|
* 小于fps时生效,大于不生效
|
|||
|
* 建议不要小于12帧,且不要大于30帧
|
|||
|
*/
|
|||
|
targetFPS = 12
|
|||
|
|
|||
|
/**
|
|||
|
* 帧数据流,已经按ImageData要求实例化
|
|||
|
*/
|
|||
|
frames = []
|
|||
|
|
|||
|
/**
|
|||
|
* 从start开始累计取帧数量
|
|||
|
*/
|
|||
|
frameCount = 0
|
|||
|
|
|||
|
/**
|
|||
|
* 取帧后回调
|
|||
|
*/
|
|||
|
onFrame = null
|
|||
|
|
|||
|
/**
|
|||
|
* 状态变更时回调
|
|||
|
*/
|
|||
|
onStatusChange = null
|
|||
|
|
|||
|
/**
|
|||
|
* 初始化控制器
|
|||
|
*/
|
|||
|
constructor() {
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
_onEmitFrame() {
|
|||
|
|
|||
|
if (this._signal === -1)
|
|||
|
return;
|
|||
|
|
|||
|
// if (utils.timestamp() % 5 == 0)
|
|||
|
// console.debug('推帧...');
|
|||
|
|
|||
|
let image = this.frames.shift();
|
|||
|
if (image) {
|
|||
|
image.data = new Uint8Array(image.rawData);
|
|||
|
image.currentTime = image.timestamp;
|
|||
|
utils.invoke(this.onFrame)(image);
|
|||
|
}
|
|||
|
|
|||
|
//完成后停止
|
|||
|
if (image || this._signal !== 0)
|
|||
|
return;
|
|||
|
|
|||
|
console.debug('相机停止推帧');
|
|||
|
this._signal = -1;
|
|||
|
utils.invoke(this.onStatusChange)(-1, this.getStatus());
|
|||
|
}
|
|||
|
|
|||
|
_isFlowControl() {
|
|||
|
|
|||
|
let stamp = (new Date()).getTime();
|
|||
|
if (!this._lastStamp || utils.isEmptyArray(this.frames)) {
|
|||
|
this._lastStamp = stamp;
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
let ts = stamp - this._lastStamp;
|
|||
|
if (ts < 1000 / this.targetFPS)
|
|||
|
return true;
|
|||
|
|
|||
|
this._lastStamp = stamp;
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
_onFrame(frame) {
|
|||
|
|
|||
|
// if (utils.timestamp() % 5 == 0)
|
|||
|
// console.debug('帧流出...');
|
|||
|
|
|||
|
if (this._signal != 1)
|
|||
|
return;
|
|||
|
|
|||
|
if (this._isFlowControl())
|
|||
|
return;
|
|||
|
|
|||
|
let iamge = {
|
|||
|
width: Number(frame.width),
|
|||
|
height: Number(frame.height),
|
|||
|
rawData: frame.data,
|
|||
|
timestamp: (new Date()).getTime()
|
|||
|
};
|
|||
|
this.frames.push(iamge);
|
|||
|
this.frameCount++;
|
|||
|
|
|||
|
if (!this._startTime) {
|
|||
|
this._startTime = utils.timestamp();
|
|||
|
} else {
|
|||
|
let tms = utils.timestamp();
|
|||
|
this.fps = this.frameCount / (tms - this._startTime);
|
|||
|
this.fps = Math.floor(this.fps);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_createListener() {
|
|||
|
const context = wx.createCameraContext();
|
|||
|
const listener = context.onCameraFrame(x => this._onFrame(x));
|
|||
|
listener.start({
|
|||
|
success(res) {
|
|||
|
console.log(res);
|
|||
|
}
|
|||
|
});
|
|||
|
this._listener = listener;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 对最后一帧进行成像,并保存至相册
|
|||
|
* 此方法仅做调试之用,不对用户开放
|
|||
|
*
|
|||
|
* @ignore
|
|||
|
*
|
|||
|
* @param {Object} frame 导出指定帧,可以为空,否则取最后帧
|
|||
|
* @returns {String} 图片保存在相册中的路径,null保存失败
|
|||
|
*
|
|||
|
* @remark 依赖的upng库可能存在兼容性问题,严禁正式使用
|
|||
|
*/
|
|||
|
takePhotoAsync(frame) {
|
|||
|
|
|||
|
const that = this;
|
|||
|
const task = new Promise((resolve, reject) => {
|
|||
|
|
|||
|
frame = frame || that.frames[that.frames.length - 1];
|
|||
|
if (!frame) {
|
|||
|
resolve('帧队列中没有帧');
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
const fs = wx.getFileSystemManager();
|
|||
|
const tpath = `${wx.env.USER_DATA_PATH}/temp.jpeg`;
|
|||
|
const buffer = upng.encode([frame.rawData], frame.width, frame.height);
|
|||
|
const res = fs.writeFileSync(tpath, buffer, "binary");
|
|||
|
if (res) {
|
|||
|
resolve('临时文件保存失败');
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
wx.saveImageToPhotosAlbum({
|
|||
|
filePath: tpath,
|
|||
|
success(res) {
|
|||
|
resolve('帧图像保存成功');
|
|||
|
},
|
|||
|
fail(res) {
|
|||
|
reject(res.errMsg);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
});
|
|||
|
|
|||
|
return task;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取当相机监听状态
|
|||
|
*
|
|||
|
* unknown:未知
|
|||
|
* stopped:已停止
|
|||
|
* stopping:停止中
|
|||
|
* runing: 运行中
|
|||
|
*
|
|||
|
* @returns {String}
|
|||
|
*/
|
|||
|
getStatus() {
|
|||
|
|
|||
|
switch (this._signal) {
|
|||
|
case -1:
|
|||
|
return 'stopped';
|
|||
|
case 0:
|
|||
|
return 'stopping';
|
|||
|
case 1:
|
|||
|
return 'running';
|
|||
|
default:
|
|||
|
return 'unknown';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 启动监听
|
|||
|
*/
|
|||
|
start() {
|
|||
|
this.frameCount = 0;
|
|||
|
this._signal = 1;
|
|||
|
this._startTime = null;
|
|||
|
this._lastStamp = -1;
|
|||
|
|
|||
|
if (!this._listener) {
|
|||
|
this._createListener();
|
|||
|
} else {
|
|||
|
this._listener.start({
|
|||
|
success(res) {
|
|||
|
console.log('start', res);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
if (!this._emitThread)
|
|||
|
this._emitThread = setInterval(() => this._onEmitFrame(), 1000 / this.targetFPS);
|
|||
|
|
|||
|
utils.invoke(this.onStatusChange)(1, this.getStatus());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 停止抽帧
|
|||
|
* @param {Boolean} true-强制停止,不等待线程完成;false-只发停止信号,等待完成
|
|||
|
**/
|
|||
|
stop(force) {
|
|||
|
|
|||
|
if (this._signal !== 1)
|
|||
|
return;
|
|||
|
|
|||
|
if (this._listener)
|
|||
|
this._listener.stop();
|
|||
|
|
|||
|
this.frames = [];
|
|||
|
this._signal = 0;
|
|||
|
|
|||
|
//强制停止
|
|||
|
if (force) {
|
|||
|
this._signal = -1;
|
|||
|
|
|||
|
if (this._emitThread)
|
|||
|
clearInterval(this._emitThread);
|
|||
|
|
|||
|
this._emitThread = null;
|
|||
|
}
|
|||
|
|
|||
|
utils.invoke(this.onStatusChange)(this._signal, this.getStatus());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 彻底释放CameraFrameListener监听
|
|||
|
*/
|
|||
|
dispose() {
|
|||
|
|
|||
|
if (!this._listener)
|
|||
|
return;
|
|||
|
|
|||
|
if (this._emitThread) {
|
|||
|
clearInterval(this._emitThread);
|
|||
|
this._emitThread = null;
|
|||
|
}
|
|||
|
|
|||
|
this._listener.stop();
|
|||
|
this._listener = null;
|
|||
|
this._signal = -1;
|
|||
|
this.frames = [];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
module.exports = CameraDevice;
|