0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙上成功调用相机!

OpenHarmony技术社区 来源:OST开源开发者 2023-02-14 09:34 次阅读

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

本文主要介绍在 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 方法申请权限:

constPERMISSIONS: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))
})

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

②准备工作

导包:

importcamerafrom'@ohos.multimedia.camera';
importimagefrom'@ohos.multimedia.image';
importfileiofrom'@ohos.fileio';
importmediaLibraryfrom'@ohos.multimedia.mediaLibrary'
constCameraSize={
WIDTH:640,
HEIGHT:480
}
定义变量:
privatemXComponentController=newXComponentController()
privatecameraManager:camera.CameraManager=undefined
privatecameras:Array=undefined
privatecameraId:string=undefined
privatemReceiver:image.ImageReceiver=undefined
privatecameraInput:camera.CameraInput=undefined
privatepreviewOutput:camera.PreviewOutput=undefined
privatemSurfaceId:string=undefined
privatephotoOutput:camera.PhotoOutput=undefined
privatecaptureSession:camera.CaptureSession=undefined
privatemediaUtil:MediaUtil=undefined
@StatedesStr:string=""
privatefileAsset:mediaLibrary.FileAsset
privatesurfaceId:number
@StatephotoUriMedia:string=""
privatephotoFlag:boolean=true
@StateimgUrl:string=""
@StateisMediaUrl:boolean=true//判断保存路径为是沙箱路径或者媒体路径,默认媒体路径
aboutToAppear(){
this.mediaTest=mediaLibrary.getMediaLibrary(globalThis.context)
}
工具方法:
asynccreateAndGetUri(mediaType:number){
letinfo=this.getInfoFromType(mediaType)
letdateTimeUtil=newDateTimeUtil()
letname=`${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`
letdisplayName=`${info.prefix}${name}${info.suffix}`
letpublicPath=awaitthis.mediaTest.getPublicDirectory(info.directory)
letdataUri=awaitthis.mediaTest.createAsset(mediaType,displayName,publicPath)
returndataUri
}
asyncgetFdPath(fileAsset:any){
letfd=awaitfileAsset.open('Rw')
returnfd
}
getInfoFromType(mediaType:number){
letresult={
prefix:'',suffix:'',directory:0
}
switch(mediaType){
casemediaLibrary.MediaType.FILE:
result.prefix='FILE_'
result.suffix='.txt'
result.directory=mediaLibrary.DirectoryType.DIR_DOCUMENTS
break
casemediaLibrary.MediaType.IMAGE:
result.prefix='IMG_'
result.suffix='.jpg'
result.directory=mediaLibrary.DirectoryType.DIR_IMAGE
break
casemediaLibrary.MediaType.VIDEO:
result.prefix='VID_'
result.suffix='.mp4'
result.directory=mediaLibrary.DirectoryType.DIR_VIDEO
break
casemediaLibrary.MediaType.AUDIO:
result.prefix='AUD_'
result.suffix='.wav'
result.directory=mediaLibrary.DirectoryType.DIR_AUDIO
break
}
returnresult
}
工具类:
/**
*@file日期工具
*/
exportdefaultclassDateTimeUtil{

/**
*时分秒
*/
getTime(){
constDATETIME=newDate()
returnthis.concatTime(DATETIME.getHours(),DATETIME.getMinutes(),DATETIME.getSeconds())
}

/**
*年月日
*/
getDate(){
constDATETIME=newDate()
returnthis.concatDate(DATETIME.getFullYear(),DATETIME.getMonth()+1,DATETIME.getDate())
}

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

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

/**
*时分秒格式修饰
*@paramhours
*@paramminutes
*@paramseconds
*/
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() 中进行的。

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

this.captureSession=awaitcamera.createCaptureSession(globalThis.context)
awaitthis.captureSession.beginConfig()
awaitthis.captureSession.addInput(this.cameraInput)
awaitthis.captureSession.addOutput(this.previewOutput)
awaitthis.captureSession.addOutput(this.photoOutput)
awaitthis.captureSession.commitConfig()
awaitthis.captureSession.start().then(()=>{
console.log('zmw1--Promisereturnedtoindicatethesessionstartsuccess.');
})
}
//创建图片接收器并进行订阅
asyncphotoReceiver(){
this.mReceiver=image.createImageReceiver(CameraSize.WIDTH,CameraSize.HEIGHT,4,8)
letbuffer=newArrayBuffer(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)
}
})
})
returnbuffer
})
}
如下:

根据 camera 的 getCameraManager 方法获取 CameraManager

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

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

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

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

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

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

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

用拍照方法拍摄照片:

//拍摄照片
asynctakePicture(){
letphotoSettings={
rotation:camera.ImageRotation.ROTATION_0,
quality:camera.QualityLevel.QUALITY_LEVEL_LOW,
mirror:false
}
awaitthis.photoOutput.capture(photoSettings)
}

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

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

//保存沙箱路径
asyncsavePictureSand(buffer:ArrayBuffer,img:image.Image){
letinfo=this.mediaUtil.getInfoFromType(mediaLibrary.MediaType.IMAGE)
letdateTimeUtil=newDateTimeUtil()
letname=`${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`
letdisplayName=`${info.prefix}${name}${info.suffix}`
letsandboxDirPath=globalThis.context.filesDir;
letpath=sandboxDirPath+'/'+displayName
this.imgUrl=path
letfdSand=awaitfileio.open(path,0o2|0o100,0o666);
awaitfileio.write(fdSand,buffer)
awaitfileio.close(fdSand).then(()=>{
this.desStr=""
});
awaitimg.release()
}
//保存媒体路径
asyncsavePictureMedia(buffer:ArrayBuffer,img:image.Image){
this.fileAsset=awaitthis.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE)
this.imgUrl=this.fileAsset.uri
letfd=awaitthis.mediaUtil.getFdPath(this.fileAsset)
awaitfileio.write(fd,buffer)
awaitthis.fileAsset.close(fd).then(()=>{
this.desStr=""
})
awaitimg.release()
}
释放相机:
//结束释放相机资源
asyncreleaseCamera(){
if(this.captureSession){
awaitthis.captureSession.stop().then(()=>{
})
}
if(this.cameraInput){
awaitthis.cameraInput.release().then(()=>{
})
}
if(this.previewOutput){
awaitthis.previewOutput.release().then(()=>{
})
}
if(this.photoOutput){
awaitthis.photoOutput.release().then(()=>{
})
}
//释放会话
if(this.captureSession){
awaitthis.captureSession.release((err)=>{
if(err){
console.error('zmwFailedtoreleasetheCaptureSessioninstance${err.message}');
return;
}
});
}
}

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

总结

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

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

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

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

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

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

    关注

    4

    文章

    1253

    浏览量

    52444
  • Module
    +关注

    关注

    0

    文章

    58

    浏览量

    12810
  • SDK
    SDK
    +关注

    关注

    3

    文章

    966

    浏览量

    44687
  • 鸿蒙
    +关注

    关注

    55

    文章

    1629

    浏览量

    42119
  • OpenHarmony
    +关注

    关注

    23

    文章

    3284

    浏览量

    15159

原文标题:鸿蒙上成功调用相机!

文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    STM32上成功移植了emWim支持触摸屏,但是效果不好?

    我在STM32上成功移植了emWim,并成功支持了触摸屏,但是效果不好触摸屏的精度不高,大概有5mm左右的偏差而且感觉触摸屏的反应很慢点着屏幕一会儿,光标才移动,怎么解决这两个问题????
    发表于 04-30 06:36

    如何在FPGA板上成功运行VHDL程序?

    我无法在12/21/2017找到我的帖子,所以我决定使用这个帖子重新发帖。问题:尽管如下,我无法在FPGA板上成功运行VHDL程序:1.生成编程文件 - 成功完成2.位文件 - 成功完成3.
    发表于 08-08 10:00

    鸿蒙系统更新后新增相机多机位模式

    近日,华为更新了鸿蒙系统HarmonyOS2.0.0.166,这个系统版本带来了一项新功能,就是相机的多机位模式,也叫作分布式相机。这个功能的特点是只要有多台华为手机或平板,在直播、视频聊天或拍摄
    发表于 08-18 15:04

    在STM32F103ZE的一款开发板上成功移植uCOSIII

    在STM32F103ZE的一款开发板上成功移植uCOSIII,并建立简单的LED测试工程使用Keil uVision4软件,3.5固件库及v3.02.00版本的uCOSIII
    发表于 08-23 08:28

    有在i.MX6上成功移植好阿里的mqtt驱动的吗

    有在i.MX6上成功移植好阿里的mqtt驱动的么?
    发表于 01-07 07:09

    如何让U-Boot在RK3399这块板子上成功的运行起来呢

    U-Boot加载方式是怎样的?如何让U-Boot在RK3399这块板子上成功的运行起来呢?
    发表于 03-07 06:03

    请问鸿蒙hap包是否支持插件化开发?

    如题,安卓上可以使用dexclassloader机制动态加载其他apk作为插件使用,鸿蒙上用类似的能力吗?有什么解决方案呢?
    发表于 06-16 11:34

    鸿蒙上能安装docker吗?

    android是不支持安装docker的,想了解下鸿蒙是否支持安装docker
    发表于 03-21 16:32

    如何使用Android调用相机并将照片存储到SD卡上

    Android中实现拍照有两种方法,一种是调用系统自带的相机,还有一种是自己用Camera类和其他相关类实现相机功能,这种方法定制度比较高,需要的朋友可以了解下
    发表于 03-18 08:00 0次下载

    鸿蒙上使用Python进行物联网编程

    在上一篇帖子《使用 Python 开发鸿蒙设备程序(1-GPIO 外设控制)》中,已经成功的使用 Python 对 GPIO 上的外设进行了控制。 这其实不是什么大不了的事,从功能的角度也着实不值得
    的头像 发表于 09-28 09:55 3727次阅读
    在<b class='flag-5'>鸿蒙上</b>使用Python进行物联网编程

    鸿蒙上安装按钮实现下载、暂停、取消、显示等操作

    今天给大家分享在鸿蒙上一个按钮实现下载、暂停、取消、显示下载进度操作。
    的头像 发表于 01-04 14:32 1838次阅读

    鸿蒙上实现“数字华容道”小游戏

    本篇文章教大家如何在鸿蒙上实现“数字华容道”小游戏。
    的头像 发表于 12-26 09:52 870次阅读

    鸿蒙上点亮LED灯

    上一篇我们成功的在鸿蒙开发板上输出了 Hello World!这一篇将带大家点亮 LED 灯。
    的头像 发表于 01-16 10:28 1724次阅读

    鸿蒙上开发“小蜜蜂”游戏

    小时候我们有个熟悉的游戏叫小蜜蜂。本文教大家在鸿蒙上学做这个小蜜蜂游戏。
    的头像 发表于 04-03 11:27 1247次阅读

    华普永明低碳储能路灯在德国法兰克福展上成功发布

    德国时间3月4日14:00,杭州华普永明光电股份有限公司(以下简称:华普永明)低碳储能路灯在德国法兰克福展上成功发布。
    的头像 发表于 03-06 10:04 236次阅读
    华普永明低碳储能路灯在德国法兰克福展<b class='flag-5'>上成功</b>发布