目录

拍照(C/C++)

概述

拍照是相机的重要功能之一,拍照模块基于相机复杂的逻辑,为了保证用户拍出的照片质量,在中间步骤可以设置分辨率、闪光灯、焦距、照片质量及旋转角度等信息。

目前相机开发有两种相机拍照方案,分别是相机分段式拍照和相机单段式拍照(本文将以单段式拍照为基础进行说明)。

开发步骤

详细的API说明请参考OH_Camera。

  1. 导入NDK接口,接口中提供了相机相关的属性和方法,导入方法如下。

    #include <cstdint>
    #include <native_buffer/buffer_common.h>
    #include <unistd.h>
    #include <string>
    #include <thread>
    #include <cstdio>
    #include <fcntl.h>
    #include <map>
    #include <vector>
    #include <native_buffer/native_buffer.h>
    #include "iostream"
    #include "mutex"
    
    #include "hilog/log.h"
    #include "ohcamera/camera.h"
    #include "ohcamera/camera_input.h"
    #include "ohcamera/capture_session.h"
    #include "ohcamera/photo_output.h"
    #include "ohcamera/preview_output.h"
    #include "ohcamera/video_output.h"
    #include "napi/native_api.h"
    #include "ohcamera/camera_manager.h"
    #include <window_manager/oh_display_info.h>
    #include <window_manager/oh_display_manager.h>
    
    namespace OHOS_CAMERA_SAMPLE {
    class NDKCamera {
      public:
        struct CameraBuildingConfig {
            char *str;
            uint32_t focusMode;
            uint32_t cameraDeviceIndex;
            bool isVideo;
            bool isHdr;
            char *videoId;
        };
        ~NDKCamera();
        explicit NDKCamera(CameraBuildingConfig config);
        // ...
    };
    } // namespace OHOS_CAMERA_SAMPLE
  2. 在CMake脚本中链接相关动态库。

    target_link_libraries(entry PUBLIC
        libace_napi.z.so
        libhilog_ndk.z.so
        libnative_buffer.so
        libohcamera.so
        libohimage.so
        libohfileuri.so
    )
  3. 创建并打开相机设备,参考 设备输入(C/C++)步骤3-5。

  4. 选择设备支持的输出流能力,创建拍照输出流。

    通过OH_CameraManager_CreatePhotoOutputWithoutSurface()方法创建拍照输出流。

    Camera_ErrorCode NDKCamera::CreatePhotoOutputWithoutSurfaceId()
    {
        OH_LOG_INFO(LOG_APP, "CreatePhotoOutputWithoutSurfaceId enter.");
        profile_ = cameraOutputCapability_->photoProfiles[0];
        Camera_Profile* profile = cameraOutputCapability_->photoProfiles[0];
        profile->size.width = NUM_1920;
        profile->size.height = NUM_1080;
        profile_ = profile;
        if (profile_ == nullptr) {
            OH_LOG_ERROR(LOG_APP, "Get photoProfiles failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
        ret_ = OH_CameraManager_CreatePhotoOutputWithoutSurface(cameraManager_, profile_, &photoOutput_);
        if (photoOutput_ == nullptr || ret_ != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "CreatePhotoOutputWithoutSurfaceId failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
    // ...
        return ret_;
    }
  5. 注册单段式(PhotoAvailable)拍照回调,若应用希望快速得到回图,推荐使用分段式拍照回调(PhotoAssetAvailable)。

    如果已经注册了PhotoAssetAvailable回调,并且在Session开始之后又注册了PhotoAvailable回调,PhotoAssetAvailable和PhotoAvailable同时注册,会导致流被重启,仅PhotoAssetAvailable生效。

    不建议开发者同时注册PhotoAssetAvailable和PhotoAvailable。

    单段式拍照开发流程(PhotoAvailable):

    • 在会话commitConfig前注册单段式拍照回调。

    • 在单段式拍照回调函数中获取图片信息,解析出buffer数据,做自定义业务处理。

    • 将处理完的buffer通过回调传给ArkTS侧,做图片显示或通过安全控件写文件保存图片。

    • 使用完后解注册单段式拍照回调函数。

      // 保存NAPI侧注册的buffer处理回调函数。
      Camera_ErrorCode NDKCamera::RegisterBufferCb(void *cb)
      {
          OH_LOG_INFO(LOG_APP, " RegisterBufferCb start");
          if (cb == nullptr) {
              OH_LOG_INFO(LOG_APP, " RegisterBufferCb invalid error");
              return CAMERA_INVALID_ARGUMENT;
          }
          g_bufferCb = cb;
          return CAMERA_OK;
      }
      
      static bool ProcessImageNative(OH_ImageNative* imageNative, uint32_t** components,
                                     OH_NativeBuffer** nativeBuffer, size_t* nativeBufferSize)
      {
          if (imageNative == nullptr || components == nullptr || nativeBuffer == nullptr || nativeBufferSize == nullptr) {
              return false;
          }
      
          Image_Size size;
          Image_ErrorCode imageErr = OH_ImageNative_GetImageSize(imageNative, &size);
          if (imageErr != IMAGE_SUCCESS) {
              return false;
          }
      
          size_t componentTypeSize = 0;
          imageErr = OH_ImageNative_GetComponentTypes(imageNative, nullptr, &componentTypeSize);
          if (imageErr != IMAGE_SUCCESS || componentTypeSize == 0) {
              OH_LOG_ERROR(LOG_APP, "GetComponentTypes failed: %{public}d, size: %{public}zu",
                  imageErr, componentTypeSize);
              return false;
          }
      
          if (componentTypeSize > (SIZE_MAX / sizeof(uint32_t))) {
              OH_LOG_ERROR(LOG_APP, "componentTypeSize too large: %{public}zu", componentTypeSize);
              return false;
          }
      
          uint32_t* compArray = new (std::nothrow) uint32_t[componentTypeSize];
          if (!compArray) {
              return false;
          }
      
          size_t tempSize = componentTypeSize;
          imageErr = OH_ImageNative_GetComponentTypes(imageNative, &compArray, &tempSize);
          if (imageErr != IMAGE_SUCCESS) {
              delete[] compArray;
              return false;
          }
          *components = compArray;
      
          imageErr = OH_ImageNative_GetByteBuffer(imageNative, compArray[0], nativeBuffer);
          if (imageErr != IMAGE_SUCCESS) {
              delete[] compArray;
              return false;
          }
      
          imageErr = OH_ImageNative_GetBufferSize(imageNative, compArray[0], nativeBufferSize);
          if (imageErr != IMAGE_SUCCESS) {
              delete[] compArray;
              return false;
          }
      
          int32_t rowStride = 0;
          int32_t pixelStride = 0;
          OH_ImageNative_GetRowStride(imageNative, compArray[0], &rowStride);
          OH_ImageNative_GetPixelStride(imageNative, compArray[0], &pixelStride);
          OH_LOG_INFO(LOG_APP, "Buffer size: %{public}zu, strides: %{public}d/%{public}d",
              *nativeBufferSize, rowStride, pixelStride);
      
          return true;
      }
      
      
      static void CleanupResources(OH_ImageNative* imageNative, uint32_t* components,
                                   OH_NativeBuffer* nativeBuffer, void* virAddr)
      {
          if (components) {
              delete[] components;
          }
      
          if (imageNative) {
              int32_t ret = OH_ImageNative_Release(imageNative);
              if (ret != 0) {
                  OH_LOG_ERROR(LOG_APP, "Release image failed: %{public}d", ret);
              }
          }
      
          if (nativeBuffer && virAddr) {
              int32_t ret = OH_NativeBuffer_Unmap(nativeBuffer);
              if (ret != 0) {
                  OH_LOG_ERROR(LOG_APP, "Unmap buffer failed: %{public}d", ret);
              }
          }
      }
      
      void OnPhotoAvailable(Camera_PhotoOutput *photoOutput, OH_PhotoNative *photo)
      {
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable start!");
      
          OH_ImageNative *imageNative = nullptr;
          Camera_ErrorCode errCode = OH_PhotoNative_GetMainImage(photo, &imageNative);
          if (errCode != CAMERA_OK || !imageNative) {
              OH_LOG_ERROR(LOG_APP, "GetMainImage failed: %{public}d", errCode);
              return;
          }
      
          uint32_t* components = nullptr;
          OH_NativeBuffer* nativeBuffer = nullptr;
          size_t nativeBufferSize = 0;
      
          if (!ProcessImageNative(imageNative, &components, &nativeBuffer, &nativeBufferSize)) {
              CleanupResources(imageNative, components, nullptr, nullptr);
              return;
          }
      
          void* virAddr = nullptr;
          int32_t ret = OH_NativeBuffer_Map(nativeBuffer, &virAddr);
          if (ret != 0 || !virAddr) {
              OH_LOG_ERROR(LOG_APP, "Map buffer failed: %{public}d", ret);
              CleanupResources(imageNative, components, nativeBuffer, nullptr);
              return;
          }
      
          auto cb = (void (*)(void *, size_t))(g_bufferCb);
          if (cb && virAddr && nativeBufferSize > 0) {
              cb(virAddr, nativeBufferSize);
              OH_LOG_INFO(LOG_APP, "Buffer callback called");
          } else {
              OH_LOG_ERROR(LOG_APP, "Invalid callback parameters");
          }
      
          CleanupResources(imageNative, components, nativeBuffer, virAddr);
      
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable end");
      }
      
      Camera_ErrorCode NDKCamera::PhotoOutputRegisterPhotoAvailableCallback(void)
      {
          OH_LOG_INFO(LOG_APP, "NDKCamera::PhotoOutputRegisterPhotoAvailableCallback start!");
          Camera_ErrorCode ret = OH_PhotoOutput_RegisterPhotoAvailableCallback(photoOutput_, OnPhotoAvailable);
          if (ret != CAMERA_OK) {
              OH_LOG_INFO(LOG_APP, "NDKCamera::PhotoOutputRegisterPhotoAvailableCallback failed.");
          }
          OH_LOG_INFO(LOG_APP, "NDKCamera::PhotoOutputRegisterPhotoAvailableCallback return with ret code: %{public}d!",
              ret_);
          return ret;
      }
      
      // 解注册单段式拍照回调。
      Camera_ErrorCode NDKCamera::PhotoOutputUnRegisterPhotoAvailableCallback()
      {
          OH_LOG_INFO(LOG_APP, "PhotoOutputUnRegisterPhotoAvailableCallback start!");
          Camera_ErrorCode ret = OH_PhotoOutput_UnregisterPhotoAvailableCallback(photoOutput_, OnPhotoAvailable);
          if (ret != CAMERA_OK) {
              OH_LOG_ERROR(LOG_APP, "PhotoOutputUnRegisterPhotoAvailableCallback failed.");
          }
          OH_LOG_INFO(LOG_APP, "PhotoOutputUnRegisterPhotoAvailableCallback return with ret code: %{public}d!", ret);
          return ret;
      }

      NAPI层buffer回调处理参考示例代码:

      // NAPI层buffer回调方法。
      static void BufferCb(void* buffer, size_t size)
      {
          OH_LOG_INFO(LOG_APP, "BufferCb size:%{public}zu", size);
          g_size = size;
          napi_value asyncResource = nullptr;
          napi_value asyncResourceName = nullptr;
          napi_async_work work;
      
          if (size == 0 || size > SIZE_MAX) {
              OH_LOG_ERROR(LOG_APP, "BufferCb size is invalid");
              return;
          }
          void* copyBuffer = malloc(size);
          if (copyBuffer == nullptr) {
              return;
          }
          OH_LOG_INFO(LOG_APP, "BufferCb copyBuffer:%{public}p", copyBuffer);
          // 使用std::memcpy复制buffer的内容到copyBuffer。
          std::memcpy(copyBuffer, buffer, size);
          napi_create_string_utf8(env_, "BufferCb", NAPI_AUTO_LENGTH, &asyncResourceName);
          napi_status status = napi_create_async_work(
              env_, asyncResource, asyncResourceName, [](napi_env env, void* copyBuffer) {},
              [](napi_env env, napi_status status, void* copyBuffer) {
                  napi_value retVal;
                  napi_value callback = nullptr;
                  void* data = nullptr;
                  napi_value arrayBuffer = nullptr;
                  size_t bufferSize = g_size;
                  napi_create_arraybuffer(env, bufferSize, &data, &arrayBuffer);
                  std::memcpy(data, copyBuffer, bufferSize);
                  OH_LOG_INFO(LOG_APP, "BufferCb g_size: %{public}zu", g_size);
                  napi_get_reference_value(env, bufferCbRef_, &callback);
                  if (callback) {
                      OH_LOG_INFO(LOG_APP, "BufferCb callback is full");
                  } else {
                      OH_LOG_ERROR(LOG_APP, "BufferCb callback is null");
                  }
                  // 调用ArkTS的buffer处理回调函数,将图片arrayBuffer传给页面做显示或保存。
                  napi_call_function(env, nullptr, callback, 1, &arrayBuffer, &retVal);
              },
              copyBuffer, &work);
          // 错误检查:创建异步工作失败时释放内存。
          if (status != napi_ok) {
              OH_LOG_ERROR(LOG_APP, "Failed to create async work");
              free(copyBuffer); // 释放分配的内存。
              return;
          }
          napi_queue_async_work_with_qos(env_, work, napi_qos_user_initiated);
      }
  6. 创建拍照类型会话,参考会话管理(C/C++),开启会话,准备拍照。

  7. 配置拍照参数(可选)。

    配置相机的参数可以调整拍照的一些功能,包括闪光灯、变焦、焦距等。

    Camera_ErrorCode NDKCamera::HasFlashFn(uint32_t mode)
    {
        Camera_FlashMode flashMode = static_cast<Camera_FlashMode>(mode);
        // 检查闪光灯。
        bool hasFlash = false;
        Camera_ErrorCode ret = OH_CaptureSession_HasFlash(captureSession_, &hasFlash);
        if (captureSession_ == nullptr || ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_HasFlash failed.");
        }
        if (hasFlash) {
            OH_LOG_INFO(LOG_APP, "hasFlash success-----");
        } else {
            OH_LOG_ERROR(LOG_APP, "hasFlash fail-----");
        }
    
        // 查询闪光灯模式是否支持。
        bool isSupported = false;
        ret = OH_CaptureSession_IsFlashModeSupported(captureSession_, flashMode, &isSupported);
        if (ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsFlashModeSupported failed.");
        }
        if (isSupported) {
            OH_LOG_INFO(LOG_APP, "isFlashModeSupported success-----");
        } else {
            OH_LOG_ERROR(LOG_APP, "isFlashModeSupported fail-----");
        }
    
        // 设置闪光灯模式。
        ret = OH_CaptureSession_SetFlashMode(captureSession_, flashMode);
        if (ret == CAMERA_OK) {
            OH_LOG_INFO(LOG_APP, "OH_CaptureSession_SetFlashMode success.");
        } else {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetFlashMode failed. %{public}d ", ret);
        }
    
        // 获取当前设备的闪光灯模式。
        ret = OH_CaptureSession_GetFlashMode(captureSession_, &flashMode);
        if (ret == CAMERA_OK) {
            OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetFlashMode success. flashMode:%{public}d ", flashMode);
        } else {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetFlashMode failed. %d ", ret);
        }
        return ret;
    }
    
    // 对焦模式。
    Camera_ErrorCode NDKCamera::IsFocusModeSupported(uint32_t mode)
    {
        Camera_FocusMode focusMode = static_cast<Camera_FocusMode>(mode);
        ret_ = OH_CaptureSession_IsFocusModeSupported(captureSession_, focusMode, &isFocusModeSupported_);
        if (&isFocusModeSupported_ == nullptr || ret_ != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "IsFocusModeSupported failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
        return ret_;
    }
    
    Camera_ErrorCode NDKCamera::IsFocusMode(uint32_t mode)
    {
        OH_LOG_INFO(LOG_APP, "IsFocusMode start.");
        Camera_FocusMode focusMode = static_cast<Camera_FocusMode>(mode);
        ret_ = OH_CaptureSession_IsFocusModeSupported(captureSession_, focusMode, &isFocusModeSupported_);
        if (&isFocusModeSupported_ == nullptr || ret_ != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "IsFocusModeSupported failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
        ret_ = OH_CaptureSession_SetFocusMode(captureSession_, focusMode);
        if (ret_ != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "SetFocusMode failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
        ret_ = OH_CaptureSession_GetFocusMode(captureSession_, &focusMode);
        if (&focusMode == nullptr || ret_ != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "GetFocusMode failed.");
            return CAMERA_INVALID_ARGUMENT;
        }
        OH_LOG_INFO(LOG_APP, "IsFocusMode end.");
        return ret_;
    }
    
    Camera_ErrorCode NDKCamera::setZoomRatioFn(uint32_t zoomRatio)
    {
        float zoom = float(zoomRatio);
        // 获取支持的缩放范围。
        float minZoom;
        float maxZoom;
        Camera_ErrorCode ret = OH_CaptureSession_GetZoomRatioRange(captureSession_, &minZoom, &maxZoom);
        if (captureSession_ == nullptr || ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetZoomRatioRange failed.");
        } else {
            OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetZoomRatioRange success. minZoom: %{public}f, maxZoom:%{public}f",
                minZoom, maxZoom);
        }
    
        // 设置缩放比例。
        ret = OH_CaptureSession_SetZoomRatio(captureSession_, zoom);
        if (ret == CAMERA_OK) {
            OH_LOG_INFO(LOG_APP, "OH_CaptureSession_SetZoomRatio success.");
        } else {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetZoomRatio failed. %{public}d ", ret);
        }
    
        // 获取当前设备的缩放比例。
        ret = OH_CaptureSession_GetZoomRatio(captureSession_, &zoom);
        if (ret == CAMERA_OK) {
            OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetZoomRatio success. zoom:%{public}f ", zoom);
        } else {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetZoomRatio failed. %{public}d ", ret);
        }
        return ret;
    }
  8. 触发拍照。

    通过OH_PhotoOutput_Capture_WithCaptureSetting()方法,执行拍照任务。

    Camera_ErrorCode NDKCamera::TakePicture(int32_t degree)
    {
        Camera_ErrorCode ret = CAMERA_OK;
        Camera_ImageRotation imageRotation;
        bool isMirSupported;
        OH_PhotoOutput_IsMirrorSupported(photoOutput_, &isMirSupported);
        OH_PhotoOutput_GetPhotoRotation(photoOutput_, degree, &imageRotation);
    
        Camera_PhotoCaptureSetting curPhotoSetting = {
            quality : QUALITY_LEVEL_HIGH,
            rotation : imageRotation,
            mirror : isMirSupported
        };
        ret = OH_PhotoOutput_Capture_WithCaptureSetting(photoOutput_, curPhotoSetting);
        OH_LOG_INFO(LOG_APP, "TakePicture get quality %{public}d, rotation %{public}d, mirror %{public}d",
            curPhotoSetting.quality, curPhotoSetting.rotation, curPhotoSetting.mirror);
        OH_LOG_INFO(LOG_APP, "TakePicture ret = %{public}d.", ret);
        return ret;
    }

高性能拍照

从API version 21开始支持高性能拍照功能,即在进行单段式拍照时设置明确的画质优先策略。

单段式拍照的体验主要由出图速度和最终图片质量衡量。因此,为满足开发者在不同场景下的差异化需求,对这两项指标的侧重也不同。例如,街头抓拍要求快速捕捉瞬间,而风景或人像拍摄则更追求极致的画质。

仅单段式拍照支持设置画质优先策略。若在分段式拍照中设置画质优先策略,该设置将无效。

[h2]画质优先策略

在使用单段式拍照时,支持设置速度优先和画质优先两种画质优先策略类型,并且分别对应着不同的Camera_PhotoQualityPrioritization枚举类型。

[h2]如何正确设置画质优先策略

为了正确地在单段式拍照中设置画质优先策略,高性能拍照功能提供了如下两个接口:

[h2]开发步骤

高性能拍照相关接口需要在会话管理(C/C++)流程的使能步骤中进行调用。

具体调用时机如下:

状态监听

在相机应用开发过程中,可以随时监听拍照输出流状态,包括拍照流开始、拍照帧的开始与结束、拍照输出流的错误。