2025-06-06 15:17:30 +08:00

281 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 相机设备操控
* @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;