RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

OpenHarmony上實現(xiàn)分布式相機

OpenHarmony技術社區(qū) ? 來源:OST開源開發(fā)者 ? 2023-02-20 10:41 ? 次閱讀

最近陸續(xù)看到各社區(qū)上有關 OpenHarmony 媒體相機的使用開發(fā)文檔,相機對于富設備來說必不可少,日常中我們經(jīng)常使用相機完成拍照、人臉驗證等。

OpenHarmony 系統(tǒng)一個重要的能力就是分布式,對于分布式相機我也倍感興趣,之前看到官方對分布式相機的一些說明,這里簡單介紹下。

有興趣可以查看官方文檔:分布式相機部件

https://gitee.com/openharmony/distributedhardware_distributed_camera

分布式框架圖

eeb726ac-b069-11ed-bfe3-dac502259ad0.png

分布式相機框架(Distributed Hardware)分為主控端和被控端。假設:設備 B 擁有本地相機設備,分布式組網(wǎng)中的設備 A 可以分布式調用設備 B 的相機設備。

這種場景下,設備 A 是主控端,設備 B 是被控端,兩個設備通過軟總線進行交互。

VirtualCameraHAL:作為硬件適配層(HAL)的一部分,負責和分布式相機框架中的主控端交互,將主控端 CameraFramwork 下發(fā)的指令傳輸給分布式相機框架的 SourceMgr 處理。

SourceMgr:通過軟總線將控制信息傳遞給被控端的 CameraClient。

CameraClient:直接通過調用被控端 CameraFramwork 的接口來完成對設備 B 相機的控制。

最后,從設備 B 反饋的預覽圖像數(shù)據(jù)會通過分布式相機框架的 ChannelSink 回傳到設備 A 的 HAL 層,進而反饋給應用。通過這種方式,設備 A 的應用就可以像使用本地設備一樣使用設備 B 的相機。

相關名詞介紹:

主控端(source):控制端,通過調用分布式相機能力,使用被控端的攝像頭進行預覽、拍照、錄像等功能。

被控端(sink):被控制端,通過分布式相機接收主控端的命令,使用本地攝像頭為主控端提供圖像數(shù)據(jù)。

現(xiàn)在我們要實現(xiàn)分布式相機,在主控端調用被控端相機,實現(xiàn)遠程操作相機,開發(fā)此應用的具體需求:

支持本地相機的預覽、拍照、保存相片、相片縮略圖、快速查看相片、切換攝像頭(如果一臺設備上存在多個攝像頭時)。

同一網(wǎng)絡下,支持分布式 pin 碼認證,遠程連接。

自由切換本地相機和遠程相機。

UI 草圖

eed7ec5c-b069-11ed-bfe3-dac502259ad0.png

從草圖上看,我們簡單的明應用 UI 布局的整體內容:

頂部右上角有個"切換設備"的按鈕,點擊彈窗顯示設備列表,可以實現(xiàn)設備認證與設備切換功能。

中間使用 XComponent 組件實現(xiàn)的相機預覽區(qū)域。

底部分為如下三個部分。

具體如下:

相機縮略圖:顯示當前設備媒體庫中最新的圖片,點擊相機縮略圖按鈕可以查看相關的圖片。

拍照:點擊拍照按鈕,將相機當前幀保存到本地媒體庫中。

切換攝像頭:如果一臺設備有多個攝像頭時,例如相機有前后置攝像頭,點擊切換后會將當前預覽的頁面切換到另外一個攝像頭的圖像。

實現(xiàn)效果

eef80c8a-b069-11ed-bfe3-dac502259ad0.jpg

ef1765b2-b069-11ed-bfe3-dac502259ad0.jpg

開發(fā)環(huán)境

如下:

系統(tǒng):OpenHarmony 3.2 beta4/OpenHarmony 3.2 beta5

設備:DAYU200

IDE:DevEco Studio 3.0 Release ,Build Version: 3.0.0.993, built on September 4, 2022

SDK:Full_3.2.9.2

開發(fā)模式:Stage

開發(fā)語言:ets

開發(fā)實踐

本篇主要在應用層的角度實現(xiàn)分布式相機,實現(xiàn)遠程相機與實現(xiàn)本地相機的流程相同,只是使用的相機對象不同,所以我們先完成本地相機的開發(fā),再通過參數(shù)修改相機對象來啟動遠程相機。

①創(chuàng)建項目

ef388f4e-b069-11ed-bfe3-dac502259ad0.png

②權限聲明

(1)module.json 配置權限

說明:在 module 模塊下添加權限聲明,權限的詳細說明

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md
"requestPermissions":[
{
"name":"ohos.permission.REQUIRE_FORM"
},
{
"name":"ohos.permission.MEDIA_LOCATION"
},
{
"name":"ohos.permission.MODIFY_AUDIO_SETTINGS"
},
{
"name":"ohos.permission.READ_MEDIA"
},
{
"name":"ohos.permission.WRITE_MEDIA"
},
{
"name":"ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"
},
{
"name":"ohos.permission.CAMERA"
},
{
"name":"ohos.permission.MICROPHONE"
},
{
"name":"ohos.permission.DISTRIBUTED_DATASYNC"
}
]

(2)在 index.ets 頁面的初始化 aboutToAppear() 申請權限

代碼如下:

letpermissionList:Array=[
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA",
"ohos.permission.WRITE_MEDIA",
"ohos.permission.CAMERA",
"ohos.permission.MICROPHONE",
"ohos.permission.DISTRIBUTED_DATASYNC"
]


asyncaboutToAppear(){
console.info(`${TAG}aboutToAppear`)
globalThis.cameraAbilityContext.requestPermissionsFromUser(permissionList).then(async(data)=>{
console.info(`${TAG}datapermissions:${JSON.stringify(data.permissions)}`)
console.info(`${TAG}dataauthResult:${JSON.stringify(data.authResults)}`)
//判斷授權是否完成
letresultCount:number=0
for(letresultofdata.authResults){
if(result===0){
resultCount+=1
}
}
if(resultCount===permissionList.length){
this.isPermissions=true
}
awaitthis.initCamera()
//獲取縮略圖
this.mCameraService.getThumbnail(this.functionBackImpl)
})
}
這里有個獲取縮略圖的功能,主要是獲取媒體庫中根據(jù)時間排序,獲取最新拍照的圖片作為當前需要顯示的縮略圖,實現(xiàn)此方法在后面說 CameraService 類的時候進行詳細介紹。 注意:如果首次啟動應用,在授權完成后需要加載相機,則建議授權放在啟動頁完成,或者在調用相機頁面之前添加一個過渡頁面,主要用于完成權限申請和啟動相機的入口,否則首次完成授權后無法顯示相機預覽,需要退出應用再重新進入才可以正常預覽,這里先簡單說明下,文章后續(xù)會在問題環(huán)節(jié)詳細介紹。

③UI 布局

說明:UI 如前面截圖所示,實現(xiàn)整體頁面的布局。 頁面中主要使用到 XComponent 組件,用于 EGL/OpenGLES 和媒體數(shù)據(jù)寫入,并顯示在 XComponent 組件。

參看:XComponent 詳細介紹

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-xcomponent.md
onLoad():XComponent 插件加載完成時的回調,在插件完成時可以獲取**ID并初始化相機。

XComponentController:XComponent 組件控制器,可以綁定至 XComponent 組件,通過 getXComponent/**aceId() 獲取 XComponent 對應的/**aceID。

代碼如下:

@State@Watch('selectedIndexChange')selectIndex:number=0
//設備列表
@Statedevices:Array=[]
//設備選擇彈窗
privatedialogController:CustomDialogController=newCustomDialogController({
builder:DeviceDialog({
deviceList:$devices,
selectIndex:$selectIndex,
}),
autoCancel:true,
alignment:DialogAlignment.Center
})
@StatecurPictureWidth:number=70
@StatecurPictureHeight:number=70
@StatecurThumbnailWidth:number=70
@StatecurThumbnailHeight:number=70
@StatecurSwitchAngle:number=0
@StateId:string=''
@Statethumbnail:image.PixelMap=undefined
@StateresourceUri:string=''
@StateisSwitchDeviceing:boolean=false//是否正在切換相機
privateisInitCamera:boolean=false//是否已初始化相機
privateisPermissions:boolean=false//是否完成授權
privatecomponentController:XComponentController=newXComponentController()
privatemCurDeviceID:string=Constant.LOCAL_DEVICE_ID//默認本地相機
privatemCurCameraIndex:number=0//默認相機列表中首個相機
privatemCameraService=CameraService.getInstance()

build(){
Stack({alignContent:Alignment.Center}){
Column(){
Row({space:20}){
Image($r('app.media.ic_camera_public_setting'))
.width(40)
.height(40)
.margin({
right:20
})
.objectFit(ImageFit.Contain)
.onClick(()=>{
console.info(`${TAG}clickdistributedauth.`)
this.showDialog()
})
}
.width('100%')
.height('5%')
.margin({
top:20,
bottom:20
})
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.End)

Column(){
XComponent({
id:'componentId',
type:'xxxxace',
controller:this.componentController
}).onLoad(async()=>{
console.info(`${TAG}XComponentonLoadiscalled`)
this.componentController.setXComponentxxxxaceSize({
xxxxWidth:Resolution.DEFAULT_WIDTH,
xxxxaceHeight:Resolution.DEFAULT_HEIGHT
})
this.id=this.componentController.getXComponentxxxxaceId()
console.info(`${TAG}id:${this.id}`)
awaitthis.initCamera()
}).height('100%')
.width('100%')
}
.width('100%')
.height('75%')
.margin({
bottom:20
})

Row(){
Column(){
Image(this.thumbnail!=undefined?this.thumbnail:$r('app.media.screen_pic'))
.width(this.curThumbnailWidth)
.height(this.curThumbnailHeight)
.objectFit(ImageFit.Cover)
.onClick(async()=>{
console.info(`${TAG}launchbundlecom.ohos.photos`)
awaitglobalThis.cameraAbilityContext.startAbility({
parameters:{uri:'photodetail'},
bundleName:'com.ohos.photos',
abilityName:'com.ohos.photos.MainAbility'
})
animateTo({
duration:200,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse,
onFinish:()=>{
animateTo({
duration:100,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse
},()=>{
this.curThumbnailWidth=70
this.curThumbnailHeight=70
})
}
},()=>{
this.curThumbnailWidth=60
this.curThumbnailHeight=60
})
})
}
.width('33%')
.alignItems(HorizontalAlign.Start)

Column(){
Image($r('app.media.icon_picture'))
.width(this.curPictureWidth)
.height(this.curPictureHeight)
.objectFit(ImageFit.Cover)
.alignRules({
center:{
align:VerticalAlign.Center,
anchor:'center'
}
})
.onClick(()=>{
this.takePicture()
animateTo({
duration:200,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse,
onFinish:()=>{
animateTo({
duration:100,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse
},()=>{
this.curPictureWidth=70
this.curPictureHeight=70
})
}
},()=>{
this.curPictureWidth=60
this.curPictureHeight=60
})
})
}
.width('33%')

Column(){
Image($r('app.media.icon_switch'))
.width(50)
.height(50)
.objectFit(ImageFit.Cover)
.rotate({
x:0,
y:1,
z:0,
angle:this.curSwitchAngle
})
.onClick(()=>{
this.switchCamera()
animateTo({
duration:500,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse,
onFinish:()=>{
animateTo({
duration:500,
curve:Curve.EaseInOut,
delay:0,
iterations:1,
playMode:PlayMode.Reverse
},()=>{
this.curSwitchAngle=0
})
}
},()=>{
this.curSwitchAngle=180
})
})
}
.width('33%')
.alignItems(HorizontalAlign.End)

}
.width('100%')
.height('10%')
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.padding({
left:40,
right:40
})
}
.height('100%')
.width('100%')
.padding(10)

if(this.isSwitchDeviceing){
Column(){
Image($r('app.media.load_switch_camera'))
.width(400)
.height(306)
.objectFit(ImageFit.Fill)
Text($r('app.string.switch_camera'))
.width('100%')
.height(50)
.fontSize(16)
.fontColor(Color.White)
.align(Alignment.Center)
}
.width('100%')
.height('100%')
.backgroundColor(Color.Black)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(()=>{

})
}
}
.height('100%')
.backgroundColor(Color.Black)
}
(1)啟動系統(tǒng)相冊

說明:用戶點擊圖片縮略圖時需要啟動圖片查看,這里直接打開系統(tǒng)相冊,查看相關的圖片。

代碼如下:

awaitglobalThis.cameraAbilityContext.startAbility({
parameters:{uri:'photodetail'},
bundleName:'com.ohos.photos',
abilityName:'com.ohos.photos.MainAbility'
})

④相機服務 CameraService.ts

(1)CameraService 單例模式,用于提供操作相機相關的業(yè)務

代碼如下:

privatestaticinstance:CameraService=null


privateconstructor(){
this.mThumbnailGetter=newThumbnailGetter()
}
/**
*單例
*/
publicstaticgetInstance():CameraService{
if(this.instance===null){
this.instance=newCameraService()
}
returnthis.instance
}
(2)初始化相機 說明:通過媒體相機提供的 API(@ohos.multimedia.camera)getCameraManager() 獲取相機管理對象 CameraManager,并注冊相機狀態(tài)變化監(jiān)聽器,實時更新相機狀態(tài)。

同時通過 CameraManager…getSupportedCameras() 獲取前期支持的相機設備集合,這里的相機設備包括當前設備上安裝的相機設備和遠程設備上的相機設備。

代碼如下:

/**
*初始化
*/
publicasyncinitCamera():Promise{
console.info(`${TAG}initCamera`)
if(this.mCameraManager===null){
this.mCameraManager=awaitcamera.getCameraManager(globalThis.cameraAbilityContext)
//注冊監(jiān)聽相機狀態(tài)變化
this.mCameraManager.on('cameraStatus',(cameraStatusInfo)=>{
console.info(`${TAG}cameraStatus:${JSON.stringify(cameraStatusInfo)}`)
})
//獲取相機列表
letcameras:Array=awaitthis.mCameraManager.getSupportedCameras()
if(cameras){
this.mCameraCount=cameras.length
console.info(`${TAG}mCameraCount:${this.mCameraCount}`)
if(this.mCameraCount===0){
returnthis.mCameraCount
}
for(leti=0;i0){
console.info(`${TAG}displayCameraDevicehasmCameraMap`)
//判斷相機列表中是否已經(jīng)存在此相機
letisExist:boolean=false
for(letitemofthis.mCameraMap.get(key)){
if(item.cameraId===cameraDevice.cameraId){
isExist=true
break
}
}
//添加列表中沒有的相機
if(!isExist){
console.info(`${TAG}displayCameraDevicenotexist,push${cameraDevice.cameraId}`)
this.mCameraMap.get(key).push(cameraDevice)
}else{
console.info(`${TAG}displayCameraDevicehasexisted`)
}
}else{
letcameras:Array=[]
console.info(`${TAG}displayCameraDevicepush${cameraDevice.cameraId}`)
cameras.push(cameraDevice)
this.mCameraMap.set(key,cameras)
}
}

(3)創(chuàng)建相機輸入流

說明:CameraManager.createCameraInput() 可以創(chuàng)建相機輸出流 CameraInput 實例,CameraInput 是在 CaptureSession 會話中使用的相機信息,支持打開相機、關閉相機等能力。 代碼如下:

/**
*創(chuàng)建相機輸入流
*@paramcameraIndex相機下標
*@paramdeviceId設備ID
*/
publicasynccreateCameraInput(cameraIndex?:number,deviceId?:string){
console.info(`${TAG}createCameraInput`)
if(this.mCameraManager===null){
console.error(`${TAG}mCameraManagerisnull`)
return
}
if(this.mCameraCount<=?0)?{
????????????console.error(`${TAG}?not?camera?device`)
????????????return
????????}
????????if?(this.mCameraInput)?{
????????????this.mCameraInput.release()
????????}
????????if?(deviceId?&&?this.mCameraMap.has(deviceId))?{
????????????if?(cameraIndex?{
console.error(`${TAG}CameraInputerror:${JSON.stringify(error)}`)
})
awaitthis.mCameraInput.open()
}catch(err){
if(err){
console.error(`${TAG}failedtocreateCameraInput`)
}
}
}
(4)相機預覽輸出流

說明:CameraManager.createPreviewOutput() 創(chuàng)建預覽輸出流對象 PreviewOutput,PreviewOutput 繼承 CameraOutput,在 CaptureSession 會話中使用的輸出信息,支持開始輸出預覽流、停止預覽輸出流、釋放預覽輸出流等能力。

/**
*創(chuàng)建相機預覽輸出流
*/
publicasynccreatePreviewOutput(Id:string,callback:PreviewCallBack){
console.info(`${TAG}createPreviewOutput`)
if(this.mCameraManager===null){
console.error(`${TAG}createPreviewOutputmCameraManagerisnull`)
return
}
this.Id=Id
console.info(`${TAG}Id${Id}}`)
//獲取當前相機設備支持的輸出能力
letcameraOutputCap=awaitthis.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
if(!cameraOutputCap){
console.error(`${TAG}createPreviewOutputgetSupportedOutputCapabilityerror}`)
return
}
console.info(`${TAG}createPreviewOutputcameraOutputCap${JSON.stringify(cameraOutputCap)}`)
letpreviewProfilesArray=cameraOutputCap.previewProfiles
letpreviewProfiles:camera.Profile
if(!previewProfilesArray||previewProfilesArray.length<=?0)?{
????????????console.error(`${TAG}?createPreviewOutput?previewProfilesArray?error}`)
????????????previewProfiles?=?{
????????????????format:?1,
????????????????size:?{
????????????????????width:?640,
????????????????????height:?480
????????????????}
????????????}
????????}?else?{
????????????console.info(`${TAG}?createPreviewOutput?previewProfile?length?${previewProfilesArray.length}`)
????????????previewProfiles?=?previewProfilesArray[0]
????????}
????????console.info(`${TAG}?createPreviewOutput?previewProfile[0]?${JSON.stringify(previewProfiles)}`)
????????try?{
????????????this.mPreviewOutput?=?await?this.mCameraManager.createPreviewOutput(previewProfiles,?id
)
????????????console.info(`${TAG}?createPreviewOutput?success`)
????????????//?監(jiān)聽預覽幀開始
????????????this.mPreviewOutput.on('frameStart',?()?=>{
console.info(`${TAG}createPreviewOutputcameraframeStart`)
callback.onFrameStart()
})
this.mPreviewOutput.on('frameEnd',()=>{
console.info(`${TAG}createPreviewOutputcameraframeEnd`)
callback.onFrameEnd()
})
this.mPreviewOutput.on('error',(error)=>{
console.error(`${TAG}createPreviewOutputerror:${error}`)
})
}catch(err){
console.error(`${TAG}failedtocreatePreviewOutput${err}`)
}
}

(5)拍照輸出流

說明:CameraManager.createPhotoOutput() 可以創(chuàng)建拍照輸出對象 PhotoOutput,PhotoOutput 繼承 CameraOutput 在拍照會話中使用的輸出信息,支持拍照、判斷是否支持鏡像拍照、釋放資源、監(jiān)聽拍照開始、拍照幀輸出捕獲、拍照結束等能力。

代碼如下:

/**
*創(chuàng)建拍照輸出流
*/
publicasynccreatePhotoOutput(functionCallback:FunctionCallBack){
console.info(`${TAG}createPhotoOutput`)
if(!this.mCameraManager){
console.error(`${TAG}createPhotoOutputmCameraManagerisnull`)
return
}
//通過寬、高、圖片格式、容量創(chuàng)建ImageReceiver實例
constreceiver:image.ImageReceiver=image.createImageReceiver(Resolution.DEFAULT_WIDTH,Resolution.DEFAULT_HEIGHT,image.ImageFormat.JPEG,8)
constimageId:string=awaitreceiver.getReceivingxxxxaceId()
console.info(`${TAG}createPhotoOutputimageId:${imageId}`)
letcameraOutputCap=awaitthis.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
console.info(`${TAG}createPhotoOutputcameraOutputCap${cameraOutputCap}`)
if(!cameraOutputCap){
console.error(`${TAG}createPhotoOutputgetSupportedOutputCapabilityerror}`)
return
}
letphotoProfilesArray=cameraOutputCap.photoProfiles
letphotoProfiles:camera.Profile
if(!photoProfilesArray||photoProfilesArray.length<=?0)?{
????????????//?使用自定義的配置
????????????photoProfiles?=?{
????????????????format:?2000,
????????????????size:?{
????????????????????width:?1280,
????????????????????height:?960
????????????????}
????????????}
????????}?else?{
????????????console.info(`${TAG}?createPhotoOutput?photoProfile?length?${photoProfilesArray.length}`)
????????????photoProfiles?=?photoProfilesArray[0]
????????}
????????console.info(`${TAG}?createPhotoOutput?photoProfile?${JSON.stringify(photoProfiles)}`)
????????try?{
????????????this.mPhotoOutput?=?await?this.mCameraManager.createPhotoOutput(photoProfiles,?id)
????????????console.info(`${TAG}?createPhotoOutput?mPhotoOutput?success`)
????????????//?保存圖片
????????????this.mSaveCameraAsset.saveImage(receiver,?Resolution.THUMBNAIL_WIDTH,?Resolution.THUMBNAIL_HEIGHT,?this.mThumbnailGetter,?functionCallback)
????????}?catch?(err)?{
????????????console.error(`${TAG}?createPhotoOutput?failed?to?createPhotoOutput?${err}`)
????????}
????}

this.mSaveCameraAsset.saveImage(),這里將保存拍照的圖片進行封裝—SaveCameraAsset.ts,后面會單獨介紹。

(6)會話管理

說明:通過 CameraManager.createCaptureSession() 可以創(chuàng)建相機的會話類,保存相機運行所需要的所有資源 CameraInput、CameraOutput,并向相機設備申請完成相機拍照或錄像功能。 CaptureSession 對象提供了開始配置會話、添加 CameraInput 到會話、添加 CameraOutput 到會話、提交配置信息、開始會話、停止會話、釋放等能力。

代碼如下:

publicasynccreateSession(id:string){
console.info(`${TAG}createSession`)
console.info(`${TAG}createSessionid${id}}`)
this.id=id

this.mCaptureSession=awaitthis.mCameraManager.createCaptureSession()
console.info(`${TAG}createSessionmCaptureSession${this.mCaptureSession}`)

this.mCaptureSession.on('error',(error)=>{
console.error(`${TAG}CaptureSessionerror${JSON.stringify(error)}`)
})
try{
awaitthis.mCaptureSession?.beginConfig()
awaitthis.mCaptureSession?.addInput(this.mCameraInput)
if(this.mPhotoOutput!=null){
console.info(`${TAG}createSessionaddOutputPhotoOutput`)
awaitthis.mCaptureSession?.addOutput(this.mPhotoOutput)
}
awaitthis.mCaptureSession?.addOutput(this.mPreviewOutput)
}catch(err){
if(err){
console.error(`${TAG}createSessionbeginConfigfailerr:${JSON.stringify(err)}`)
}
}
try{
awaitthis.mCaptureSession?.commitConfig()
}catch(err){
if(err){
console.error(`${TAG}createSessioncommitConfigfailerr:${JSON.stringify(err)}`)
}
}
try{
awaitthis.mCaptureSession?.start()
}catch(err){
if(err){
console.error(`${TAG}createSessionstartfailerr:${JSON.stringify(err)}`)
}
}
console.info(`${TAG}createSessionmCaptureSessionstart`)
}
⑤拍照 說明:通過 PhotoOutput.capture() 可以實現(xiàn)拍照功能。 代碼如下:
/**
*拍照
*/
publicasynctakePicture(){
console.info(`${TAG}takePicture`)
if(!this.mCaptureSession){
console.info(`${TAG}takePicturesessionisrelease`)
return
}
if(!this.mPhotoOutput){
console.info(`${TAG}takePicturemPhotoOutputisnull`)
return
}
try{
constphotoCaptureSetting:camera.PhotoCaptureSetting={
quality:camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation:camera.ImageRotation.ROTATION_0,
location:{
latitude:0,
longitude:0,
altitude:0
},
mirror:false
}
awaitthis.mPhotoOutput.capture(photoCaptureSetting)
}catch(err){
console.error(`${TAG}takePictureerr:${JSON.stringify(err)}`)
}
}
⑥保存圖片 SaveCameraAsset

說明:SaveCameraAsset.ts 主要用于保存拍攝的圖片,即是調用拍照操作后,會觸發(fā)圖片接收監(jiān)聽器,在將圖片的字節(jié)流進行寫入本地文件操作。

代碼如下:

/**
*保存相機拍照的資源
*/
importimagefrom'@ohos.multimedia.image'
importmediaLibraryfrom'@ohos.multimedia.mediaLibrary'
import{FunctionCallBack}from'../model/CameraService'
importDateTimeUtilfrom'../utils/DateTimeUtil'
importfileIOfrom'@ohos.file.fs';
importThumbnailGetterfrom'../model/ThumbnailGetter'
letphotoUri:string//圖片地址
constTAG:string='SaveCameraAsset'
exportdefaultclassSaveCameraAsset{
privatelastSaveTime:string=''
privatesaveIndex:number=0
constructor(){
}
publicgetPhotoUri():string{
console.info(`${TAG}getPhotoUri=${photoUri}`)
returnphotoUri
}
/**
*保存拍照圖片
*@paramimageReceiver圖像接收對象
*@paramthumbWidth縮略圖寬度
*@paramthumbHeight縮略圖高度
*@paramcallback回調
*/
publicsaveImage(imageReceiver:image.ImageReceiver,thumbWidth:number,thumbHeight:number,thumbnailGetter:ThumbnailGetter,callback:FunctionCallBack){
console.info(`${TAG}saveImage`)
constmDateTimeUtil=newDateTimeUtil()
constfileKeyObj=mediaLibrary.FileKey
constmediaType=mediaLibrary.MediaType.IMAGE
letbuffer=newArrayBuffer(4096)
constmedia=mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext)//獲取媒體庫實例
//接收圖片回調
imageReceiver.on('imageArrival',async()=>{
console.info(`${TAG}saveImageImageArrival`)
//使用當前時間命名
constdisplayName=this.checkName(`IMG_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`)+'.jpg'
console.info(`${TAG}displayName=${displayName}}`)
imageReceiver.readNextImage((err,imageObj:image.Image)=>{
if(imageObj===undefined){
console.error(`${TAG}saveImagefailedtogetvalidimageerror=${err}`)
return
}
//根據(jù)圖像的組件類型從圖像中獲取組件緩存4-JPEG類型
imageObj.getComponent(image.ComponentType.JPEG,async(errMsg,imgComponent)=>{
if(imgComponent===undefined){
console.error(`${TAG}getComponentfailedtogetvalidbuffererror=${errMsg}`)
return
}
if(imgComponent.byteBuffer){
console.info(`${TAG}getComponentimgComponent.byteBuffer${imgComponent.byteBuffer}`)
buffer=imgComponent.byteBuffer
}else{
console.info(`${TAG}getComponentimgComponent.byteBufferisundefined`)
}
awaitimageObj.release()
})
})
letpublicPath:string=awaitmedia.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)
console.info(`${TAG}saveImagepublicPath=${publicPath}`)
//創(chuàng)建媒體資源返回提供封裝文件屬性
constdataUri:mediaLibrary.FileAsset=awaitmedia.createAsset(mediaType,displayName,publicPath)
//媒體文件資源創(chuàng)建成功,將拍照的數(shù)據(jù)寫入到媒體資源
if(dataUri!==undefined){
photoUri=dataUri.uri
console.info(`${TAG}saveImagephotoUri:${photoUri}`)
constargs=dataUri.id.toString()
console.info(`${TAG}saveImageid:${args}`)
//通過ID查找媒體資源
constfetchOptions:mediaLibrary.MediaFetchOptions={
selections:`${fileKeyObj.ID}=?`,
selectionArgs:[args]
}
console.info(`${TAG}saveImagefetchOptions:${JSON.stringify(fetchOptions)}`)
constfetchFileResult=awaitmedia.getFileAssets(fetchOptions)
constfileAsset=awaitfetchFileResult.getAllObject()//獲取文件檢索結果中的所有文件資
if(fileAsset!=undefined){
fileAsset.forEach((dataInfo)=>{
dataInfo.open('Rw').then((fd)=>{//RW是讀寫方式打開文件獲取fd
console.info(`${TAG}saveImagedataInfo.opencalled.fd:${fd}`)
//將緩存圖片流寫入資源
fileIO.write(fd,buffer).then(()=>{
console.info(`${TAG}saveImagefileIO.writecalled`)
dataInfo.close(fd).then(()=>{
console.info(`${TAG}saveImagedataInfo.closecalled`)
//獲取資源縮略圖
thumbnailGetter.getThumbnailInfo(thumbWidth,thumbHeight,photoUri).then((thumbnail=>{
if(thumbnail===undefined){
console.error(`${TAG}saveImagegetThumbnailInfoundefined`)
callback.onCaptureFailure()
}else{
console.info(`${TAG}photoUri:${photoUri}PixelBytesNumber:${thumbnail.getPixelBytesNumber()}`)
callback.onCaptureSuccess(thumbnail,photoUri)
}
}))
}).catch(error=>{
console.error(`${TAG}saveImagecloseiserror${JSON.stringify(error)}`)
})
})
})
})
}else{
console.error(`${TAG}saveImagefileAsset:isnull`)
}
}else{
console.error(`${TAG}saveImagephotoUriisnull`)
}
})
}
/**
*檢測文件名稱
*@paramfileName文件名稱
*如果同一時間有多張圖片,則使用時間_index命名
*/
privatecheckName(fileName:string):string{
if(this.lastSaveTime==fileName){
this.saveIndex++
return`${fileName}_${this.saveIndex}`
}
this.lastSaveTime=fileName
this.saveIndex=0
returnfileName
}
}

⑦獲取縮略圖

說明:主要通過獲取當前媒體庫中根據(jù)時間排序,獲取最新的圖片并縮放圖片大小后返回。

代碼如下:

/**
*獲取縮略圖
*@paramcallback
*/
publicgetThumbnail(callback:FunctionCallBack){
console.info(`${TAG}getThumbnail`)
this.mThumbnailGetter.getThumbnailInfo(Resolution.THUMBNAIL_WIDTH,Resolution.THUMBNAIL_HEIGHT).then((thumbnail)=>{
console.info(`${TAG}getThumbnailthumbnail=${thumbnail}`)
callback.thumbnail(thumbnail)
})
}
(1)ThumbnailGetter.ts 說明:實現(xiàn)獲取縮略圖的對象。 代碼如下:
/**
*縮略圖處理器
*/
importmediaLibraryfrom'@ohos.multimedia.mediaLibrary';
importimagefrom'@ohos.multimedia.image';
constTAG:string='ThumbnailGetter'
exportdefaultclassThumbnailGetter{
publicasyncgetThumbnailInfo(width:number,height:number,uri?:string):Promise{
console.info(`${TAG}getThumbnailInfo`)
//文件關鍵信息
constfileKeyObj=mediaLibrary.FileKey
//獲取媒體資源公共路徑
constmedia:mediaLibrary.MediaLibrary=mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext)
letpublicPath:string=awaitmedia.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)
console.info(`${TAG}publicPath=${publicPath}`)
letfetchOptions:mediaLibrary.MediaFetchOptions={
selections:`${fileKeyObj.RELATIVE_PATH}=?`,//檢索條件RELATIVE_PATH-相對公共目錄的路徑
selectionArgs:[publicPath]//檢索條件值
}
if(uri){
fetchOptions.uri=uri//文件的URI
}else{
fetchOptions.order=fileKeyObj.DATE_ADDED+'DESC'
}
console.info(`${TAG}getThumbnailInfofetchOptions:${JSON.stringify(fetchOptions)}}`)
constfetchFileResult=awaitmedia.getFileAssets(fetchOptions)//文件檢索結果集
constcount=fetchFileResult.getCount()
console.info(`${TAG}count=${count}`)
if(count==0){
returnundefined
}
//獲取結果集合中的最后一張圖片
constlastFileAsset=awaitfetchFileResult.getFirstObject()
if(lastFileAsset==null){
console.error(`${TAG}getThumbnailInfolastFileAssetisnull`)
returnundefined
}
constthumbnailPixelMap=lastFileAsset.getThumbnail({
width:width,
height:height
})
console.info(`${TAG}getThumbnailInfothumbnailPixelMap${JSON.stringify(thumbnailPixelMap)}}`)
returnthumbnailPixelMap
}
}
⑧釋放資源 說明:在相機設備切換時,如前后置攝像頭切換或者不同設備之間的攝像頭切換時都需要先釋放資源,再重新創(chuàng)建新的相機會話才可以正常運行,釋放的資源包括:釋放相機輸入流、預覽輸出流、拍照輸出流、會話。 代碼如下:
/**
*釋放相機輸入流
*/
publicasyncreleaseCameraInput(){
console.info(`${TAG}releaseCameraInput`)
if(this.mCameraInput){
try{
awaitthis.mCameraInput.release()
}catch(err){
console.error(`${TAG}releaseCameraInput${err}}`)
}
this.mCameraInput=null
}
}



/**
*釋放預覽輸出流
*/
publicasyncreleasePreviewOutput(){
console.info(`${TAG}releasePreviewOutput`)
if(this.mPreviewOutput){
awaitthis.mPreviewOutput.release()
this.mPreviewOutput=null
}
}


/**
*釋放拍照輸出流
*/
publicasyncreleasePhotoOutput(){
console.info(`${TAG}releasePhotoOutput`)
if(this.mPhotoOutput){
awaitthis.mPhotoOutput.release()
this.mPhotoOutput=null
}
}


publicasyncreleaseSession(){
console.info(`${TAG}releaseSession`)
if(this.mCaptureSession){
awaitthis.mCaptureSession.stop()
console.info(`${TAG}releaseSessionstop`)
awaitthis.mCaptureSession.release()
console.info(`${TAG}releaseSessionrelease`)
this.mCaptureSession=null
console.info(`${TAG}releaseSessionnull`)
}
}

至此,總結下,需要實現(xiàn)相機預覽、拍照功能:

通過 camera 媒體 api 提供的 camera.getCameraManager() 獲取 CameraManager 相機管理類。

通過相機管理類型創(chuàng)建相機預覽與拍照需要的輸入流(createCameraInput)和輸出流(createPreviewOutPut、createPhotoOutput),同時創(chuàng)建相關會話管理(createCaptureSession)

將輸入流、輸出流添加到會話中,并啟動會話

拍照可以直接使用 PhotoOutput.capture 執(zhí)行拍照,并將拍照結果保存到媒體

在退出相機應用時,需要注意釋放相關的資源。

因為分布式相機的應用開發(fā)內容比較長,這篇只說到主控端相機設備預覽與拍照功能,下一篇會將結合分布式相關內容完成主控端設備調用遠程相機進行預覽的功能。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 攝像頭
    +關注

    關注

    59

    文章

    4836

    瀏覽量

    95599
  • 相機
    +關注

    關注

    4

    文章

    1350

    瀏覽量

    53581
  • Module
    +關注

    關注

    0

    文章

    68

    瀏覽量

    12851
  • SDK
    SDK
    +關注

    關注

    3

    文章

    1035

    瀏覽量

    45900
  • OpenHarmony
    +關注

    關注

    25

    文章

    3713

    瀏覽量

    16254

原文標題:OpenHarmony上實現(xiàn)分布式相機

文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術社區(qū)】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    OpenHarmony南向開發(fā)案例:【分布式畫板】

    使用OpenHarmony3.1-Release開發(fā)的應用。通過OpenHarmony分布式技術,使多人能夠一起畫畫。
    的頭像 發(fā)表于 04-12 14:40 ?1036次閱讀
    <b class='flag-5'>OpenHarmony</b>南向開發(fā)案例:【<b class='flag-5'>分布式</b>畫板】

    分布式軟件系統(tǒng)

    分布式軟件系統(tǒng)分布式軟件系統(tǒng)(Distributed Software Systems)是支持分布式處理的軟件系統(tǒng),是在由通信網(wǎng)絡互聯(lián)的多處理機體系結構執(zhí)行任務的系統(tǒng)。它包括
    發(fā)表于 07-22 14:53

    OpenHarmony分布式軟總線流程分析

    OpenHarmony分布式軟總線流程分析,大神總結,大家可以下載去學習了~.~
    發(fā)表于 11-19 15:56

    OpenHarmony標準設備應用開發(fā)(三)——分布式數(shù)據(jù)管理

    程序,并在此基礎,知道了如何在 OpenHarmony 中做到音樂播放,顯示動畫,轉場動畫等相關進階技能,以及如何通過分布式數(shù)據(jù)管理在多臺設備之間實現(xiàn)數(shù)據(jù)的同步更新。在后續(xù)
    發(fā)表于 04-07 18:48

    OpenHarmony3.1分布式技術資料合集

    客戶端(ScreenClient):屏幕圖像顯示代理客戶端,用于在設備顯示其他設備投射過來的屏幕圖像數(shù)據(jù)。3、OpenHarmony3.1的分布式手寫板1.介紹基于TS擴展的聲明
    發(fā)表于 04-11 11:50

    【學習打卡】OpenHarmony分布式數(shù)據(jù)管理介紹

    中,精心設計的架構為數(shù)據(jù)庫和其他數(shù)據(jù)平臺提供了一個模型,在該模型上將部署特定技術以適應各個應用程序。分布式數(shù)據(jù)管理作為OpenHarmony系統(tǒng)的模塊之一,它建立在分布式軟總線的基礎
    發(fā)表于 07-15 15:49

    【學習打卡】OpenHarmony分布式任務調度

    之前我們分享過分布式軟總線和分布式數(shù)據(jù)管理,今天主要說一下OpenHarmony分布式任務調度,分布式任務調度是建立在
    發(fā)表于 07-18 17:06

    【開發(fā)樣例】OpenHarmony分布式購物車

    OpenHarmony分布式購物車一、簡介1.樣例效果分布式購物車demo 模擬的是我們購物時參加滿減活動,進行拼單的場景;實現(xiàn)兩人拼單時,其他一人添加商品到購物車,另外一人購物車列表
    發(fā)表于 07-29 14:17

    OpenHarmony 分布式硬件關鍵技術

    的視頻會議;在影音娛樂場景下,能夠輕松地把手機音視頻放到電視和音箱播放,還可以讓家里的燈光自動跟隨電影和音樂進行變化,實現(xiàn)非常震撼的家庭影院的效果。 期待越來越多的開發(fā)者參與OpenHarmony的生態(tài)中來,共同研究和探討
    發(fā)表于 08-24 17:25

    分布式系統(tǒng)硬件資源池原理和接入實踐

    /distributed_hardware_components_cfg.json 三個接口的 so 實現(xiàn)后,編譯打包到系統(tǒng)庫路徑下,同時配置到分布式硬件部件配置文件中,設備組網(wǎng)上線后,可以看到分布式
    發(fā)表于 12-06 10:02

    基于OpenHarmony分布式應用開發(fā)框架使用教程

    電子發(fā)燒友網(wǎng)站提供《基于OpenHarmony分布式應用開發(fā)框架使用教程.zip》資料免費下載
    發(fā)表于 04-12 11:19 ?9次下載

    OpenHarmony技術論壇:分布式相機分布式圖庫功能

    OpenHarmony Tech Day·技術日》 技術論壇 新增分布式相機分布式圖庫功能 相比OpenHarmony 3.0版本,
    的頭像 發(fā)表于 04-25 15:06 ?1819次閱讀
    <b class='flag-5'>OpenHarmony</b>技術論壇:<b class='flag-5'>分布式</b><b class='flag-5'>相機</b>和<b class='flag-5'>分布式</b>圖庫功能

    OpenHarmony生態(tài)論壇:OpenHarmony分布式能力帶來智聯(lián)新體驗

    OpenHarmony生態(tài)論壇:OpenHarmony分布式能力帶來智聯(lián)新體驗 ? ? 審核編輯:彭菁 ?
    的頭像 發(fā)表于 04-25 17:13 ?1308次閱讀
    <b class='flag-5'>OpenHarmony</b>生態(tài)論壇:<b class='flag-5'>OpenHarmony</b><b class='flag-5'>分布式</b>能力帶來智聯(lián)新體驗

    鴻蒙分布式相機“踩坑”分享

    接上一篇 OpenHarmony 分布式相機),今天我們來說下如何實現(xiàn)分布式
    的頭像 發(fā)表于 03-08 14:19 ?1860次閱讀

    誠邀共建 | OpenHarmony分布式兼容性測試盒子共建任務

    廠商的115個標準系統(tǒng)產(chǎn)品,通過OpenHarmony官網(wǎng)分布式兼容性測評。 為支撐OpenHarmony分布式在開源領域的繁榮共建,兼容性工作重點需提升不同形態(tài)設備的測評能力,提高了
    的頭像 發(fā)表于 06-20 21:05 ?603次閱讀
    RM新时代网站-首页