HarmonyOS鸿蒙Next中相机预览流保存本地图片多分身问题

HarmonyOS鸿蒙Next中相机预览流保存本地图片多分身问题 一、问题描述
使用相机双路预览开发时,对第二路预览流,也就是receiver接受的那路流进行接受本地编码保存为图片时,出现了保存图片多分身的问题,如下:

图片

二、核心代码
核心代码如下:

// 扫码解析
async initPreviewFrame() {
    let mReceiver = image.createImageReceiver(
      (this.previewProfile as camera.Profile).size.width,
      (this.previewProfile as camera.Profile).size.height,
      image.ImageFormat.JPEG,
      8
    );
    let mSurfaceId = await mReceiver.getReceivingSurfaceId();
    this.cameraOutput = (this.cameraManager as camera.CameraManager).createPreviewOutput(this.previewProfile as camera.Profile, mSurfaceId);
    (this.captureSession as camera.CaptureSession).addOutput(this.cameraOutput as camera.CameraOutput);
    // 相机扫描
    this.isRun = true
    mReceiver.on('imageArrival', async () => {
      let imageData = await mReceiver.readNextImage();
      let imageComponent = await imageData.getComponent(4);
      let imageBuffer = imageComponent.byteBuffer;
      this.cameraLifecycleProxy.onCaptureSuccess(this.curMode, imageBuffer);
      // todo hj decode
      if (this.isRun) {
        console.info("format:",imageData.format.toString())
        await this.saveImage(imageData,imageComponent)
        this.isRun = false
        let result = await this.decode(imageData, imageComponent)
        let decodeResult: string = ""
        if (result != undefined) {
          decodeResult = result as string
          MyStorage.instance().qrCodeParseResult = decodeResult
        }
      }
      await imageData.release();
    })
  }

  private async saveImage(imageData: image.Image, component: image.Component) {

    let file: fs.File | undefined = undefined;
    let pixelMap: image.PixelMap | undefined = undefined;

    try {
      const width = imageData.size.width;
      const height = imageData.size.height;

      console.info("image size",width+":"+height)

      // 使用精准到纳秒的标识符,或加一个随机数防止文件名重复
      let context = getContext(this) as common.UIAbilityContext;
      let timestamp = Date.now() + "_" + Math.floor(Math.random() * 1000);
      let path = context.filesDir + `/shot_${timestamp}.jpg`;

      // 创建 PixelMap
      let opts: image.InitializationOptions = {
        editable: true,
        pixelFormat: image.PixelMapFormat.NV21,
        size: { height: height, width: width }
      };
      pixelMap = await image.createPixelMap(component.byteBuffer, opts);

      // 打包图片
      const imagePackerApi = image.createImagePacker();
      let packOptions: image.PackingOption = { format: 'image/jpeg', quality: 90 };
      let arrayBuffer = await imagePackerApi.packing(pixelMap, packOptions);

      // 安全写入:使用同步或带 Try-Catch 的异步
      file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.write(file.fd, arrayBuffer); // 建议使用 await fs.write 异步写入

      console.info("camera", `成功保存独立文件: ${path}`);

    } catch (err) {
      console.error("camera", "保存图片失败: " + JSON.stringify(err));
    } finally {
      // 释放资源
      if (file) {
        fs.closeSync(file.fd);
      }
      if (pixelMap) {
        await pixelMap.release();
      }
    }
  }

更多关于HarmonyOS鸿蒙Next中相机预览流保存本地图片多分身问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

好吧,忘记处理这个了,已解决

cke_142.png

更多关于HarmonyOS鸿蒙Next中相机预览流保存本地图片多分身问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS Next 中相机预览流保存本地图片出现“分身”(多张重复图片)通常是因为预览帧回调被多次触发,或未正确管理 ImageReceiver 的 buffer 释放。常见原因:imageReceiver.on 监听未及时移除、多次调用 readNextImagesaveImage、或同时使用多个接收实例。确认回调中仅执行一次保存操作,并在保存后释放 buffer,可避免重复生成。

问题出在两个地方:

  1. PixelMap 创建参数错误
    你通过 image.createPixelMap(component.byteBuffer, opts) 将 JPEG 码流按 NV21 格式解码,会导致图像数据解析错乱,出现“分裂/多分身”现象。ImageReceiver 已配置为 ImageFormat.JPEGcomponent.byteBuffer 就是完整的 JPEG 字节流,无需再次解码和重新打包。

  2. 并发保存控制不力
    isRun 标记在 await this.saveImage(...) 之后才置 false,而 imageArrival 回调可能并发触发。第一个回调进入 saveImage 未结束时,第二个回调可能仍读到 isRun == true,导致保存多份。

修复方案(直接保存 JPEG 流,无需重编码)
修改 saveImage,直接写入 component.byteBuffer

private async saveImage(imageData: image.Image, component: image.Component) {
  let file: fs.File | undefined = undefined;
  try {
    let context = getContext(this) as common.UIAbilityContext;
    let timestamp = Date.now() + "_" + Math.floor(Math.random() * 1000);
    let path = context.filesDir + `/shot_${timestamp}.jpg`;

    // 直接写入 JPEG 字节流
    file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    await fs.write(file.fd, component.byteBuffer);
    console.info("camera", `成功保存: ${path}`);
  } catch (err) {
    console.error("camera", "保存失败: " + JSON.stringify(err));
  } finally {
    if (file) {
      fs.closeSync(file.fd);
    }
  }
}

如需增强并发控制,建议在回调最前面加锁,例如使用一个 Promise 锁或直接使用原子标记(如 this.isSaving)并在操作前后置位,避免重复执行。

回到顶部