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

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

OpenHarmony上實(shí)現(xiàn)分布式相機(jī)

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

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

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

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

https://gitee.com/openharmony/distributedhardware_distributed_camera

分布式框架圖

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

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

這種場(chǎng)景下,設(shè)備 A 是主控端,設(shè)備 B 是被控端,兩個(gè)設(shè)備通過(guò)軟總線進(jìn)行交互。

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

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

CameraClient:直接通過(guò)調(diào)用被控端 CameraFramwork 的接口來(lái)完成對(duì)設(shè)備 B 相機(jī)的控制。

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

相關(guān)名詞介紹:

主控端(source):控制端,通過(guò)調(diào)用分布式相機(jī)能力,使用被控端的攝像頭進(jìn)行預(yù)覽、拍照、錄像等功能。

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

現(xiàn)在我們要實(shí)現(xiàn)分布式相機(jī),在主控端調(diào)用被控端相機(jī),實(shí)現(xiàn)遠(yuǎn)程操作相機(jī),開(kāi)發(fā)此應(yīng)用的具體需求:

支持本地相機(jī)的預(yù)覽、拍照、保存相片、相片縮略圖、快速查看相片、切換攝像頭(如果一臺(tái)設(shè)備上存在多個(gè)攝像頭時(shí))。

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

自由切換本地相機(jī)和遠(yuǎn)程相機(jī)。

UI 草圖

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

從草圖上看,我們簡(jiǎn)單的明應(yīng)用 UI 布局的整體內(nèi)容:

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

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

底部分為如下三個(gè)部分。

具體如下:

相機(jī)縮略圖:顯示當(dāng)前設(shè)備媒體庫(kù)中最新的圖片,點(diǎn)擊相機(jī)縮略圖按鈕可以查看相關(guān)的圖片。

拍照:點(diǎn)擊拍照按鈕,將相機(jī)當(dāng)前幀保存到本地媒體庫(kù)中。

切換攝像頭:如果一臺(tái)設(shè)備有多個(gè)攝像頭時(shí),例如相機(jī)有前后置攝像頭,點(diǎn)擊切換后會(huì)將當(dāng)前預(yù)覽的頁(yè)面切換到另外一個(gè)攝像頭的圖像。

實(shí)現(xiàn)效果

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

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

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

如下:

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

設(shè)備:DAYU200

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

SDK:Full_3.2.9.2

開(kāi)發(fā)模式:Stage

開(kāi)發(fā)語(yǔ)言:ets

開(kāi)發(fā)實(shí)踐

本篇主要在應(yīng)用層的角度實(shí)現(xiàn)分布式相機(jī),實(shí)現(xiàn)遠(yuǎn)程相機(jī)與實(shí)現(xiàn)本地相機(jī)的流程相同,只是使用的相機(jī)對(duì)象不同,所以我們先完成本地相機(jī)的開(kāi)發(fā),再通過(guò)參數(shù)修改相機(jī)對(duì)象來(lái)啟動(dòng)遠(yuǎn)程相機(jī)。

①創(chuàng)建項(xiàng)目

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

②權(quán)限聲明

(1)module.json 配置權(quán)限

說(shuō)明:在 module 模塊下添加權(quán)限聲明,權(quán)限的詳細(xì)說(shuō)明

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 頁(yè)面的初始化 aboutToAppear() 申請(qǐng)權(quán)限

代碼如下:

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)}`)
//判斷授權(quán)是否完成
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)
})
}
這里有個(gè)獲取縮略圖的功能,主要是獲取媒體庫(kù)中根據(jù)時(shí)間排序,獲取最新拍照的圖片作為當(dāng)前需要顯示的縮略圖,實(shí)現(xiàn)此方法在后面說(shuō) CameraService 類的時(shí)候進(jìn)行詳細(xì)介紹。 注意:如果首次啟動(dòng)應(yīng)用,在授權(quán)完成后需要加載相機(jī),則建議授權(quán)放在啟動(dòng)頁(yè)完成,或者在調(diào)用相機(jī)頁(yè)面之前添加一個(gè)過(guò)渡頁(yè)面,主要用于完成權(quán)限申請(qǐng)和啟動(dòng)相機(jī)的入口,否則首次完成授權(quán)后無(wú)法顯示相機(jī)預(yù)覽,需要退出應(yīng)用再重新進(jìn)入才可以正常預(yù)覽,這里先簡(jiǎn)單說(shuō)明下,文章后續(xù)會(huì)在問(wèn)題環(huán)節(jié)詳細(xì)介紹。

③UI 布局

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

參看:XComponent 詳細(xì)介紹

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

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

代碼如下:

@State@Watch('selectedIndexChange')selectIndex:number=0
//設(shè)備列表
@Statedevices:Array=[]
//設(shè)備選擇彈窗
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//是否正在切換相機(jī)
privateisInitCamera:boolean=false//是否已初始化相機(jī)
privateisPermissions:boolean=false//是否完成授權(quán)
privatecomponentController:XComponentController=newXComponentController()
privatemCurDeviceID:string=Constant.LOCAL_DEVICE_ID//默認(rèn)本地相機(jī)
privatemCurCameraIndex:number=0//默認(rèn)相機(jī)列表中首個(gè)相機(jī)
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)啟動(dòng)系統(tǒng)相冊(cè)

說(shuō)明:用戶點(diǎn)擊圖片縮略圖時(shí)需要啟動(dòng)圖片查看,這里直接打開(kāi)系統(tǒng)相冊(cè),查看相關(guān)的圖片。

代碼如下:

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

④相機(jī)服務(wù) CameraService.ts

(1)CameraService 單例模式,用于提供操作相機(jī)相關(guān)的業(yè)務(wù)

代碼如下:

privatestaticinstance:CameraService=null


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

同時(shí)通過(guò) CameraManager…getSupportedCameras() 獲取前期支持的相機(jī)設(shè)備集合,這里的相機(jī)設(shè)備包括當(dāng)前設(shè)備上安裝的相機(jī)設(shè)備和遠(yuǎn)程設(shè)備上的相機(jī)設(shè)備。

代碼如下:

/**
*初始化
*/
publicasyncinitCamera():Promise{
console.info(`${TAG}initCamera`)
if(this.mCameraManager===null){
this.mCameraManager=awaitcamera.getCameraManager(globalThis.cameraAbilityContext)
//注冊(cè)監(jiān)聽(tīng)相機(jī)狀態(tài)變化
this.mCameraManager.on('cameraStatus',(cameraStatusInfo)=>{
console.info(`${TAG}cameraStatus:${JSON.stringify(cameraStatusInfo)}`)
})
//獲取相機(jī)列表
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ī)列表中是否已經(jīng)存在此相機(jī)
letisExist:boolean=false
for(letitemofthis.mCameraMap.get(key)){
if(item.cameraId===cameraDevice.cameraId){
isExist=true
break
}
}
//添加列表中沒(méi)有的相機(jī)
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)建相機(jī)輸入流

說(shuō)明:CameraManager.createCameraInput() 可以創(chuàng)建相機(jī)輸出流 CameraInput 實(shí)例,CameraInput 是在 CaptureSession 會(huì)話中使用的相機(jī)信息,支持打開(kāi)相機(jī)、關(guān)閉相機(jī)等能力。 代碼如下:

/**
*創(chuàng)建相機(jī)輸入流
*@paramcameraIndex相機(jī)下標(biāo)
*@paramdeviceId設(shè)備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)相機(jī)預(yù)覽輸出流

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

/**
*創(chuàng)建相機(jī)預(yù)覽輸出流
*/
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}}`)
//獲取當(dāng)前相機(jī)設(shè)備支持的輸出能力
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)聽(tīng)預(yù)覽幀開(kāi)始
????????????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)拍照輸出流

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

代碼如下:

/**
*創(chuàng)建拍照輸出流
*/
publicasynccreatePhotoOutput(functionCallback:FunctionCallBack){
console.info(`${TAG}createPhotoOutput`)
if(!this.mCameraManager){
console.error(`${TAG}createPhotoOutputmCameraManagerisnull`)
return
}
//通過(guò)寬、高、圖片格式、容量創(chuàng)建ImageReceiver實(shí)例
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(),這里將保存拍照的圖片進(jìn)行封裝—SaveCameraAsset.ts,后面會(huì)單獨(dú)介紹。

(6)會(huì)話管理

說(shuō)明:通過(guò) CameraManager.createCaptureSession() 可以創(chuàng)建相機(jī)的會(huì)話類,保存相機(jī)運(yùn)行所需要的所有資源 CameraInput、CameraOutput,并向相機(jī)設(shè)備申請(qǐng)完成相機(jī)拍照或錄像功能。 CaptureSession 對(duì)象提供了開(kāi)始配置會(huì)話、添加 CameraInput 到會(huì)話、添加 CameraOutput 到會(huì)話、提交配置信息、開(kāi)始會(huì)話、停止會(huì)話、釋放等能力。

代碼如下:

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`)
}
⑤拍照 說(shuō)明:通過(guò) PhotoOutput.capture() 可以實(shí)現(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

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

代碼如下:

/**
*保存相機(jī)拍照的資源
*/
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
}
/**
*保存拍照?qǐng)D片
*@paramimageReceiver圖像接收對(duì)象
*@paramthumbWidth縮略圖寬度
*@paramthumbHeight縮略圖高度
*@paramcallback回調(diào)
*/
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)//獲取媒體庫(kù)實(shí)例
//接收?qǐng)D片回調(diào)
imageReceiver.on('imageArrival',async()=>{
console.info(`${TAG}saveImageImageArrival`)
//使用當(dāng)前時(shí)間命名
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ù)寫(xiě)入到媒體資源
if(dataUri!==undefined){
photoUri=dataUri.uri
console.info(`${TAG}saveImagephotoUri:${photoUri}`)
constargs=dataUri.id.toString()
console.info(`${TAG}saveImageid:${args}`)
//通過(guò)ID查找媒體資源
constfetchOptions:mediaLibrary.MediaFetchOptions={
selections:`${fileKeyObj.ID}=?`,
selectionArgs:[args]
}
console.info(`${TAG}saveImagefetchOptions:${JSON.stringify(fetchOptions)}`)
constfetchFileResult=awaitmedia.getFileAssets(fetchOptions)
constfileAsset=awaitfetchFileResult.getAllObject()//獲取文件檢索結(jié)果中的所有文件資
if(fileAsset!=undefined){
fileAsset.forEach((dataInfo)=>{
dataInfo.open('Rw').then((fd)=>{//RW是讀寫(xiě)方式打開(kāi)文件獲取fd
console.info(`${TAG}saveImagedataInfo.opencalled.fd:${fd}`)
//將緩存圖片流寫(xiě)入資源
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`)
}
})
}
/**
*檢測(cè)文件名稱
*@paramfileName文件名稱
*如果同一時(shí)間有多張圖片,則使用時(shí)間_index命名
*/
privatecheckName(fileName:string):string{
if(this.lastSaveTime==fileName){
this.saveIndex++
return`${fileName}_${this.saveIndex}`
}
this.lastSaveTime=fileName
this.saveIndex=0
returnfileName
}
}

⑦獲取縮略圖

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

代碼如下:

/**
*獲取縮略圖
*@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 說(shuō)明:實(shí)現(xiàn)獲取縮略圖的對(duì)象。 代碼如下:
/**
*縮略圖處理器
*/
importmediaLibraryfrom'@ohos.multimedia.mediaLibrary';
importimagefrom'@ohos.multimedia.image';
constTAG:string='ThumbnailGetter'
exportdefaultclassThumbnailGetter{
publicasyncgetThumbnailInfo(width:number,height:number,uri?:string):Promise{
console.info(`${TAG}getThumbnailInfo`)
//文件關(guān)鍵信息
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-相對(duì)公共目錄的路徑
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)//文件檢索結(jié)果集
constcount=fetchFileResult.getCount()
console.info(`${TAG}count=${count}`)
if(count==0){
returnundefined
}
//獲取結(jié)果集合中的最后一張圖片
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
}
}
⑧釋放資源 說(shuō)明:在相機(jī)設(shè)備切換時(shí),如前后置攝像頭切換或者不同設(shè)備之間的攝像頭切換時(shí)都需要先釋放資源,再重新創(chuàng)建新的相機(jī)會(huì)話才可以正常運(yùn)行,釋放的資源包括:釋放相機(jī)輸入流、預(yù)覽輸出流、拍照輸出流、會(huì)話。 代碼如下:
/**
*釋放相機(jī)輸入流
*/
publicasyncreleaseCameraInput(){
console.info(`${TAG}releaseCameraInput`)
if(this.mCameraInput){
try{
awaitthis.mCameraInput.release()
}catch(err){
console.error(`${TAG}releaseCameraInput${err}}`)
}
this.mCameraInput=null
}
}



/**
*釋放預(yù)覽輸出流
*/
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`)
}
}

至此,總結(jié)下,需要實(shí)現(xiàn)相機(jī)預(yù)覽、拍照功能:

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

通過(guò)相機(jī)管理類型創(chuàng)建相機(jī)預(yù)覽與拍照需要的輸入流(createCameraInput)和輸出流(createPreviewOutPut、createPhotoOutput),同時(shí)創(chuàng)建相關(guān)會(huì)話管理(createCaptureSession)

將輸入流、輸出流添加到會(huì)話中,并啟動(dòng)會(huì)話

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

在退出相機(jī)應(yīng)用時(shí),需要注意釋放相關(guān)的資源。

因?yàn)榉植际较鄼C(jī)的應(yīng)用開(kāi)發(fā)內(nèi)容比較長(zhǎng),這篇只說(shuō)到主控端相機(jī)設(shè)備預(yù)覽與拍照功能,下一篇會(huì)將結(jié)合分布式相關(guān)內(nèi)容完成主控端設(shè)備調(diào)用遠(yuǎn)程相機(jī)進(jìn)行預(yù)覽的功能。

審核編輯:湯梓紅

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

    關(guān)注

    59

    文章

    4751

    瀏覽量

    94376
  • 相機(jī)
    +關(guān)注

    關(guān)注

    4

    文章

    1303

    瀏覽量

    53132
  • Module
    +關(guān)注

    關(guān)注

    0

    文章

    63

    瀏覽量

    12832
  • SDK
    SDK
    +關(guān)注

    關(guān)注

    3

    文章

    1006

    瀏覽量

    45411
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3546

    瀏覽量

    15734

原文標(biāo)題:OpenHarmony上實(shí)現(xiàn)分布式相機(jī)

文章出處:【微信號(hào):gh_834c4b3d87fe,微信公眾號(hào):OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

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

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

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

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

    OpenHarmony分布式軟總線流程分析

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

    OpenHarmony標(biāo)準(zhǔn)設(shè)備應(yīng)用開(kāi)發(fā)(三)——分布式數(shù)據(jù)管理

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

    OpenHarmony3.1分布式技術(shù)資料合集

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

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

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

    【學(xué)習(xí)打卡】OpenHarmony分布式任務(wù)調(diào)度

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

    【開(kāi)發(fā)樣例】OpenHarmony分布式購(gòu)物車

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

    OpenHarmony 分布式硬件關(guān)鍵技術(shù)

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

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

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

    基于OpenHarmony分布式應(yīng)用開(kāi)發(fā)框架使用教程

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

    OpenHarmony技術(shù)論壇:分布式相機(jī)分布式圖庫(kù)功能

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

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

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

    鴻蒙分布式相機(jī)“踩坑”分享

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

    誠(chéng)邀共建 | OpenHarmony分布式兼容性測(cè)試盒子共建任務(wù)

    廠商的115個(gè)標(biāo)準(zhǔn)系統(tǒng)產(chǎn)品,通過(guò)OpenHarmony官網(wǎng)分布式兼容性測(cè)評(píng)。 為支撐OpenHarmony分布式在開(kāi)源領(lǐng)域的繁榮共建,兼容性工作重點(diǎn)需提升不同形態(tài)設(shè)備的測(cè)評(píng)能力,提高了
    的頭像 發(fā)表于 06-20 21:05 ?530次閱讀