鸿蒙上成功调用相机!

相机是一个系统的基础能力,能够通过调用相机进行拍照,在很多场景下都会使用到相机的调用,如人脸识别门禁,人脸解锁等操作。
本文主要介绍在 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://。
需要扩展研究的是进行相机的摄像操作,以及相机拍照与摄像的切换操作。


低压总断路器保护整定方法详解
Vishay电动汽车专用EMI抑制安规电容器
为实现管控物联网卡上网行为,SIMBOSS物联网卡支持“白名单功能
LTC2512-24的分布式读取示例
Mini LED技术哪家厂商实力领跑
鸿蒙上成功调用相机!
这8个因素会导致HPC存储成本增加
一场关于药物传递的对话,领先的科技成果
Imagination全新发布IMG CXM GPU,打造RISC-V伙伴的首选GPU
英特尔公布11代桌面酷睿
2.5D和3D封装的差异和应用有哪些呢?
Surface Laptop拆解:修理Surface Laptop最好的方法就是换一台
怎样利用AI构建数字世界
兆芯发布了同样x86架构、面向服务器与工作站的“开胜KH-4000系列”
区块链炒币会有哪一些风险
传感器和MEMS之间的互帮互助
对2023年半导体销量排名和市值的思考
基于Linux内存管理与Android内存分配机制
中易云物联网系统在城市中的应用
医疗应用中的Everspin MRAM存储器