鸿蒙上成功调用相机!

描述

相机是一个系统的基础能力,能够通过调用相机进行拍照,在很多场景下都会使用到相机的调用,如人脸识别门禁,人脸解锁等操作。

本文主要介绍在 OpenHarmony 应用开发中 ArkUI 开发框架下相机应用的开发。

开发模式:Stage 开发模式

SDK 版本:3.2.2.5

开发环境:DevEco Studio 3.0 Release 3.0.0.993

实现步骤

①声明权限

在 module.json5 中配置权限:

 

 "reqPermissions": [   {
        "name": "ohos.permission.LOCATION",
      },
      {
        "name": "ohos.permission.CAMERA"
      },
      {
        "name": "ohos.permission.MICROPHONE"
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION"
      },
      {
        "name": "ohos.permission.WRITE_MEDIA"
      },
      {
        "name": "ohos.permission.READ_MEDIA"
      }]

 

在 MainAbility.ts 中调用 requestPermissionsFromUser 方法申请权限:

 

 const PERMISSIONS: Array = [
    'ohos.permission.CAMERA',
    'ohos.permission.MICROPHONE',
    'ohos.permission.MEDIA_LOCATION',
    'ohos.permission.READ_MEDIA',
    'ohos.permission.WRITE_MEDIA',
    'ohos.permission.GET_WIFI_INFO ',
    'ohos.permission.GET_WIFI_PEERS_MAC ',
]

           globalThis.abilityWant = want;
        globalThis.context = this.context
        globalThis.abilityContext = this.context;

globalThis.context.requestPermissionsFromUser(PERMISSIONS).then((message)=>{
            console.log(JSON.stringify(message))
        })

 

注意:权限需要在页面加载前提前申请,所以需要在调用相机的页面前添加一个过渡的页面。

②准备工作

导包:

 

import camera from '@ohos.multimedia.camera';
import image from '@ohos.multimedia.image';
import fileio from '@ohos.fileio';
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
const CameraSize = {
  WIDTH: 640,
  HEIGHT: 480
}
定义变量:
  private mXComponentController = new XComponentController()
  private cameraManager: camera.CameraManager = undefined
  private cameras: Array = undefined
  private cameraId: string = undefined
  private mReceiver: image.ImageReceiver = undefined
  private cameraInput: camera.CameraInput = undefined
  private previewOutput: camera.PreviewOutput = undefined
  private mSurfaceId: string = undefined
  private photoOutput: camera.PhotoOutput = undefined
  private captureSession: camera.CaptureSession = undefined
  private mediaUtil: MediaUtil = undefined
  @State desStr: string = ""
  private fileAsset: mediaLibrary.FileAsset
  private surfaceId: number
  @State photoUriMedia: string = ""
  private photoFlag: boolean = true
  @State  imgUrl: string = ""
  @State isMediaUrl:boolean=true  //判断保存路径为是沙箱路径或者媒体路径,默认媒体路径
  aboutToAppear(){
    this.mediaTest = mediaLibrary.getMediaLibrary(globalThis.context)
  }
工具方法:
  async createAndGetUri(mediaType: number) {
    let info = this.getInfoFromType(mediaType)
    let dateTimeUtil = new DateTimeUtil()
    let name = `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`
    let displayName = `${info.prefix}${name}${info.suffix}`
    let publicPath = await this.mediaTest.getPublicDirectory(info.directory)
    let dataUri = await this.mediaTest.createAsset(mediaType, displayName, publicPath)
    return dataUri
  }
  async getFdPath(fileAsset: any) {
    let fd = await fileAsset.open('Rw')
    return fd
  }
  getInfoFromType(mediaType: number) {
    let result = {
      prefix: '', suffix: '', directory: 0
    }
    switch (mediaType) {
      case mediaLibrary.MediaType.FILE:
        result.prefix = 'FILE_'
        result.suffix = '.txt'
        result.directory = mediaLibrary.DirectoryType.DIR_DOCUMENTS
        break
      case mediaLibrary.MediaType.IMAGE:
        result.prefix = 'IMG_'
        result.suffix = '.jpg'
        result.directory = mediaLibrary.DirectoryType.DIR_IMAGE
        break
      case mediaLibrary.MediaType.VIDEO:
        result.prefix = 'VID_'
        result.suffix = '.mp4'
        result.directory = mediaLibrary.DirectoryType.DIR_VIDEO
        break
      case mediaLibrary.MediaType.AUDIO:
        result.prefix = 'AUD_'
        result.suffix = '.wav'
        result.directory = mediaLibrary.DirectoryType.DIR_AUDIO
        break
    }
    return result
  }
工具类:
/**
 * @file 日期工具
 */
export default class DateTimeUtil {

    /**
   * 时分秒
   */
    getTime() {
        const DATETIME = new Date()
        return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds())
    }

    /**
   * 年月日
   */
    getDate() {
        const DATETIME = new Date()
        return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate())
    }

    /**
   * 日期不足两位补充0
   * @param value-数据值
   */
    fill(value: number) {
        return (value > 9 ? '' : '0') + value
    }

    /**
   * 年月日格式修饰
   * @param year
   * @param month
   * @param date
   */
    concatDate(year: number, month: number, date: number) {
        return `${year}${this.fill(month)}${this.fill(date)}`
    }

    /**
   * 时分秒格式修饰
   * @param hours
   * @param minutes
   * @param seconds
   */
    concatTime(hours: number, minutes: number, seconds: number) {
        return `${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}`
    }
}

 

这个工具类主要是用来进行获取时间对相片进行命名的工具类。

③构建 UI 组件

页面主要分为 2 块,左边为相机的 XComponent 组件,右边为图片显示区域。拍完的照片能够显示在右边。 XComponent 组件作用于 EGL/OpenGLES 和媒体数据写入,并显示在 XComponent 组件。

相关资料:

 

https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-xcomponent-0000001333800561

 

hml 代码如下:

 

  build() {
    Flex() {
      Flex() {
        Stack() {
          Flex() {
            //相机显示的组件
            XComponent({
              id: 'componentId',
              type: 'surface',
              controller: this.mXComponentController
            }).onLoad(() => {
              this.mXComponentController.setXComponentSurfaceSize({ surfaceWidth: 640, surfaceHeight: 480 })
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId()
              this.initCamera(this.surfaceId)
            })
          }.width(800).height(800)
          //显示在相机上面的组件:拍照和摄像的图标,摄像的时间
          Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.End, alignItems: ItemAlign.Center }) {
            if (this.photoFlag) { //拍照
              Image($r("app.media.take_photo_normal")).width(50).height(50).onClick(() => {
                this.desStr = "拍照完成"
                this.takePicture()
              })
            }
            Text(this.desStr).fontColor("red").height(30).fontSize(20)
          }.width(480).height(480)
        }.border({ width: 1, style: BorderStyle.Solid, color: "#000000" })
        //右边的控制button和图片显示区域
        Flex({
          direction: FlexDirection.Column,
          justifyContent: FlexAlign.SpaceBetween,
          alignItems: ItemAlign.Center,
        }) {
          Button("选择沙箱路径存储").onClick(()=>{
              this.isMediaUrl=false
          }) .stateStyles({
            normal: { // 设置默认情况下的显示样式
              .backgroundColor(Color.Blue)
            },
            pressed: { // 设置手指摁下时的显示样式
              .backgroundColor(Color.Pink)
            }
          })
          Image(decodeURI("file://"+this.imgUrl)).width(480).height(350)//显示沙箱图片
          Button("选择媒体路径存储").onClick(()=>{
            this.isMediaUrl=true
          }) .stateStyles({
            normal: { // 设置默认情况下的显示样式
              .backgroundColor(Color.Blue)
            },
            pressed: { // 设置手指摁下时的显示样式
              .backgroundColor(Color.Pink)
            }
          })
          Image(decodeURI(this.imgUrl)).width(480).height(350)   //显示媒体图片
        }.width(480).height("100%").border({ width: 1, style: BorderStyle.Solid, color: "#000000" })

      }.border({ width: 1, style: BorderStyle.Solid, color: "red" })
      .width("100%").height("100%")
    }
    .height('100%').width("100%")
  }
UI 实现了对存储路径的选择,需要存储到沙箱路径还是媒体路径。

 

注意:沙箱路径需要加上"file://",查看对应的存储路径步骤:

打开 hdc 命令窗口

cd /data/app/el2/100/base/com.chinasoft.photo/haps/entry/files进入

ls 查看全部文件

④拍照流程

初始化相机:这一步需要在拍照前就进行,一般是在 XComponent 组件的 onLoad() 中进行的。

 

   //初始化相机和会话管理
  async initCamera(surfaceId: number) {
    this.cameraManager = await camera.getCameraManager(globalThis.context)//需要在Ability中定义globalThis.context=this.context
    this.cameras = await this.cameraManager.getCameras()
    this.cameraId = this.cameras[1].cameraId
    await this.photoReceiver() //创建图片接收器并进行订阅
    this.mSurfaceId = await this.mReceiver.getReceivingSurfaceId()
    this.cameraInput = await this.cameraManager.createCameraInput(this.cameraId)
    this.previewOutput = await camera.createPreviewOutput(surfaceId.toString())
    this.photoOutput = await camera.createPhotoOutput(this.mSurfaceId)

    this.captureSession = await camera.createCaptureSession(globalThis.context)
    await this.captureSession.beginConfig()
    await this.captureSession.addInput(this.cameraInput)
    await this.captureSession.addOutput(this.previewOutput)
    await this.captureSession.addOutput(this.photoOutput)
    await this.captureSession.commitConfig()
    await this.captureSession.start().then(() => {
      console.log('zmw1--Promise returned to indicate the session start success.');
    })
  }
  //创建图片接收器并进行订阅
  async photoReceiver() {
    this.mReceiver = image.createImageReceiver(CameraSize.WIDTH, CameraSize.HEIGHT, 4, 8)
    let buffer = new ArrayBuffer(4096)
    this.mReceiver.on('imageArrival', () => {
      console.log("zmw -service-imageArrival")
      this.mReceiver.readNextImage((err, image) => {
        if (err || image === undefined) {
          return
        }
        image.getComponent(4, (errMsg, img) => {
          if (errMsg || img === undefined) {
            return
          }
          if (img.byteBuffer) {
            buffer = img.byteBuffer
          }
          if(this.isMediaUrl){
            this.savePictureMedia(buffer, image)
          }else{
            this.savePictureSand(buffer, image)
          }
        })
      })
      return buffer
    })
  }
如下:

 

根据 camera 的 getCameraManager 方法获取 CameraManager

通过 CameraManager 获取所有的相机数组,找到可用的相机,并获取相机的 cameraid

创建图片接收器并进行订阅,获取 receiver 的 surfaceId

通过 CameraManager 的 createCameraInput(cameraid) 创建相机输入流

通过 camera 的 createPreviewOutput(sufaceId) 创建相机预览输出流,这里 sufaceId 为 XComponent 的 id

通过 camera 的 createPhotoOutput(sufaceId) 创建相机拍照输出流,这里 sufaceId 为图片接收器的 surfaceId

会话管理:创建会话,并且配置会话的相机输入流,相机拍照输出流与相机预览流,提交配置,开始会话

至此,相机就能正常的显示出图像了。

用拍照方法拍摄照片:

 

  //拍摄照片
  async takePicture() {
    let photoSettings = {
      rotation: camera.ImageRotation.ROTATION_0,
      quality: camera.QualityLevel.QUALITY_LEVEL_LOW,
      mirror: false
    }
    await this.photoOutput.capture(photoSettings)
  }

 

调用相机的输出流的 capture 方法进行拍照操作,会触发图片接收器的监听,进行对字节流的写入操作,保存到沙箱或者媒体。

保存图片:分为沙箱路径与媒体路径。

 

//保存沙箱路径
  async savePictureSand(buffer: ArrayBuffer, img: image.Image) {
    let info = this.mediaUtil.getInfoFromType(mediaLibrary.MediaType.IMAGE)
    let dateTimeUtil = new DateTimeUtil()
    let name = `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`
    let displayName = `${info.prefix}${name}${info.suffix}`
    let sandboxDirPath = globalThis.context.filesDir;
    let path = sandboxDirPath + '/' + displayName
    this.imgUrl=path
    let fdSand = await fileio.open(path, 0o2 | 0o100, 0o666);
    await fileio.write(fdSand, buffer)
    await fileio.close(fdSand).then(()=>{
      this.desStr=""
    });
    await img.release()
  }
//保存媒体路径
  async savePictureMedia(buffer: ArrayBuffer, img: image.Image) {
    this.fileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE)
    this.imgUrl = this.fileAsset.uri
    let fd = await this.mediaUtil.getFdPath(this.fileAsset)
    await fileio.write(fd, buffer)
    await this.fileAsset.close(fd).then(()=>{
      this.desStr=""
    })
    await img.release()
  }
释放相机:
  //结束释放相机资源
  async releaseCamera() {
    if (this.captureSession) {
      await this.captureSession.stop().then(() => {
      })
    }
    if (this.cameraInput) {
      await this.cameraInput.release().then(() => {
      })
    }
    if (this.previewOutput) {
      await this.previewOutput.release().then(() => {
      })
    }
    if (this.photoOutput) {
      await this.photoOutput.release().then(() => {
      })
    }
    // 释放会话
    if (this.captureSession) {
      await this.captureSession.release((err) => {
        if (err) {
          console.error('zmw  Failed to release the CaptureSession instance ${err.message}');
          return;
        }
      });
    }
  }

 

在完成了相机的调用后,需要对相机的资源进行释放,否则再次调用的时候会一直被占用而导致黑屏。

总结

OpenHarmony 对于相机的官方使用文档不太清晰,有许多的坑,需要去趟。

在这个过程中我遇到的问题:

在相机的使用时,由于开发板上的相机获取到了两个,一个是外接 USB 的相机,一个应该是系统的,在获取相机的 id 的时候需要注意。

在保存相机拍照的图片的时候,保存到沙箱路径时显示不到页面上,需要在保存的路径前加上"file://"。

需要扩展研究的是进行相机的摄像操作,以及相机拍照与摄像的切换操作。

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分