【踩坑日记】uni-app相机抽帧,相机被多次初始化问题

发布时间 2023-09-19 09:09:20作者: alphaair

缘起:最近频繁接到使用我们AI运行识别插件用户的反馈,部分机型在uni中抽几帧后,就不再帧的了。开始以为又是小程序的API兼容的问题(确有机型出现过抽帧兼容性问题),后面越来越多的反馈在原生下无问题,只有采用uni-app方案的有问题...

一、先看抽帧简略代码

下面是小程序做AI运动识别的第一步,摄像头帧数据采集的精简代码版段,大致流程是:在摄像头初始化完成后,初始化一个CameraFrameListener、进行抽帧,并根据抽取的帧图像大小,调整Camera组件大小与帧图大小同比缩放(宽全为全屏、高自适应)。

为什么要同比缩放的原因,请见我们的系列分享:【一步步开发AI运动小程序】四、小程序如何抽帧

<template>
	<view class="container">
		<camera id="preview" class="preview" :style="videoStyles" flash="off" :device-position="deviceKey"
			resolution="high" frame-size="low" @initdone="onCameraReady">
		</camera>
	</view>
</template>

<script>

	export default {
		data() {
			return {
				deviceKey: "back",
				previewWidth: 480,
				previewHeight: 640,
				previewRate: 1,

				frameWidth: 480,
				frameHeight: 640
			};
		},
		computed: {
			videoStyles() {
				const style = `width:${this.previewWidth}px;height:${this.previewHeight}px;`;

				return style;
			}
		},
		methods: {
			autoFitPreview(width, height) {
				const sifno = uni.getSystemInfoSync();
				let rate = sifno.windowWidth / width;

				this.previewWidth = width * rate;
				this.previewHeight = height * rate;
				this.previewRate = rate;
				this.frameWidth = width;
				this.frameHeight = height;
			},
			initCamera(){
				//防止重初始化
				if(this.listener)
					return;
					
				const that = this;
				const context = wx.createCameraContext();
				const listener = context.onCameraFrame((frame) => {
					//当帧图像大小发生变化时,重新调整摄像头代码
					if(that.frameWidth != frame.width)
						that.autoFitPreview(frame.width, frame.height);
  					console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height)
				});
				listener.start();
			},
			onCameraReady(e) {
				this.autoFitPreview(480, 640);
				this.initCamera();
			},
			onStart(){
				this.listener.start();
			},
			onStop(){
				this.listener.stop();
			}
		}
	}
</script>

二、BUG复现

在确认原生小程序抽帧无问题后,我们偿试了问题复现,经过多次测试,发现在uni下,Camera组件会多次触发initdone事件,进一步测试后发现,只要动态改变camera的style高、宽,便会触发重新初始化。

initCamera(){
	//这里的防初始化,便是引发抽帧断流的原因,因为相机重新初始化了,所以listener实际已经无法再监听帧流了,必须重新创建
	if(this.listener)
		return;
		
	const that = this;
	const context = wx.createCameraContext();
	const listener = context.onCameraFrame((frame) => {
		//当帧图像大小发生变化时,重新调整摄像头代码
		//此处的重新适应,便导致了相机的重初始化
		if(that.frameWidth != frame.width)
		that.autoFitPreview(frame.width, frame.height);
  		console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height)
	});
	listener.start();
}

三、问题修复

找出问题原因,修复便很简单了,去除相应的防CameraFrameListener重新始化锁,只要触发initdone便重新初始化,并同步初始化其它逻辑。同时也要注意尽量将camera大小一次绑定到位,减少动态绑定的次数。

四、问题总结

虽然uni-app大部分场景都与原生无异,但是受限于vue的值绑定和节点渲染机制,在实际使用中还是有细微的差别的,特别是原生组件。

另外,针对此问题,我们已经更新了我们的AI运动识别小程序插件uni版本Demo,请各用户联系我们索取。