拍照(C/C++)

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

开发步骤

详细的API说明请参考Camera API参考

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

    // 导入NDK接口头文件。
    #include <cstdint>
    #include <cstdlib>
    #include <cstring>
    #include <string.h>
    #include <new>
    #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 "ohcamera/camera_manager.h"
    #include <multimedia/image_framework/image/image_native.h>
    
  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_PhotoOutput* CreatePhotoOutput(Camera_Manager* cameraManager, const Camera_Profile* photoProfile) {
        Camera_PhotoOutput* photoOutput = nullptr;
        // 无需传入surfaceId,直接创建拍照流。
        Camera_ErrorCode ret = OH_CameraManager_CreatePhotoOutputWithoutSurface(cameraManager, photoProfile, &photoOutput);
        if (photoOutput == nullptr || ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CameraManager_CreatePhotoOutputWithoutSurface failed.");
        }
        return photoOutput;
    }
    
  5. 注册单段式(PhotoAvailable)拍照回调,若应用希望快速得到回图,推荐使用分段式拍照回调(PhotoAssetAvailable)

    注意:

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

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

    单段式拍照开发流程(PhotoAssetAvailable)

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

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

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

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

      // 保存NAPI侧注册的buffer处理回调函数。
      static void* bufferCb = nullptr;
      Camera_ErrorCode 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;
          }
          bufferCb = cb;
          return CAMERA_OK;
      }
      
      // 单段式拍照回调函数。
      void OnPhotoAvailable(Camera_PhotoOutput* photoOutput, OH_PhotoNative* photo) {
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable start!");
          OH_ImageNative* imageNative;
          Camera_ErrorCode errCode = OH_PhotoNative_GetMainImage(photo, &imageNative);
          if (errCode != CAMERA_OK || imageNative == nullptr) {
              OH_LOG_ERROR(LOG_APP, "OH_PhotoNative_GetMainImage call failed, errorCode: %{public}d", errCode);
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable errCode:%{public}d imageNative:%{public}p", errCode, imageNative);
          // 读取OH_ImageNative的size属性。
          Image_Size size;
          Image_ErrorCode imageErr = OH_ImageNative_GetImageSize(imageNative, &size);
          if (imageErr != IMAGE_SUCCESS) {
               OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetImageSize call failed, errorCode: %{public}d", imageErr);
               OH_ImageNative_Release(imageNative);
               return;
           }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable imageErr:%{public}d width:%{public}d height:%{public}d", imageErr,
             size.width, size.height);
          // 读取OH_ImageNative的组件列表的元素个数。
          size_t componentTypeSize = 0;
          imageErr = OH_ImageNative_GetComponentTypes(imageNative, nullptr, &componentTypeSize);
          if (imageErr != IMAGE_SUCCESS || componentTypeSize == 0) {
              OH_LOG_ERROR(LOG_APP, "cOH_ImageNative_GetComponentTypes call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable imageErr:%{public}d componentTypeSize:%{public}zu", imageErr,
             componentTypeSize);
          // 读取OH_ImageNative的组件列表。
          uint32_t* components = new (std::nothrow) uint32_t[componentTypeSize];
          if (!components) {
              OH_LOG_ERROR(LOG_APP, "Failed to allocate memory");
              OH_ImageNative_Release(imageNative);
              return;
          }
          imageErr = OH_ImageNative_GetComponentTypes(imageNative, &components, &componentTypeSize);
          if (imageErr != IMAGE_SUCCESS) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetComponentTypes call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable OH_ImageNative_GetComponentTypes imageErr:%{public}d", imageErr);
          // 读取OH_ImageNative的第一个组件所对应的缓冲区对象。
          OH_NativeBuffer* nativeBuffer = nullptr;
          imageErr = OH_ImageNative_GetByteBuffer(imageNative, components[0], &nativeBuffer);
          if (imageErr != IMAGE_SUCCESS) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetByteBuffer call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable OH_ImageNative_GetByteBuffer imageErr:%{public}d", imageErr);
          // 读取OH_ImageNative的第一个组件所对应的缓冲区大小。
          size_t nativeBufferSize = 0;
          imageErr = OH_ImageNative_GetBufferSize(imageNative, components[0], &nativeBufferSize);
          if (imageErr != IMAGE_SUCCESS) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetBufferSize call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable imageErr:%{public}d nativeBufferSize:%{public}zu", imageErr,
             nativeBufferSize);
          // 读取OH_ImageNative的第一个组件所对应的像素行宽。
          int32_t rowStride = 0;
          imageErr = OH_ImageNative_GetRowStride(imageNative, components[0], &rowStride);
          if (imageErr != IMAGE_SUCCESS) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetRowStride call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable imageErr:%{public}d rowStride:%{public}d", imageErr, rowStride);
          // 读取OH_ImageNative的第一个组件所对应的像素大小。
          int32_t pixelStride = 0;
          imageErr = OH_ImageNative_GetPixelStride(imageNative, components[0], &pixelStride);
          if (imageErr != IMAGE_SUCCESS) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_GetPixelStride call failed, errorCode: %{public}d", imageErr);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable imageErr:%{public}d pixelStride:%{public}d", imageErr, pixelStride);
          // 将ION内存映射到进程空间。
          void* virAddr = nullptr; // 指向映射内存的虚拟地址,解除映射后这个指针将不再有效。
          int32_t ret = OH_NativeBuffer_Map(nativeBuffer, &virAddr); // 映射后通过第二个参数virAddr返回内存的首地址。
          if (ret != 0) {
              OH_LOG_ERROR(LOG_APP, "OH_NativeBuffer_Map call failed, errorCode: %{public}d", ret);
              OH_ImageNative_Release(imageNative);
              delete[] components;
              return;
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable OH_NativeBuffer_Map err:%{public}d", ret);
          // 调用NAPI层buffer回调。
          auto cb = (void (*)(void *, size_t))(bufferCb);
          if (!virAddr || nativeBufferSize <= 0) {
            OH_LOG_INFO(LOG_APP, "On buffer callback failed");
            return;
          }
          cb(virAddr, nativeBufferSize);
          // 释放资源。
          delete[] components;
          ret = OH_ImageNative_Release(imageNative);
          if (ret != 0) {
              OH_LOG_ERROR(LOG_APP, "OH_ImageNative_Release call failed., errorCode: %{public}d", ret);
          }
          ret = OH_NativeBuffer_Unmap(nativeBuffer); // 在处理完之后,解除映射并释放缓冲区。
          if (ret != 0) {
              OH_LOG_ERROR(LOG_APP, "OH_NativeBuffer_Unmap call failed, errorCode: %{public}d", ret);
          }
          OH_LOG_INFO(LOG_APP, "OnPhotoAvailable end");
      }
      
      // 注册单段式拍照回调。
      Camera_ErrorCode PhotoOutputRegisterPhotoAvailableCallback(Camera_PhotoOutput* photoOutput) {
          OH_LOG_INFO(LOG_APP, "PhotoOutputRegisterPhotoAvailableCallback start!");
          Camera_ErrorCode ret = OH_PhotoOutput_RegisterPhotoAvailableCallback(photoOutput, OnPhotoAvailable);
          if (ret != CAMERA_OK) {
              OH_LOG_ERROR(LOG_APP, "PhotoOutputRegisterPhotoAvailableCallback failed.");
          }
          OH_LOG_INFO(LOG_APP, "PhotoOutputRegisterPhotoAvailableCallback return with ret code: %{public}d!", ret);
          return ret;
      }
      
      // 解注册单段式拍照回调。
      Camera_ErrorCode PhotoOutputUnRegisterPhotoAvailableCallback(Camera_PhotoOutput* photoOutput) {
          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回处理参考示例代码:

      static napi_ref bufferCbRef_ = nullptr;
      static napi_env env_;
      size_t g_size = 0;
      
      // 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;
      
          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);
                  // 清理内存。
                  free(data); // 释放在异步工作中分配的内存。
                  free(copyBuffer);
              },
              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);
      }
      
      // 保存ArkTS侧传入的buffer处理回调函数。
      static napi_value SetBufferCb(napi_env env, napi_callback_info info) {
          OH_LOG_INFO(LOG_APP, "SetBufferCb start");
          napi_value result;
          napi_get_undefined(env, &result);
      
          napi_value argValue[1] = {nullptr};
          size_t argCount = 1;
          napi_get_cb_info(env, info, &argCount, argValue, nullptr, nullptr);
      
          env_ = env;
          napi_create_reference(env, argValue[0], 1, &bufferCbRef_);
          if (bufferCbRef_) {
              OH_LOG_INFO(LOG_APP, "SetBufferCb callbackRef is full");
          } else {
              OH_LOG_ERROR(LOG_APP, "SetBufferCb callbackRef is null");
          }
          // 注册ArkTS侧buffer回调到NAPI层。
          RegisterBufferCb((void *)BufferCb);
          return result;
      }
      
  6. 创建拍照类型会话,参考会话管理(C/C++),开启会话,准备拍照。

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

    // 判断设备是否支持闪光灯。
    bool HasFlash(Camera_CaptureSession* captureSession)
    {
        bool hasFlash = false;
        Camera_ErrorCode ret = OH_CaptureSession_HasFlash(captureSession, &hasFlash);
        if (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");
        }
        return hasFlash;
    }
    
    // 检测闪光灯模式是否支持。
    bool IsFlashModeSupported(Camera_CaptureSession* captureSession, Camera_FlashMode flashMode)
    {
        bool isSupported = false;
        Camera_ErrorCode ret = OH_CaptureSession_IsFlashModeSupported(captureSession, flashMode, &isSupported);
        if (ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsFlashModeSupported failed.");
        }
        return isSupported;
    }
    // 在支持flashMode的情况下进行调用OH_CaptureSession_SetFlashMode。
    Camera_ErrorCode SetFlashMode(Camera_CaptureSession* captureSession, Camera_FlashMode flashMode)
    {
        Camera_ErrorCode 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);
        }
        return ret;
    }
    
    // 判断是否支持连续自动变焦模式。
    bool IsFocusModeSupported(Camera_CaptureSession* captureSession, Camera_FocusMode focusMode)
    {
        bool isFocusModeSupported = false;
        Camera_ErrorCode ret = OH_CaptureSession_IsFocusModeSupported(captureSession, focusMode, &isFocusModeSupported);
        if (ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsFocusModeSupported failed.");
        }
        return isFocusModeSupported;
    }
    // 在支持focusMode的情况下进行OH_CaptureSession_SetFocusMode。
    Camera_ErrorCode SetFocusMode(Camera_CaptureSession* captureSession, Camera_FocusMode focusMode)
    {
        Camera_ErrorCode ret = OH_CaptureSession_SetFocusMode(captureSession, focusMode);
        if (ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetFocusMode failed. %{public}d ", ret);
        }
        return ret;
    }
    
    // 获取相机支持的可变焦距比范围。
    Camera_ErrorCode GetZoomRatioRange(Camera_CaptureSession* captureSession, float* minZoom, float* maxZoom)
    {
        Camera_ErrorCode ret = OH_CaptureSession_GetZoomRatioRange(captureSession, minZoom, maxZoom);
        if (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);
        }
        return ret;
    }
    
    // 设置变焦,zoom需要在可变焦距比范围内。
    Camera_ErrorCode SetZoomRatio(Camera_CaptureSession* captureSession, float zoom)
    {
        Camera_ErrorCode 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);
        }
        return ret;
    }
    
  8. 触发拍照。

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

    Camera_ErrorCode Capture(Camera_PhotoOutput* photoOutput)
    {
        Camera_ErrorCode ret = OH_PhotoOutput_Capture(photoOutput);
        if (ret == CAMERA_OK) {
            OH_LOG_INFO(LOG_APP, "OH_PhotoOutput_Capture success ");
        } else {
            OH_LOG_ERROR(LOG_APP, "OH_PhotoOutput_Capture failed. %d ", ret);
        }
        return ret;
    }
    

状态监听

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

  • 通过注册固定的onFrameStart回调函数获取监听拍照开始结果,photoOutput创建成功时即可监听,拍照第一次曝光时触发。

    void PhotoOutputOnFrameStart(Camera_PhotoOutput* photoOutput)
    {
        OH_LOG_INFO(LOG_APP, "PhotoOutputOnFrameStart");
    }
    void PhotoOutputOnFrameShutter(Camera_PhotoOutput* photoOutput, Camera_FrameShutterInfo* info)
    {
        OH_LOG_INFO(LOG_APP, "PhotoOutputOnFrameShutter");
    }
    
  • 通过注册固定的onFrameEnd回调函数获取监听拍照结束结果,photoOutput创建成功时即可监听。

    void PhotoOutputOnFrameEnd(Camera_PhotoOutput* photoOutput, int32_t frameCount)
    {
        OH_LOG_INFO(LOG_APP, "PhotoOutput frameCount = %{public}d", frameCount);
    }
    
  • 通过注册固定的onError回调函数获取监听拍照输出流的错误结果。callback返回拍照输出接口使用错误时的对应错误码,错误码类型参见Camera_ErrorCode

    void PhotoOutputOnError(Camera_PhotoOutput* photoOutput, Camera_ErrorCode errorCode)
    {
        OH_LOG_INFO(LOG_APP, "PhotoOutput errorCode = %{public}d", errorCode);
    }
    
    PhotoOutput_Callbacks* GetPhotoOutputListener()
    {
        static PhotoOutput_Callbacks photoOutputListener = {
            .onFrameStart = PhotoOutputOnFrameStart,
            .onFrameShutter = PhotoOutputOnFrameShutter,
            .onFrameEnd = PhotoOutputOnFrameEnd,
            .onError = PhotoOutputOnError
        };
        return &photoOutputListener;
    }
    Camera_ErrorCode RegisterPhotoOutputCallback(Camera_PhotoOutput* photoOutput)
    {
        Camera_ErrorCode ret = OH_PhotoOutput_RegisterCallback(photoOutput, GetPhotoOutputListener());
        if (ret != CAMERA_OK) {
            OH_LOG_ERROR(LOG_APP, "OH_PhotoOutput_RegisterCallback failed.");
        }
        return ret;
    }