Harmony-拍照实现

188 阅读1分钟

**camerakit自定义相机拍照**

# 一、申请相关的权限
{
      "name" : "ohos.permission.xxxxxx", // 需要使用的权限名称
      "reason": "$string:reason",        // 申请权限的原因(当申请的权限为user——grant时必填!!)=> 必须去resources/base/element/string.json中声明(注意国际化)
      "usedScene": { // 权限使用的场景
        "abilities": [ //选填
          "EntryAbility"  // 使用权限的UIAbility或者ExtensionAbility组件的名称
        ],
        "when":"inuse" // 调用时机:inuse-使用时, always-始终
      }
},

// reason字段内容在实际向用户弹窗申请授权时显示
  1. (必选)申请相机权限:ohos.permission.CAMERA
  2. (可选)申请麦克风权限(录制音频):ohos.permission.MICROPHONE
  3. (可选)申请地理位置信息权限:ohos.permission.MEDIA_LOCATION

二、校验当前是否已经授权-checkAccessToken()

注意:这一步是在权限申请之前,需要先检查当前应用程序是否被授予权限


async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;

  // 获取应用程序的accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo =
      await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus;
}

async function checkPermissions(): Promise<void> {
  let grantStatus1: boolean = await checkPermissionGrant('ohos.permission.MEDIA_LOCATION') ===
  abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED; // 获取定位权限状态
  let grantStatus2: boolean = await checkPermissionGrant('ohos.permission.ohos.permission.CAMERA') ===
  abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED; // 获取相机权限状态
  if (grantStatus2 && grantStatus1)  {
    // 已经授权,可以继续访问目标操作
  }
}

三、动态向用户申请授权-requestPermissionsFromUser()

两种方式:

  • 在UIAbility的onWindowStageCreate()的loadContent()/setUIContent()回调中申请授权
const permissions: Array<Permissions> = ['ohos.permission.MEDIA_LOCATION', 'ohos.permission.ohos.permission.CAMERA'];

function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
  atManager.requestPermissionsFromUser(context, permissions).then((data) => {
    let grantStatus: Array<number> = data.authResults;
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
      } else {
        // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
        return;
      }
    }
    // 授权成功
  }).catch((err: BusinessError) => {
    console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
  })
}


export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // ...
    windowStage.loadContent('pages/Index', (err, data) => {
      reqPermissionsFromUser(permissions, this.context);
      // ...
    });
  }

  // ...
}
  • 在UIExtensionAbility的onWindowStageCreate()回调中调用(同上,仅需将common.UIAbilityContext 替换为common.UIExtensionContext)

四、处理授权结果

注意:如果用户拒绝授权,应用不会再次拉起弹框,应将用户引导至系统应用“设置”界面-requestPermissionOnSeting()

let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let context: Context = getContext(this) as common.UIAbilityContext;
atManager.requestPermissionOnSetting(context, ['ohos.permission.ohos.permission.CAMERA','ohos.permission.MEDIA_LOCATION']).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
  console.info('data:' + JSON.stringify(data));
}).catch((err: BusinessError) => {
  console.error('data:' + JSON.stringify(err));
});

五、创建一个相机设备

// 创建CameraManager对象
let cameraManager: camera.CameraManager = camera.getCameraManager(baseContext);
if (!cameraManager) {
  console.error("camera.getCameraManager error");
  return;
}

// 获取当前设备支持的相机列表,列表中存储了设备支持的所有相机ID
let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
if (cameraArray.length <= 0) {
  console.error("cameraManager.getSupportedCameras error");
  return;
}

// 创建相机输入流
let cameraInput: camera.CameraInput | undefined = undefined;
try {
  cameraInput = cameraManager.createCameraInput(cameraArray[0]);
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to createCameraInput errorCode = ' + err.code);
}
if (cameraInput === undefined) {
  return;
}

// 监听cameraInput错误信息
let cameraDevice: camera.CameraDevice = cameraArray[0];
cameraInput.on('error', cameraDevice, (error: BusinessError) => {
  console.error(`Camera input error code: ${error.code}`);
})

// 打开相机
await cameraInput.open();

// 获取相机设备支持的输出流能力
let cameraOutputCap: camera.CameraOutputCapability =
  cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO);
if (!cameraOutputCap) {
  console.error("cameraManager.getSupportedOutputCapability error");
  return;
}
console.info("outputCapability: " + JSON.stringify(cameraOutputCap));
// 【会话的切换:控制输入流的方式->切换相机模式(拍照或者录像)】
// 创建会话
let photoSession: camera.PhotoSession | undefined = undefined;
try {
  photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to create the session instance. errorCode = ' + err.code);
}
if (photoSession === undefined) {
  return;
}
// 监听session错误信息
photoSession.on('error', (error: BusinessError) => {
  console.error(`Capture session error code: ${error.code}`);
});

// 获取profile对象
let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;
if (!previewProfilesArray) {
  console.error("createOutput previewProfilesArray == null || undefined");
}

let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;
if (!photoProfilesArray) {
  console.error("createOutput photoProfilesArray == null || undefined");
}

// 创建预览输出流,
let previewOutput: camera.PreviewOutput | undefined = undefined;
try {
  previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], XComponentSurfaceId);
} catch (error) {
  let err = error as BusinessError;
  console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`);
}
if (previewOutput === undefined) {
  return;
}
// 监听预览输出错误信息
previewOutput.on('error', (error: BusinessError) => {
  console.error(`Preview output error code: ${error.code}`);
});

// 创建拍照输出流
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
  photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to createPhotoOutput errorCode = ' + err.code);
}
if (photoOutput === undefined) {
  return;
}

// 开始配置会话
try {
  photoSession.beginConfig();
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to beginConfig. errorCode = ' + err.code);
}

// 向会话中添加相机输入流(添加设备输入)
try {
  photoSession.addInput(cameraInput);
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to addInput. errorCode = ' + err.code);
}

// 向会话中添加预览输出流(数据以什么形式输出,图片还是录像)
try {
  photoSession.addOutput(previewOutput);
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code);
}

// 向会话中添加拍照输出流
try {
  photoSession.addOutput(photoOutput);
} catch (error) {
  let err = error as BusinessError;
  console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code);
}

// 提交会话配置
await photoSession.commitConfig();

// 启动会话
await photoSession.start().then(() => {
  console.info('Promise returned to indicate the session start success.');
});

六、实现拍照功能

// 创建XComponent
XComponent({
  id: '',
  type: 'surface',
  libraryname: '',
  controller: this.mXComponentController
})
  .onLoad(async () => {
    // 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
    // 预览流与录像输出流的分辨率的宽高比要保持一致
    this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: cameraWidth, surfaceHeight: cameraHeight });
    // 获取Surface ID
    this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
  })

// 接收图片数据
function setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {
  //设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中
  photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
    console.info('getPhoto start');
    console.info(`err: ${JSON.stringify(errCode)}`);
    if (errCode || photo === undefined) {
      console.error('getPhoto failed');
      return;
    }
    let imageObj = photo.main;
    imageObj.getComponent(image.ComponentType.JPEG, (errCode: BusinessError, component: image.Component): void => {
      console.info('getComponent start');
      if (errCode || component === undefined) {
        console.error('getComponent failed');
        return;
      }
      let buffer: ArrayBuffer;
      if (component.byteBuffer) {
        buffer = component.byteBuffer;
      } else {
        console.error('byteBuffer is null');
        return;
      }
     
      // buffer处理结束后需要释放该资源,如果未正确释放资源会导致后续拍照获取不到buffer
      imageObj.release();
    });
  });
}

// 触发拍照
let photoCaptureSetting: camera.PhotoCaptureSetting = {
  quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高
  rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0
}
// 使用当前拍照设置进行拍照
photoOutput.capture(photoCaptureSetting, (err: BusinessError) => {
  if (err) {
    console.error(`Failed to capture the photo ${err.message}`);
    return;
  }
  console.info('Callback invoked to indicate the photo capture request success.');
});