# DealStrideSolution **Repository Path**: tzliujiahui/deal-stride-solution ## Basic Information - **Project Name**: DealStrideSolution - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-12 - **Last Updated**: 2024-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 处理stride解决相机预览花屏问题 ## 介绍 开发者在使用相机服务时,如果仅用于预览流展示,通常使用XComponent组件实现,如果需要获取每帧图像做二次处理(例如获取每帧图像完成二维码识别或人脸识别场景),可以通过ImageReceiver中imageArrival事件监听预览流每帧数据,解析图像内容。在解析图像内容时,如果未考虑stride,直接通过使用width*height读取图像内容去解析图像,会导致相机预览异常,从而出现相机预览花屏的现象。 当开发者获取预览流每帧图像buffer后,若发现图片内容出现花屏堆叠状,出现“相机预览花屏”现象,此时需要排查解析每帧图像,当预览流图像stride与width不一致时,需要对stride进行无效像素处理。 ## 效果 | 正例一 | 正例二 | |----------------------------------------------------------|----------------------------------------------------------| | | | ## 使用说明 1、父页面点击"反例:未处理stride"按钮,跳转子页面NoDealStride,子页面的预览界面展示相机预览流,出现花屏现象。 1、父页面点击"处理stride:方案一"按钮,跳转子页面DealStrideOne,子页面的预览界面展示相机预览流,无明显花屏现象。 2、父页面点击"处理stride:方案二"按钮,跳转子页面DealStrideTwo,子页面的预览界面展示相机预览流,无明显花屏现象。 ### 目录结构 ``` ├──entry/src/main/ets/ │ ├──common │ │ ├──CommonConstants.ets // 通用常量 │ │ └──Constants.ets // 业务涉及的常量 │ ├──entryability │ │ └──EntryAbility.ets // Ability的生命周期回调内容 │ ├──entrybackupability │ │ └──EntryBackipAbility.ets // 自定义应用数据转换和迁移模板类 │ ├──pages │ │ ├──Index.ets // 应用入口页 │ │ ├──PageOne.ets // 预览流展示页(正例方案一) │ │ ├──PageThree.ets // 反例预览流展示页(反例) │ │ └──PageTwo.ets // 预览流展示页(正例方案二) │ └──utils │ ├──CameraServiceOne.ets // 相机服务类(正例方案一) │ ├──CameraServiceThree.ets // 相机服务类(反例) │ ├──CameraServiceTwo.ets // 相机服务类(正例方案二) │ └──Logger.ets // 日志工具类 └──entry/src/main/resources // 应用静态资源目录 ``` ## 具体实现 以一种高频的用户使用场景为例,应用需要定义一个1080*1080分辨率的预览流图像,此时的stride在相关平台的返回值为1088,此时需要对stride进行处理,处理无效像素后解析出正确的像素数据,传给Image组件送显,避免出现预览流花屏。 1. 应用通过image.ImageReceiver注册imageArrival图像回调方法,获取每帧图像数据实例image.Image,应用通过定义一个width为1080*height为1080分辨率的预览流直接创建pixelMap,此时获取到的stride的值为1088,需将处理stride后的buffer传给Image送显,预览流正常展示。 2. 处理stride有两种方式,详见方案一二。 ### 方案一 开发者使用width,height,stride三个值,处理相机预览流数据。 分两种情况: - 当stride和width相等时,按宽读取buffer不影响结果; - 当stride和width不等时,将相机返回的预览流数据即component.byteBuffer中的数据去除掉stride, 此时将component.byteBuffer中的数据去除掉stride,拷贝得到新的dstArr数据进行数据处理,将处理后的dstArr数组buffer,通过width和height直接创建pixelMap, 并存储到全局变量stridePixel中,传给Image送显。 以下为关键示例代码: ```typescript function onImageArrival(receiver: image.ImageReceiver): void { receiver.on('imageArrival', () => { receiver.readNextImage((err: BusinessError, nextImage: image.Image) => { if (err || nextImage === undefined) { Logger.error(TAG, `requestPermissionsFromUser call Failed! error: ${err.code}`); return; } nextImage.getComponent(image.ComponentType.JPEG, async (err, component: image.Component) => { let width = 1080; // Application create preview stream resolution corresponding to the width let height = 1080; // Application create preview stream resolution corresponding to the height let stride = component.rowStride; // Get stride by using component.rowStride Logger.info(TAG, `receiver getComponent width:${width} height:${height} stride:${stride}`); // Positive example: Case 1.stride and width are equal. Reading buffer by width does not affect the result. if (stride === width) { let pixelMap = await image.createPixelMap(component.byteBuffer, { size: { height: height, width: width }, srcPixelFormat: image.PixelMapFormat.NV21, }) AppStorage.setOrCreate('stridePixel', pixelMap); } else { // Positive example: Case 2.When width and stride are not equal, // At this time, the camera returned preview stream data component.byteBuffer to remove stride, // copy the new dstArr data, data processing to other do not support stride interface processing. const dstBufferSize = width * height * 1.5; // Create a dstBufferSize space of width * height * 1.5. This is NV21 data format. const dstArr = new Uint8Array(dstBufferSize); // Store the buffer after the stride is removed. // For each line of data read, the camera supports an even width and height profile, which does not involve rounding. for (let j = 0; j < height * 1.5; j++) { // Loop each row of dstArr data. // Copy the first width bytes of each line of data from component.byteBuffer into dstArr (remove invalid pixels and get exactly an eight-byte array space of width*height per line). const srcBuf = new Uint8Array(component.byteBuffer, j * stride, width); // The buffer returned by component.byteBuffer traverses each line, starting at the top, with width bytes cut off each line. dstArr.set(srcBuf, j * width); // Store the width*height data in dstArr. } let pixelMap = await image.createPixelMap(dstArr.buffer, { // The processed dstArr array buffer creates pixelMap directly by width and height, and stores it in the global variable stridePixel and passes it to Image for display. size: { height: height, width: width }, srcPixelFormat: image.PixelMapFormat.NV21, }) AppStorage.setOrCreate('stridePixel', pixelMap); } nextImage.release(); }) }); }) } ``` ### 方案二 开发者使用width,height,stride三个值,处理相机预览流数据。 分两种情况: - 当stride和width相等时,与正例一情况一致,此处不再赘述。 - 当stride和width不等时,如果应用想使用byteBuffer预览流数据创建pixelMap直接显示,可以根据stride*height字节的大小先创建pixelMap,然后调用 PixelMap的cropSync方法裁剪掉多余的像素,从而正确处理stride,解决预览流花屏问题。 以下为关键示例代码: ```typescript function onImageArrival(receiver: image.ImageReceiver): void { receiver.on('imageArrival', () => { receiver.readNextImage((err: BusinessError, nextImage: image.Image) => { if (err || nextImage === undefined) { Logger.error(TAG, `requestPermissionsFromUser call Failed! error: ${err.code}`); return; } if (nextImage) { nextImage.getComponent(image.ComponentType.JPEG, async (err, component: image.Component) => { let width = 1080; // Application create preview stream resolution corresponding to the width let height = 1080; // Application create preview stream resolution corresponding to the height let stride = component.rowStride; // Get stride by using component.rowStride Logger.info(TAG, `receiver getComponent width:${width} height:${height} stride:${stride}`); // stride and width are equal. Reading buffer by width does not affect the result if (stride === width) { let pixelMap = await image.createPixelMap(component.byteBuffer, { size: { height: height, width: width }, srcPixelFormat: image.PixelMapFormat.NV21, }) AppStorage.setOrCreate('stridePixel', pixelMap); } else { let pixelMap = await image.createPixelMap(component.byteBuffer, { // Positive example: 1. width transmission stride when creating PixelMap. size: { height: height, width: stride }, srcPixelFormat: 8, }) // 2. then call the cropSync method of PixelMap to crop out the excess pixels. pixelMap.cropSync({ size: { width: width, height: height }, x: 0, y: 0 }) // Crop the image according to the size entered, starting with (0,0), crop the area of width*height bytes. let pixelBefore: PixelMap | undefined = AppStorage.get('stridePixel'); await pixelBefore?.release(); AppStorage.setOrCreate('stridePixel', pixelMap); } nextImage.release(); }) } }); }) } ``` ### 相关权限 允许应用使用相机:ohos.permission.CAMERA ### 依赖 不涉及。 ### 约束与限制 1.本示例仅支持标准系统上运行,支持设备:华为手机。 2.HarmonyOS系统:HarmonyOS 5.0.0 Release及以上。 3.DevEco Studio版本:DevEco Studio 5.0.0 Release及以上。 4.HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上。