# YUV拍照(ArkTS)

从API version 23开始，相机框架提供YUV格式图片拍照能力。与普通拍照相比，YUV拍照获取到的是未经过编码的图像数据，完整保留了传感器捕获的原始亮度和色度信息，适用于视频编码或专业处理。同时，拍摄过程会产生更高的能耗开销，保存会占用更多的存储空间。

#### 开发步骤

详细的相机功能API说明请参考Camera模块描述OH\_Camera。

1.  导入依赖模块。
    
    获取拍照输出的数据需要用到系统提供的image、dataSharePredicates、photoAccessHelper接口能力，方法如下。
    
    ```
    import { BusinessError } from '@kit.BasicServicesKit';
    import { camera } from '@kit.CameraKit';
    import { dataSharePredicates } from '@kit.ArkData';
    import { fileIo } from '@kit.CoreFileKit';
    import { image } from '@kit.ImageKit';
    import { photoAccessHelper} from '@kit.MediaLibraryKit';
    ```
    
2.  获取相机设备完整输出能力。
    
    通过getSupportedFullOutputCapability方法，获取当前相机设备支持的所有输出流的能力，包含预览流、拍照流、录像流等。输出流在CameraOutputCapability中的各个profile字段中，其中拍照流支持YUV格式。
    
    ```
    function getFullOutputCapability(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice, sceneMode: camera.SceneMode): camera.CameraOutputCapability | undefined {
      let cameraOutputCapability = cameraManager.getSupportedFullOutputCapability(cameraDevice, sceneMode);
      if (!cameraOutputCapability) {
        console.error("cameraManager.getSupportedFullOutputCapability error");
        return undefined;
      }
      return cameraOutputCapability;
    }
    ```
    
3.  创建拍照输出流。
    
    通过CameraOutputCapability中的photoProfiles属性，可获取当前设备支持的拍照输出流。
    
    通过createPhotoOutput方法传入支持的某一个输出流Profile，创建拍照输出流。
    
    通过getSupportedFullOutputCapability获取相机支持的完整输出能力cameraOutputCapability，参考步骤2。在cameraOutputCapability的photoProfiles中选择支持YUV格式的Profile，作为创建拍照输出流的参数photoProfile。
    
    ```
    function getPhotoOutput(cameraManager: camera.CameraManager, photoProfile: camera.Profile): camera.PhotoOutput | undefined {
      // 创建拍照输出流。
      let photoOutput: camera.PhotoOutput | undefined = undefined;
      try {
        photoOutput = cameraManager.createPhotoOutput(photoProfile);
      } catch (error) {
        let err = error as BusinessError;
        console.error("Failed to createPhotoOutput. error: ${err}");
      }
      return photoOutput;
    }
    ```
    
4.  设置拍照输出流的回调。
    
    设置单段式拍照onCapturePhotoAvailable或分段式拍照on('photoAssetAvailable')的回调，并将拍照的pixelMap数据保存为图片。如果应用需要快速得到回图，推荐使用分段式拍照回调。
    
    Context获取方式请参考：获取UIAbility的上下文信息。
    
    如果需要在图库中看到所保存的图片、视频资源，需要先将其保存到媒体库，保存方式请参考：保存媒体库资源。
    
    如果需要在onCapturePhotoAvailable接口获取到buffer，先将PixelMap数据在安全控件中保存到媒体库。
    
    *   **单段式拍照（onCapturePhotoAvailable）开发流程**：
        
        *   在会话commitConfig前注册单段式拍照回调。
        *   在单段式拍照回调函数中获取图片信息，解析出pixelMap数据，做自定义业务处理。
        *   将处理完的pixelMap通过回调回传，做图片显示或通过安全控件写文件保存图片。
        *   使用完后解注册单段式拍照回调函数。
        
        ```
        // 单段式拍照回调函数。
        function setPhotoOutputSingleCb(context: Context, photoOutput: camera.PhotoOutput)
        {
          // 设置回调之后，调用photoOutput的capture方法，就会将拍照的pixelMap回传到回调中。
          photoOutput.onCapturePhotoAvailable(async (capturePhoto: camera.CapturePhoto): Promise<void> => {
            console.info("getPhoto start");
            if (capturePhoto === undefined) {
              console.error("getPhoto failed, capturePhoto is null or undefined");
              return;
            }
            let pictureObj: image.Picture = capturePhoto.main as image.Picture;
            if (pictureObj === undefined) {
              console.error("getPhoto failed, pictureObj is null or undefined");
              return;
            }
            // 获取拍照的主图的pixelMap。
            let mainPixelMap: image.PixelMap = pictureObj.getMainPixelmap();
            if (mainPixelMap === undefined) {
              console.error("getPhoto failed, mainPixelMap is null or undefined");
              return;
            }
            // 对pixelMap中的数据做编码处理。
            const imagePackerApi = image.createImagePacker();
            let packOpts: image.PackingOption = {format: 'image/jpeg', quality: 95};
            const path: string = context.cacheDir + '/pixel_map.jpg';
            let srcFileUri: string = '';
            let file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
            try {
              await imagePackerApi.packToFile(mainPixelMap, file.fd, packOpts);
              srcFileUri = file.path;
            } catch (error) {
              console.error("Failed to pack the pixelMap to file. And the errorcode: ${error.code} ,error.message: ${error.message}");
            }
            // 对图片做保存操作。
            let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
            try {
              // 指定待保存到媒体库的位于应用沙箱的图片uri。
              let srcFileUris: string[] = [
                srcFileUri
              ];
              // 指定待保存照片的创建选项，包括文件后缀和照片类型，标题和照片子类型可选。
              let photoCreationConfigs: photoAccessHelper.PhotoCreationConfig[] = [
                {
                  title: 'test', // 可选。
                  fileNameExtension: 'jpg',
                  photoType: photoAccessHelper.PhotoType.IMAGE,
                  subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 可选。
                }
              ];
              // 基于弹窗授权的方式获取媒体库的目标uri。
              let desFileUris: string[] = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
              // 将来源于应用沙箱的照片内容写入媒体库的目标uri。
              let desFile: fileIo.File = await fileIo.open(desFileUris[0], fileIo.OpenMode.WRITE_ONLY);
              let srcFile: fileIo.File = await fileIo.open(srcFileUri, fileIo.OpenMode.READ_ONLY);
              await fileIo.copyFile(srcFile.fd, desFile.fd);
              fileIo.closeSync(srcFile);
              fileIo.closeSync(desFile);
              console.info("create asset by dialog successfully");
            } catch (err) {
              console.error("failed to create asset by dialog successfully errCode is: ${err.code}, ${err.message}");
            }
            // 从PixelMap中获取元数据。
            let metadataType: image.MetadataType = image.MetadataType.EXIF_METADATA;
            let pictureMetadata: Promise<image.Metadata>  = pictureObj.getMetadata(metadataType);
            if (pictureMetadata != undefined) {
              console.info("Get picture metadata with EXIF_METADATA successfully");
            } else {
              console.error("Get picture metadata with EXIF_METADATA failed");
            }
            // 释放资源。
            mainPixelMap.release();
            pictureObj.release();
          });
        }
        ```
        
    *   **分段式拍照（PhotoAvailable）开发流程**：
        
        *   在会话commitConfig前注册分段式拍照回调。
        *   在分段式拍照回调函数中获取图片信息，解析出pixelMap数据，做自定义业务处理。
        *   将处理完的pixelMap回传，做图片显示或通过安全控件写文件保存图片。
        *   调用capture拍照后，需要及时调用saveCameraPhoto保存图片或discardCameraPhoto取消保存图片，否则会影响后续图片的拍摄。
        *   使用完后解注册分段式拍照回调函数。
        
        ```
        // 分段式拍照回调函数。
        function setPhotoOutputDefferCb(photoOutput: camera.PhotoOutput, context: Context, callback: (pixelMap: image.PixelMap, uri: string) => void)
        {
           photoOutput.on('photoAssetAvailable', async (_err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): Promise<void> => {
             try {
               console.info("On photoAssetAvailable callback uri: ${photoAsset.uri}");
               let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
               // 保存图片。
               try {
                 // 创建媒体资产变更请求。
                 let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
                 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
                 console.info("Start to save camera photo");
                 // 保存相机拍摄的照片。
                 await assetChangeRequest.saveCameraPhoto(photoAccessHelper.ImageFileType.JPEG);
                 // 提交媒体变更请求。
                 await phAccessHelper.applyChanges(assetChangeRequest);
                 console.info("Save camera photo end");
                 await phAccessHelper.release();
               } catch (error) {
                 console.error("On photoAssetAvailable save camera photo error:  ${error.code}, ${error.message}");
               }
               // 获取图片pixelmap信息。
               try {
                 class MediaDataHandler implements photoAccessHelper.QuickImageDataHandler<image.Picture> {
                   onDataPrepared(data: image.Picture, imageSource: image.ImageSource, map: Map<string, string>) {
                     if (data != undefined) {
                       console.info("On photoAssetAvailable callback data is not undefined");
                       let pixelMap: image.PixelMap = data.getMainPixelmap();
                       pixelMap.getImageInfo().then((info) => {
                         console.info("On photoAssetAvailable pixelMap.width: " + info.size.width + ", pixelMap.height: " +
                           info.size.height + ", pixelMap.pixelFormat: " + info.pixelFormat);
                       })
                       callback(pixelMap, photoAsset.uri);
                     } else if (data === undefined && imageSource != undefined) {
                       console.info("On photoAssetAvailable callback data is undefined, and imageSource is not undefined");
                       imageSource.createPixelMap().then((pixelMap: image.PixelMap) => {
                         callback(pixelMap, photoAsset.uri);
                       }).catch((error: BusinessError) => {
                         console.error("On photoAssetAvailable callback createPixelMap failed, error: ${error.message}");
                       })
                     } else {
                       console.error("On photoAssetAvailable callback data and imageSource are both undefined");
                       return;
                     }
                   }
                 }
                 // 创建数据共享谓词。
                 let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
                 // 配置媒体资产检索条件。
                 let fetchOptions: photoAccessHelper.FetchOptions = {
                   fetchColumns: [],
                   predicates: predicates,
                 };
                 // 配置请求策略为平衡模式。
                 let requestOptions: photoAccessHelper.RequestOptions = {
                   deliveryMode: photoAccessHelper.DeliveryMode.BALANCE_MODE
                 };
                 const handler = new MediaDataHandler();
                 await photoAccessHelper.MediaAssetManager.quickRequestImage(context, photoAsset, requestOptions, handler);
                 console.info("On photoAssetAvailable callback end");
               } catch (error) {
                 console.error("On photoAssetAvailable quickRequest error:  ${error.code}, ${error.message}");
               }
             } catch (error) {
               console.error("On photoAssetAvailable callback error:  ${error.code}, ${error.message}");
             }
           });
           console.info("Set photoAssetAvailable callback end");
        }
        ```
        
5.  触发拍照。
    
    通过photoOutput的capture方法，执行拍照任务。该方法有两个参数，第一个参数为拍照设置setting，setting中可以设置图片质量，图片旋转角度等信息。第二参数为异步回调函数，用于获取结果。接口调用失败会返回相应错误码。
    
    通过PhotoOutput中的getPhotoRotation方法，可以获取拍照旋转角度。
    
    通过geoLocationManager中的geoLocationManager.getCurrentLocation方法，可以获取图片地理位置信息。使用方法可参考capture示例。
    
    ```
    function capture(captureLocation: camera.Location, photoOutput: camera.PhotoOutput): void {
      let settings: camera.PhotoCaptureSetting = {
        quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,  // 设置图片质量为高质量。
        rotation: camera.ImageRotation.ROTATION_0,  // 设置图片旋转角度0度。
        location: captureLocation,  // 设置图片地理位置。
        mirror: false  // 设置镜像使能开关（默认关）。
      };
      try {
        photoOutput.capture(settings, (err: BusinessError) => {
          if (err) {
            console.error("Failed to capture the photo. error: ${err}");
            return;
          }
          console.info("Callback invoked to indicate the photo capture request success.");
        });
      } catch (error) {
        console.error("capture call failed. error: ${error}");
      }
    }
    ```
    

#### 状态监听

在相机应用开发过程中，可以随时监听拍照输出流状态，包括拍照流开始、拍照帧的开始与结束、拍摄下一张图片是否就绪、拍照输出流的错误等。

*   通过注册固定的captureStart回调函数监听拍照开始结果，当photoOutput创建成功时，即可监听。在相机设备准备开始当前拍照时触发，该事件返回此次拍照的captureId。
    
    ```
    function onPhotoOutputCaptureStart(photoOutput: camera.PhotoOutput): void {
      photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo) => {
        if (err !== undefined && err.code !== 0) {
          return;
        }
        console.info("photo capture started, captureId : ${captureStartInfo.captureId}");
      });
    }
    ```
    
*   通过注册固定的captureEnd回调函数监听拍照结束结果，当photoOutput创建成功时，即可监听。该事件返回结果为拍照完全结束后的相关信息CaptureEndInfo。
    
    ```
    function onPhotoOutputCaptureEnd(photoOutput: camera.PhotoOutput): void {
      photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo) => {
        if (err !== undefined && err.code !== 0) {
          return;
        }
        console.info("photo capture end, captureId : ${captureEndInfo.captureId}");
        console.info("frameCount : ${captureEndInfo.frameCount}");
      });
    }
    ```
    
*   通过注册固定的captureReady回调函数获取监听能否继续拍摄下一张的结果，当photoOutput创建成功时，即可监听。当下一张可拍时触发，该事件返回结果为下一张可拍的相关信息。
    
    ```
    function onPhotoOutputCaptureReady(photoOutput: camera.PhotoOutput): void {
      photoOutput.on('captureReady', (err: BusinessError) => {
        if (err !== undefined && err.code !== 0) {
          return;
        }
        console.info("photo capture ready");
      });
    }
    ```
    
*   通过注册固定的error回调函数获取监听拍照输出流的错误结果。回调返回拍照输出接口使用错误时的对应错误码，错误码类型参见CameraErrorCode。
    
    ```
    function onPhotoOutputError(photoOutput: camera.PhotoOutput): void {
      photoOutput.on('error', (error: BusinessError) => {
        console.error("Photo output error code: ${error.code}");
      });
    }
    ```