281 lines
5.1 KiB
JavaScript
Raw Normal View History

2025-06-06 15:17:30 +08:00
/**
* 相机设备操控
* @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;