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