成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

OpenHarmony 分布式相機(上)

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

??想了解更多關于開源的內容,請訪問:??

??51CTO 開源基礎軟件社區??

??https://ost.51cto.com??

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

OpenHarmony系統一個重要的能力就是分布式,對于分布式相機我也倍感興趣,之前看到官方對分布式相機的一些說明,這里簡單介紹下,有興趣可以查看官方文檔:??分布式相機部件??。

分布式框架圖

OpenHarmony 分布式相機(上)-開源基礎軟件社區

分布式相機框架(Distributed Hardware)分為主控端和被控端。假設:設備B擁有本地相機設備,分布式組網中的設備A可以分布式調用設備B的相機設備。這種場景下,設備A是主控端,設備B是被控端,兩個設備通過軟總線進行交互。

  • VirtualCameraHAL:作為硬件適配層(HAL)的一部分,負責和分布式相機框架中的主控端交互,將主控端CameraFramwork下發的指令傳輸給分布式相機框架的SourceMgr處理。
  • SourceMgr:通過軟總線將控制信息傳遞給被控端的CameraClient。
  • CameraClient:直接通過調用被控端CameraFramwork的接口來完成對設備B相機的控制。

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

相關名詞介紹

  • 主控端(source):控制端,通過調用分布式相機能力,使用被控端的攝像頭進行預覽、拍照、錄像等功能。
  • 被控端(sink):被控制端,通過分布式相機接收主控端的命令,使用本地攝像頭為主控端提供圖像數據。

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

  1. 支持本地相機的預覽、拍照、保存相片、相片縮略圖、快速查看相片、切換攝像頭(如果一臺設備上存在多個攝像頭時);
  2. 同一網絡下,支持分布式pin碼認證,遠程連接;
  3. 自由切換本地相機和遠程相機。

UI草圖

OpenHarmony 分布式相機(上)-開源基礎軟件社區

從草圖上看,我們簡單的明應用UI布局的整體內容:
1、頂部右上角有個"切換設備"的按鈕,點擊 彈窗顯示設備列表,可以實現設備認證與設備切換功能。
2、中間使用XComponent組件實現的相機預覽區域。
3、底部分為三個部分。

  • 相機縮略圖:顯示當前設備媒體庫中最新的圖片,點擊相機縮略圖按鈕可以查看相關的圖片。
  • 拍照:點擊拍照按鈕,將相機當前幀保存到本地媒體庫中。
  • 切換攝像頭:如果一臺設備有多個攝像頭時,例如相機有前后置攝像頭,點擊切換后會將當前預覽的頁面切換到另外一個攝像頭的圖像。

實現效果

??演示視頻地址–待發布??https://ost.51cto.com/show/21218

OpenHarmony 分布式相機(上)-開源基礎軟件社區

OpenHarmony 分布式相機(上)-開源基礎軟件社區

開發環境

系統: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
開發模式:Stage
開發語言:ets

開發實踐

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

1、創建項目

OpenHarmony 分布式相機(上)-開源基礎軟件社區

2、權限聲明

(1)module.json 配置權限

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

"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()申請權限

代碼如下:

let permissionList: Array<string> = [
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA",
"ohos.permission.WRITE_MEDIA",
"ohos.permission.CAMERA",
"ohos.permission.MICROPHONE",
"ohos.permission.DISTRIBUTED_DATASYNC"
]
async aboutToAppear() {
console.info(`${TAG} aboutToAppear`)
globalThis.cameraAbilityContext.requestPermissionsFromUser(permissionList).then(async (data) => {
console.info(`${TAG} data permissions: ${JSON.stringify(data.permissions)}`)
console.info(`${TAG} data authResult: ${JSON.stringify(data.authResults)}`)
// 判斷授權是否完成
let resultCount: number = 0
for (let result of data.authResults) {
if (result === 0) {
resultCount += 1
}
}
if (resultCount === permissionList.length) {
this.isPermissions = true
}
await this.initCamera()
// 獲取縮略圖
this.mCameraService.getThumbnail(this.functionBackImpl)
})
}

這里有個獲取縮略圖的功能,主要是獲取媒體庫中根據時間排序,獲取最新拍照的圖片作為當前需要顯示的縮略圖,實現此方法在后面說CameraService類的時候進行詳細介紹。

注意: 如果首次啟動應用,在授權完成后需要加載相機,則建議授權放在啟動頁完成,或者在調用相機頁面之前添加一個過渡頁面,主要用于完成權限申請和啟動相機的入口,否則首次完成授權后無法顯示相機預覽,需要退出應用再重新進入才可以正常預覽,這里先簡單說明下,文章后續會在問題環節詳細介紹。

3、UI布局

說明: UI如前面截圖所示,實現整體頁面的布局。頁面中主要使用到XComponent組件,用于EGL/OpenGLES和媒體數據寫入,并顯示在XComponent組件。參看:??XComponent詳細介紹??

  • onLoad():XComponent插件加載完成時的回調,在插件完成時可以獲取**ID并初始化相機;
  • XComponentController:XComponent組件控制器,可以綁定至XComponent組件,通過getXComponent/**aceId()獲取XComponent對應的/**aceID。

代碼如下:

@State @Watch('selectedIndexChange') selectIndex: number = 0
// 設備列表
@State devices: Array<deviceManager.DeviceInfo> = []
// 設備選擇彈窗
private dialogController: CustomDialogController = new CustomDialogController({
builder: DeviceDialog({
deviceList: $devices,
selectIndex: $selectIndex,
}),
autoCancel: true,
alignment: DialogAlignment.Center
})
@State curPictureWidth: number = 70
@State curPictureHeight: number = 70
@State curThumbnailWidth: number = 70
@State curThumbnailHeight: number = 70
@State curSwitchAngle: number = 0
@State Id: string = ''
@State thumbnail: image.PixelMap = undefined
@State resourceUri: string = ''
@State isSwitchDeviceing: boolean = false // 是否正在切換相機
private isInitCamera: boolean = false // 是否已初始化相機
private isPermissions: boolean = false // 是否完成授權
private componentController: XComponentController = new XComponentController()
private mCurDeviceID: string = Constant.LOCAL_DEVICE_ID // 默認本地相機
private mCurCameraIndex: number = 0 // 默認相機列表中首個相機
private mCameraService = 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} click distributed auth.`)
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} XComponent onLoad is called`)
this.componentController.setXComponentxxxxaceSize({
xxxxWidth: Resolution.DEFAULT_WIDTH,
xxxxaceHeight: Resolution.DEFAULT_HEIGHT
})
this.id = this.componentController.getXComponentxxxxaceId()
console.info(`${TAG} id: ${this.id}`)
await this.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} launch bundle com.ohos.photos`)
await globalThis.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)啟動系統相冊

說明: 用戶點擊圖片縮略圖時需要啟動圖片查看,這里直接打開系統相冊,查看相關的圖片。
代碼如下:

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

4、相機服務 CameraService.ts

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

代碼如下:

private static instance: CameraService = null
private constructor() {
this.mThumbnailGetter = new ThumbnailGetter()
}
/**
* 單例
*/
public static getInstance(): CameraService {
if (this.instance === null) {
this.instance = new CameraService()
}
return this.instance
}

(2)初始化相機

說明: 通過媒體相機提供的API(@ohos.multimedia.camera)getCameraManager()獲取相機管理對象CameraManager,并注冊相機狀態變化監聽器,實時更新相機狀態,同時通過CameraManager…getSupportedCameras() 獲取前期支持的相機設備集合,這里的相機設備包括當前設備上安裝的相機設備和遠程設備上的相機設備。
代碼如下:

/**
* 初始化
*/
public async initCamera(): Promise<number> {
console.info(`${TAG} initCamera`)
if (this.mCameraManager === null) {
this.mCameraManager = await camera.getCameraManager(globalThis.cameraAbilityContext)
// 注冊監聽相機狀態變化
this.mCameraManager.on('cameraStatus', (cameraStatusInfo) => {
console.info(`${TAG} camera Status: ${JSON.stringify(cameraStatusInfo)}`)
})
// 獲取相機列表
let cameras: Array<camera.CameraDevice> = await this.mCameraManager.getSupportedCameras()
if (cameras) {
this.mCameraCount = cameras.length
console.info(`${TAG} mCameraCount: ${this.mCameraCount}`)
if (this.mCameraCount === 0) {
return this.mCameraCount
}
for (let i = 0; i < cameras.length; i++) {
console.info(`${TAG} --------------Camera Info-------------`)
const tempCameraId: string = cameras[i].cameraId
console.info(`${TAG} camera_id: ${tempCameraId}`)
console.info(`${TAG} cameraPosition: ${cameras[i].cameraPosition}`)
console.info(`${TAG} cameraType: ${cameras[i].cameraType}`)
const connectionType = cameras[i].connectionType
console.info(`${TAG} connectionType: ${connectionType}`)
// CameraPosition 0-未知未知 1-后置 2-前置
// CameraType 0-未知類型 1-廣角 2-超廣角 3長焦 4-帶景深信息
// connectionType 0-內置相機 1-USB連接相機 2-遠程連接相機
// 判斷本地相機還是遠程相機
if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) {
// 本地相機
this.displayCameraDevice(Constant.LOCAL_DEVICE_ID, cameras[i])
} else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {
// 遠程相機 相機ID格式 : deviceID__Camera_cameraID 例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7__Camera_lcam001
const cameraKey: string = tempCameraId.split('__Camera_')[0]
console.info(`${TAG} cameraKey: ${cameraKey}`)
this.displayCameraDevice(cameraKey, cameras[i])
}
}
// todo test 選擇首個相機
this.mCurCameraDevice = cameras[0]
console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)
}
}
return this.mCameraCount
}
/**
* 處理相機設備
* @param key
* @param cameraDevice
*/
private displayCameraDevice(key: string, cameraDevice: camera.CameraDevice) {
console.info(`${TAG} displayCameraDevice ${key}`)
if (this.mCameraMap.has(key) && this.mCameraMap.get(key)?.length > 0) {
console.info(`${TAG} displayCameraDevice has mCameraMap`)
// 判斷相機列表中是否已經存在此相機
let isExist: boolean = false
for (let item of this.mCameraMap.get(key)) {
if (item.cameraId === cameraDevice.cameraId) {
isExist = true
break
}
}
// 添加列表中沒有的相機
if (!isExist) {
console.info(`${TAG} displayCameraDevice not exist , push ${cameraDevice.cameraId}`)
this.mCameraMap.get(key).push(cameraDevice)
} else {
console.info(`${TAG} displayCameraDevice has existed`)
}
} else {
let cameras: Array<camera.CameraDevice> = []
console.info(`${TAG} displayCameraDevice push ${cameraDevice.cameraId}`)
cameras.push(cameraDevice)
this.mCameraMap.set(key, cameras)
}
}

(3)創建相機輸入流

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

/**
* 創建相機輸入流
* @param cameraIndex 相機下標
* @param deviceId 設備ID
*/
public async createCameraInput(cameraIndex?: number, deviceId?: string) {
console.info(`${TAG} createCameraInput`)
if (this.mCameraManager === null) {
console.error(`${TAG} mCameraManager is null`)
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 < this.mCameraMap.get(deviceId)?.length) {
this.mCurCameraDevice = this.mCameraMap.get(deviceId)[cameraIndex]
} else {
this.mCurCameraDevice = this.mCameraMap.get(deviceId)[0]
}
}
console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)
try {
this.mCameraInput = await this.mCameraManager.createCameraInput(this.mCurCameraDevice)
console.info(`${TAG} mCameraInput: ${JSON.stringify(this.mCameraInput)}`)
this.mCameraInput.on('error', this.mCurCameraDevice, (error) => {
console.error(`${TAG} CameraInput error: ${JSON.stringify(error)}`)
})
await this.mCameraInput.open()
} catch (err) {
if (err) {
console.error(`${TAG} failed to createCameraInput`)
}
}
}

(4)相機預覽輸出流

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

/**
* 創建相機預覽輸出流
*/
public async createPreviewOutput(Id: string, callback : PreviewCallBack) {
console.info(`${TAG} createPreviewOutput`)
if (this.mCameraManager === null) {
console.error(`${TAG} createPreviewOutput mCameraManager is null`)
return
}
this.Id = Id
console.info(`${TAG} Id ${Id}}`)
// 獲取當前相機設備支持的輸出能力
let cameraOutputCap = await this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
if (!cameraOutputCap) {
console.error(`${TAG} createPreviewOutput getSupportedOutputCapability error}`)
return
}
console.info(`${TAG} createPreviewOutput cameraOutputCap ${JSON.stringify(cameraOutputCap)}`)
let previewProfilesArray = cameraOutputCap.previewProfiles
let previewProfiles: 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`)
// 監聽預覽幀開始
this.mPreviewOutput.on('frameStart', () => {
console.info(`${TAG} createPreviewOutput camera frame Start`)
callback.onFrameStart()
})
this.mPreviewOutput.on('frameEnd', () => {
console.info(`${TAG} createPreviewOutput camera frame End`)
callback.onFrameEnd()
})
this.mPreviewOutput.on('error', (error) => {
console.error(`${TAG} createPreviewOutput error: ${error}`)
})
} catch (err) {
console.error(`${TAG} failed to createPreviewOutput ${err}`)
}
}

(5)拍照輸出流

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

/**
* 創建拍照輸出流
*/
public async createPhotoOutput(functionCallback: FunctionCallBack) {
console.info(`${TAG} createPhotoOutput`)
if (!this.mCameraManager) {
console.error(`${TAG} createPhotoOutput mCameraManager is null`)
return
}
// 通過寬、高、圖片格式、容量創建ImageReceiver實例
const receiver: image.ImageReceiver = image.createImageReceiver(Resolution.DEFAULT_WIDTH, Resolution.DEFAULT_HEIGHT, image.ImageFormat.JPEG, 8)
const imageId: string = await receiver.getReceivingxxxxaceId()
console.info(`${TAG} createPhotoOutput imageId: ${imageId}`)
let cameraOutputCap = await this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)
console.info(`${TAG} createPhotoOutput cameraOutputCap ${cameraOutputCap}`)
if (!cameraOutputCap) {
console.error(`${TAG} createPhotoOutput getSupportedOutputCapability error}`)
return
}
let photoProfilesArray = cameraOutputCap.photoProfiles
let photoProfiles: 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()可以創建相機的會話類,保存相機運行所需要的所有資源CameraInput、CameraOutput,并向相機設備申請完成相機拍照或錄像功能。CaptureSession對象提供了開始配置會話、添加CameraInput到會話、添加CameraOutput到會話、提交配置信息、開始會話、停止會話、釋放等能力。
代碼如下:

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

this.mCaptureSession = await this.mCameraManager.createCaptureSession()
console.info(`${TAG} createSession mCaptureSession ${this.mCaptureSession}`)

this.mCaptureSession.on('error', (error) => {
console.error(`${TAG} CaptureSession error ${JSON.stringify(error)}`)
})
try {
await this.mCaptureSession?.beginConfig()
await this.mCaptureSession?.addInput(this.mCameraInput)
if (this.mPhotoOutput != null) {
console.info(`${TAG} createSession addOutput PhotoOutput`)
await this.mCaptureSession?.addOutput(this.mPhotoOutput)
}
await this.mCaptureSession?.addOutput(this.mPreviewOutput)
} catch (err) {
if (err) {
console.error(`${TAG} createSession beginConfig fail err:${JSON.stringify(err)}`)
}
}
try {
await this.mCaptureSession?.commitConfig()
} catch (err) {
if (err) {
console.error(`${TAG} createSession commitConfig fail err:${JSON.stringify(err)}`)
}
}
try {
await this.mCaptureSession?.start()
} catch (err) {
if (err) {
console.error(`${TAG} createSession start fail err:${JSON.stringify(err)}`)
}
}
console.info(`${TAG} createSession mCaptureSession start`)
}

5、拍照

說明: 通過PhotoOutput.capture()可以實現拍照功能。
代碼如下:

/**
* 拍照
*/
public async takePicture() {
console.info(`${TAG} takePicture`)
if (!this.mCaptureSession) {
console.info(`${TAG} takePicture session is release`)
return
}
if (!this.mPhotoOutput) {
console.info(`${TAG} takePicture mPhotoOutput is null`)
return
}
try {
const photoCaptureSetting: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: camera.ImageRotation.ROTATION_0,
location: {
latitude: 0,
longitude: 0,
altitude: 0
},
mirror: false
}
await this.mPhotoOutput.capture(photoCaptureSetting)
} catch (err) {
console.error(`${TAG} takePicture err:${JSON.stringify(err)}`)
}
}

6、保存圖片 SaveCameraAsset

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

/**
* 保存相機拍照的資源
*/
import image from '@ohos.multimedia.image'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
import { FunctionCallBack } from '../model/CameraService'
import DateTimeUtil from '../utils/DateTimeUtil'
import fileIO from '@ohos.file.fs';
import ThumbnailGetter from '../model/ThumbnailGetter'
let photoUri: string // 圖片地址
const TAG: string = 'SaveCameraAsset'
export default class SaveCameraAsset {
private lastSaveTime: string = ''
private saveIndex: number = 0
constructor() {
}
public getPhotoUri(): string {
console.info(`${TAG} getPhotoUri = ${photoUri}`)
return photoUri
}
/**
* 保存拍照圖片
* @param imageReceiver 圖像接收對象
* @param thumbWidth 縮略圖寬度
* @param thumbHeight 縮略圖高度
* @param callback 回調
*/
public saveImage(imageReceiver: image.ImageReceiver, thumbWidth: number, thumbHeight: number, thumbnailGetter :ThumbnailGetter, callback: FunctionCallBack) {
console.info(`${TAG} saveImage`)
const mDateTimeUtil = new DateTimeUtil()
const fileKeyObj = mediaLibrary.FileKey
const mediaType = mediaLibrary.MediaType.IMAGE
let buffer = new ArrayBuffer(4096)
const media = mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext) // 獲取媒體庫實例
// 接收圖片回調
imageReceiver.on('imageArrival', async () => {
console.info(`${TAG} saveImage ImageArrival`)
// 使用當前時間命名
const displayName = 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} saveImage failed to get valid image error = ${err}`)
return
}
// 根據圖像的組件類型從圖像中獲取組件緩存 4-JPEG類型
imageObj.getComponent(image.ComponentType.JPEG, async (errMsg, imgComponent) => {
if (imgComponent === undefined) {
console.error(`${TAG} getComponent failed to get valid buffer error = ${errMsg}`)
return
}
if (imgComponent.byteBuffer) {
console.info(`${TAG} getComponent imgComponent.byteBuffer ${imgComponent.byteBuffer}`)
buffer = imgComponent.byteBuffer
} else {
console.info(`${TAG} getComponent imgComponent.byteBuffer is undefined`)
}
await imageObj.release()
})
})
let publicPath:string = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)
console.info(`${TAG} saveImage publicPath = ${publicPath}`)
// 創建媒體資源 返回提供封裝文件屬性
const dataUri : mediaLibrary.FileAsset = await media.createAsset(mediaType, displayName, publicPath)
// 媒體文件資源創建成功,將拍照的數據寫入到媒體資源
if (dataUri !== undefined) {
photoUri = dataUri.uri
console.info(`${TAG} saveImage photoUri: ${photoUri}`)
const args = dataUri.id.toString()
console.info(`${TAG} saveImage id: ${args}`)
// 通過ID查找媒體資源
const fetchOptions:mediaLibrary.MediaFetchOptions = {
selections : `${fileKeyObj.ID} = ?`,
selectionArgs : [args]
}
console.info(`${TAG} saveImage fetchOptions: ${JSON.stringify(fetchOptions)}`)
const fetchFileResult = await media.getFileAssets(fetchOptions)
const fileAsset = await fetchFileResult.getAllObject() // 獲取文件檢索結果中的所有文件資
if (fileAsset != undefined) {
fileAsset.forEach((dataInfo) => {
dataInfo.open('Rw').then((fd) => { // RW是讀寫方式打開文件 獲取fd
console.info(`${TAG} saveImage dataInfo.open called. fd: ${fd}`)
// 將緩存圖片流寫入資源
fileIO.write(fd, buffer).then(() => {
console.info(`${TAG} saveImage fileIO.write called`)
dataInfo.close(fd).then(() => {
console.info(`${TAG} saveImage dataInfo.close called`)
// 獲取資源縮略圖
thumbnailGetter.getThumbnailInfo(thumbWidth, thumbHeight, photoUri).then((thumbnail => {
if (thumbnail === undefined) {
console.error(`${TAG} saveImage getThumbnailInfo undefined`)
callback.onCaptureFailure()
} else {
console.info(`${TAG} photoUri: ${photoUri} PixelBytesNumber: ${thumbnail.getPixelBytesNumber()}`)
callback.onCaptureSuccess(thumbnail, photoUri)
}
}))
}).catch(error => {
console.error(`${TAG} saveImage close is error ${JSON.stringify(error)}`)
})
})
})
})
} else {
console.error(`${TAG} saveImage fileAsset: is null`)
}
} else {
console.error(`${TAG} saveImage photoUri is null`)
}
})
}
/**
* 檢測文件名稱
* @param fileName 文件名稱
* 如果同一時間有多張圖片,則使用時間_index命名
*/
private checkName(fileName: string): string {
if (this.lastSaveTime == fileName) {
this.saveIndex++
return `${fileName}_${this.saveIndex}`
}
this.lastSaveTime = fileName
this.saveIndex = 0
return fileName
}
}

7、獲取縮略圖

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

/**
* 獲取縮略圖
* @param callback
*/
public getThumbnail(callback: FunctionCallBack) {
console.info(`${TAG} getThumbnail`)
this.mThumbnailGetter.getThumbnailInfo(Resolution.THUMBNAIL_WIDTH, Resolution.THUMBNAIL_HEIGHT).then((thumbnail) => {
console.info(`${TAG} getThumbnail thumbnail = ${thumbnail}`)
callback.thumbnail(thumbnail)
})
}

(1)ThumbnailGetter.ts

說明: 實現獲取縮略圖的對象。
代碼如下:

/**
* 縮略圖處理器
*/
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import image from '@ohos.multimedia.image';
const TAG: string = 'ThumbnailGetter'
export default class ThumbnailGetter {
public async getThumbnailInfo(width: number, height: number, uri?: string): Promise<image.PixelMap | undefined> {
console.info(`${TAG} getThumbnailInfo`)
// 文件關鍵信息
const fileKeyObj = mediaLibrary.FileKey
// 獲取媒體資源公共路徑
const media: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext)
let publicPath: string = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)
console.info(`${TAG} publicPath = ${publicPath}`)
let fetchOptions: 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} getThumbnailInfo fetchOptions : ${JSON.stringify(fetchOptions)}}`)
const fetchFileResult = await media.getFileAssets(fetchOptions) // 文件檢索結果集
const count = fetchFileResult.getCount()
console.info(`${TAG} count = ${count}`)
if (count == 0) {
return undefined
}
// 獲取結果集合中的最后一張圖片
const lastFileAsset = await fetchFileResult.getFirstObject()
if (lastFileAsset == null) {
console.error(`${TAG} getThumbnailInfo lastFileAsset is null`)
return undefined
}
const thumbnailPixelMap = lastFileAsset.getThumbnail({
width: width,
height: height
})
console.info(`${TAG} getThumbnailInfo thumbnailPixelMap ${JSON.stringify(thumbnailPixelMap)}}`)
return thumbnailPixelMap
}
}

8、釋放資源

說明: 在相機設備切換時,如前后置攝像頭切換或者不同設備之間的攝像頭切換時都需要先釋放資源,再重新創建新的相機會話才可以正常運行,釋放的資源包括:釋放相機輸入流、預覽輸出流、拍照輸出流、會話。
代碼如下:

/**
* 釋放相機輸入流
*/
public async releaseCameraInput() {
console.info(`${TAG} releaseCameraInput`)
if (this.mCameraInput) {
try {
await this.mCameraInput.release()
} catch (err) {
console.error(`${TAG} releaseCameraInput ${err}}`)
}
this.mCameraInput = null
}
}
/**
* 釋放預覽輸出流
*/
public async releasePreviewOutput() {
console.info(`${TAG} releasePreviewOutput`)
if (this.mPreviewOutput) {
await this.mPreviewOutput.release()
this.mPreviewOutput = null
}
}
/**
* 釋放拍照輸出流
*/
public async releasePhotoOutput() {
console.info(`${TAG} releasePhotoOutput`)
if (this.mPhotoOutput) {
await this.mPhotoOutput.release()
this.mPhotoOutput = null
}
}
public async releaseSession() {
console.info(`${TAG} releaseSession`)
if (this.mCaptureSession) {
await this.mCaptureSession.stop()
console.info(`${TAG} releaseSession stop`)
await this.mCaptureSession.release()
console.info(`${TAG} releaseSession release`)
this.mCaptureSession = null
console.info(`${TAG} releaseSession null`)
}
}
至此,總結下,需要實現相機預覽、拍照功能:
1、通過camera媒體api提供的camera.getCameraManager()獲取CameraManager相機管理類;
2、通過相機管理類型創建相機預覽與拍照需要的輸入流(createCameraInput)和輸出流(createPreviewOutPut、createPhotoOutput),同時創建相關會話管理(createCaptureSession)
3、將輸入流、輸出流添加到會話中,并啟動會話
4、拍照可以直接使用PhotoOutput.capture執行拍照,并將拍照結果保存到媒體
5、在退出相機應用時,需要注意釋放相關的資源。

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

責任編輯:jianghua 來源: 51CTO 開源基礎軟件社區
相關推薦

2023-02-21 16:41:41

分布式相機鴻蒙

2023-02-20 15:38:38

2022-06-20 15:32:55

Stage模型分布式開發

2022-04-24 16:00:03

Ability鴻蒙

2022-04-08 11:08:17

分布式數據接口同步機制

2021-11-10 16:10:18

鴻蒙HarmonyOS應用

2019-10-10 09:16:34

Zookeeper架構分布式

2023-05-29 14:07:00

Zuul網關系統

2017-09-01 05:35:58

分布式計算存儲

2019-06-19 15:40:06

分布式鎖RedisJava

2022-02-17 18:08:04

OpenHarmon應用開發鴻蒙

2022-06-15 16:16:21

分布式數據庫鴻蒙

2021-12-14 10:16:00

鴻蒙HarmonyOS應用

2017-10-27 08:40:44

分布式存儲剪枝系統

2023-10-26 18:10:43

分布式并行技術系統

2022-07-27 14:30:15

分布式數據鴻蒙

2024-03-01 09:53:34

2018-07-17 08:14:22

分布式分布式鎖方位

2023-05-12 08:23:03

分布式系統網絡

2022-06-27 08:21:05

Seata分布式事務微服務
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产综合久久久久久鬼色 | 国产成人免费在线 | 国产福利视频在线观看 | 精品av久久久久电影 | 亚洲一区久久 | 一区二区三区在线 | 国产 欧美 日韩 一区 | 久久国产日本 | 国产精品久久九九 | 亚洲一区二区三区四区五区中文 | 国产亚洲一区二区三区在线观看 | 正在播放国产精品 | 97精品国产一区二区三区 | 男人天堂色 | 欧美日韩高清在线一区 | 精品欧美乱码久久久久久 | 亚洲综合99 | 国产精品欧美一区二区 | 一a一片一级一片啪啪 | 成人性视频免费网站 | 亚洲精品在线视频 | 国产精品成人久久久久 | 亚州无限乱码 | 99久久99久久精品国产片果冰 | 精品美女在线观看视频在线观看 | 一级黄色片在线看 | 久久精品一级 | 天天碰日日操 | 99re6在线| 亚洲美女在线一区 | 亚洲三区视频 | 我想看国产一级毛片 | 粉嫩国产精品一区二区在线观看 | 久久久久久艹 | 人人性人人性碰国产 | www.久久 | 91高清在线观看 | 国产欧美日韩 | 五月天天丁香婷婷在线中 | 国产精品国产 | 免费午夜剧场 |