相机是一个系统的基础能力,能够通过调用相机进行拍照,在很多场景下都会使用到相机的调用,如人脸识别门禁,人脸解锁等操作。
本文主要介绍在 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存储器