相机开发指导


相机开发指导

场景介绍

OpenHarmony相机模块支持相机业务的开发,开发者可以通过已开放的接口实现相机硬件的访问、操作和新功能开发,最常见的操作如:预览、拍照和录像等。开发者也可以通过合适的接口或者接口组合实现闪光灯控制、曝光时间控制、手动对焦和自动对焦控制、变焦控制以及更多的功能。

开发者在调用Camera能力时,需要了解Camera的一些基本概念:

  • 相机静态能力:用于描述相机的固有能力的一系列参数,比如朝向、支持的分辨率等信息。
  • 物理相机:物理相机就是独立的实体摄像头设备。物理相机ID是用于标志每个物理摄像头的唯一字串。
  • 异步操作:为保证UI线程不被阻塞,部分Camera接口采用异步调用方式。异步方式API均提供了callback函数和Promise函数。

开发步骤

接口说明

详细API含义请参考:相机管理API文档

全流程场景

包含流程:权限申请、创建实例、参数设置、会话管理、拍照、录像、释放资源等。

权限申请

在使用相机之前,需要申请相机的相关权限,保证应用拥有相机硬件及其他功能权限,应用权限的介绍请参考权限章节,相机涉及权限如下表。

权限名称 权限属性值
相机权限 ohos.permission.CAMERA
录音权限 ohos.permission.MICROPHONE
存储权限 ohos.permission.WRITE_MEDIA
读取权限 ohos.permission.READ_MEDIA
位置权限 ohos.permission.MEDIA_LOCATION

参考代码如下:

const PERMISSIONS: Array<string> = [
    'ohos.permission.CAMERA',
    'ohos.permission.MICROPHONE',
    'ohos.permission.MEDIA_LOCATION',
    'ohos.permission.READ_MEDIA',
    'ohos.permission.WRITE_MEDIA'
]

function applyPermission() {
        console.info('[permission] get permission');
        globalThis.abilityContext.requestPermissionFromUser(PERMISSIONS)
    }
1
2
3
4
5
6
7
8
9
10
11
12

创建实例

在实现一个相机应用之前必须先创建一个独立的相机设备,然后才能继续相机的其他操作。如果此步骤操作失败,相机可能被占用或无法使用。如果被占用,必须等到相机释放后才能重新获取CameraManager对象。通过getSupportedCameras() 方法,获取当前使用的设备支持的相机列表。相机列表中存储了当前设备拥有的所有相机ID,如果列表不为空,则列表中的每个ID都支持独立创建相机对象;否则,说明正在使用的设备无可用的相机,不能继续后续的操作。相机设备具备预览、拍照、录像、Metadata等输出流,需要通过getSupportedOutputCapability()接口获取各个输出流的具体能力,通过该接口,可以获取当前设备支持的所有输出流能力,分别在CameraOutputCapability中的各个profile字段中,相机设备创建的建议步骤如下:

import camera from '@ohos.multimedia.camera'
import image from '@ohos.multimedia.image'
import media from '@ohos.multimedia.media'

// 创建CameraManager对象
context: any = getContext(this)
let cameraManager = camera.getCameraManager(this.context)
if (!cameraManager) {
    console.error("camera.getCameraManager error")
    return;
} 
// 监听相机状态变化
cameraManager.on('cameraStatus', (cameraStatusInfo) => {
    console.log(`camera : ${cameraStatusInfo.camera.cameraId}`);
    console.log(`status: ${cameraStatusInfo.status}`);
})

// 获取相机列表
let cameraArray = cameraManager.getSupportedCameras();
if (cameraArray.length <= 0) {
    console.error("cameraManager.getSupportedCameras error")
    return;
} 

for (let index = 0; index < cameraArray.length; index++) {
    console.log('cameraId : ' + cameraArray[index].cameraId);                        // 获取相机ID
    console.log('cameraPosition : ' + cameraArray[index].cameraPosition);              // 获取相机位置
    console.log('cameraType : ' + cameraArray[index].cameraType);                      // 获取相机类型
    console.log('connectionType : ' + cameraArray[index].connectionType);              // 获取相机连接类型
}

// 创建相机输入流
let cameraInput
try {
    cameraInput = cameraManager.createCameraInput(cameraArray[0]);
} catch () {
   console.error('Failed to createCameraInput errorCode = ' + error.code);
}

// 监听cameraInput错误信息
let cameraDevice = cameraArray[0];
cameraInput.on('error', cameraDevice, (error) => {
    console.log(`Camera input error code: ${error.code}`);
})

// 打开相机
await cameraInput.open();

// 获取相机设备支持的输出流能力
let cameraOutputCap = cameraManager.getSupportedOutputCapability(cameraArray[0]);
if (!cameraOutputCap) {
    console.error("cameraManager.getSupportedOutputCapability error")
    return;
}
console.info("outputCapability: " + JSON.stringify(cameraOutputCap));

let previewProfilesArray = cameraOutputCap.previewProfiles;
if (!previewProfilesArray) {
    console.error("createOutput previewProfilesArray == null || undefined")
} 

let photoProfilesArray = cameraOutputCap.photoProfiles;
if (!photoProfilesArray) {
    console.error("createOutput photoProfilesArray == null || undefined")
} 

let videoProfilesArray = cameraOutputCap.videoProfiles;
if (!videoProfilesArray) {
    console.error("createOutput videoProfilesArray == null || undefined")
} 

let metadataObjectTypesArray = cameraOutputCap.supportedMetadataObjectTypes;
if (!metadataObjectTypesArray) {
    console.error("createOutput metadataObjectTypesArray == null || undefined")
}

// 创建预览输出流,其中参数 surfaceId 参考下面 XComponent 组件,预览流为XComponent组件提供的surface
let previewOutput
try {
    previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId)
} catch (error) {
    console.error("Failed to create the PreviewOutput instance.")
}

// 监听预览输出错误信息
previewOutput.on('error', (error) => {
    console.log(`Preview output error code: ${error.code}`);
})

// 创建ImageReceiver对象,并设置照片参数:分辨率大小是根据前面 photoProfilesArray 获取的当前设备所支持的拍照分辨率大小去设置
let imageReceiver = await image.createImageReceiver(1920, 1080, 4, 8)
// 获取照片显示SurfaceId
let photoSurfaceId = await imageReceiver.getReceivingSurfaceId()
// 创建拍照输出流
let photoOutput
try {
    photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0], photoSurfaceId)
} catch (error) {
   console.error('Failed to createPhotoOutput errorCode = ' + error.code);
}

// 创建视频录制的参数
let videoConfig = {
    audioSourceType: 1,
    videoSourceType: 1,
    profile: {
        audioBitrate: 48000,
        audioChannels: 2,
        audioCodec: 'audio/mp4v-es',
        audioSampleRate: 48000,
        durationTime: 1000,
        fileFormat: 'mp4',
        videoBitrate: 48000,
        videoCodec: 'video/mp4v-es',
        videoFrameWidth: 640,
        videoFrameHeight: 480,
        videoFrameRate: 30
    },
    url: 'file:///data/media/01.mp4',
    orientationHint: 0,
    maxSize: 100,
    maxDuration: 500,
    rotation: 0
}

// 创建录像输出流
let videoRecorder
media.createVideoRecorder().then((recorder) => {
    console.log('createVideoRecorder called')
    videoRecorder = recorder
})
// 设置视频录制的参数
videoRecorder.prepare(videoConfig)
//获取录像SurfaceId
let videoSurfaceId
videoRecorder.getInputSurface().then((id) => {
    console.log('getInputSurface called')
    videoSurfaceId = id
})

// 创建VideoOutput对象
let videoOutput
try {
    videoOutput = cameraManager.createVideoOutput(videoProfilesArray[0], videoSurfaceId)
} catch (error) {
    console.error('Failed to create the videoOutput instance. errorCode = ' + error.code);
}

// 监听视频输出错误信息
videoOutput.on('error', (error) => {
    console.log(`Preview output error code: ${error.code}`);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

预览流、拍照流和录像流的输入均需要提前创建surface,其中预览流为XComponent组件提供的surface,拍照流为ImageReceiver提供的surface,录像流为VideoRecorder的surface。

XComponent

mXComponentController: XComponentController = new XComponentController                   // 创建XComponentController

build() {
    Flex() {
        XComponent({                                                                     // 创建XComponent
            id: '',
            type: 'surface',
            libraryname: '',
            controller: this.mXComponentController
        })
        .onload(() => {                                                                  // 设置onload回调
            // 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置
            this.mXComponentController.setXComponentSurfaceSize({surfaceWidth:1920,surfaceHeight:1080})
            // 获取Surface ID
            globalThis.surfaceId = mXComponentController.getXComponentSurfaceId()
        })
        .width('1920px')                                                                 // 设置XComponent宽度
        .height('1080px')                                                                // 设置XComponent高度
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

ImageReceiver

function getImageReceiverSurfaceId() {
    let receiver = image.createImageReceiver(640, 480, 4, 8)
    console.log(TAG + 'before ImageReceiver check')
    if (receiver !== undefined) {
      console.log('ImageReceiver is ok')
      surfaceId1 = receiver.getReceivingSurfaceId()
      console.log('ImageReceived id: ' + JSON.stringify(surfaceId1))
    } else {
      console.log('ImageReceiver is not ok')
    }
  }
1
2
3
4
5
6
7
8
9
10
11

VideoRecorder

function getVideoRecorderSurface() {
        await getFd('CameraManager.mp4');
        mVideoConfig.url = mFdPath;
        media.createVideoRecorder((err, recorder) => {
            console.info('Entering create video receiver')
            mVideoRecorder = recorder
            console.info('videoRecorder is :' + JSON.stringify(mVideoRecorder))
            console.info('videoRecorder.prepare called.')
            mVideoRecorder.prepare(mVideoConfig, (err) => {
                console.info('videoRecorder.prepare success.')
                mVideoRecorder.getInputSurface((err, id) => {
                    console.info('getInputSurface called')
                    mVideoSurface = id
                    console.info('getInputSurface surfaceId: ' + JSON.stringify(mVideoSurface))
                })
            })
        })
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

会话管理

创建会话
//创建会话
let captureSession
try {
    captureSession = cameraManager.createCaptureSession()
} catch (error) {
    console.error('Failed to create the CaptureSession instance. errorCode = ' + error.code);
}

// 监听session错误信息
captureSession.on('error', (error) => {
    console.log(`Capture session error code: ${error.code}`);
})

// 开始配置会话
try {
    captureSession.beginConfig()
} catch (error) {
    console.error('Failed to beginConfig. errorCode = ' + error.code);
}

// 向会话中添加相机输入流
try {
    captureSession.addInput(cameraInput)
} catch (error) {
    console.error('Failed to addInput. errorCode = ' + error.code);
}

// 向会话中添加预览输入流
try {
    captureSession.addOutput(previewOutput)
} catch (error) {
    console.error('Failed to addOutput(previewOutput). errorCode = ' + error.code);
}

// 向会话中添加拍照输出流
try {
    captureSession.addOutput(photoOutput)
} catch (error) {
    console.error('Failed to addOutput(photoOutput). errorCode = ' + error.code);
}

// 提交会话配置
await captureSession.commitConfig()

// 启动会话
await captureSession.start().then(() => {
    console.log('Promise returned to indicate the session start success.');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
切换会话
// 停止当前会话
await captureSession.stop()

// 开始配置会话
try {
    captureSession.beginConfig()
} catch (error) {
    console.error('Failed to beginConfig. errorCode = ' + error.code);
}

// 从会话中移除拍照输出流
try {
    captureSession.removeOutput(photoOutput)
} catch (error) {
    console.error('Failed to removeOutput(photoOutput). errorCode = ' + error.code);
}

// 向会话中添加录像输出流
try {
    captureSession.addOutput(videoOutput)
} catch (error) {
    console.error('Failed to addOutput(videoOutput). errorCode = ' + error.code);
}

// 提交会话配置
await captureSession.commitConfig()

// 启动会话
await captureSession.start().then(() => {
    console.log('Promise returned to indicate the session start success.');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

参数设置

// 判断设备是否支持闪光灯
let flashStatus
try {
    flashStatus = captureSession.hasFlash()
} catch (error) {
    console.error('Failed to hasFlash. errorCode = ' + error.code);
}
console.log('Promise returned with the flash light support status:' + flashStatus);

if (flashStatus) {
    // 判断是否支持自动闪光灯模式
    let flashModeStatus
    try {
        let status = captureSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO)
        flashModeStatus = status
    } catch (error) {
        console.error('Failed to check whether the flash mode is supported. errorCode = ' + error.code);
    }
    if(flashModeStatus) {
        // 设置自动闪光灯模式
        try {
            captureSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO)
        } catch (error) {
            console.error('Failed to set the flash mode. errorCode = ' + error.code);
        }
    }
}

// 判断是否支持连续自动变焦模式
let focusModeStatus
try {
    let status = captureSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO)
    focusModeStatus = status
} catch (error) {
    console.error('Failed to check whether the focus mode is supported. errorCode = ' + error.code);
}

if (focusModeStatus) {
    // 设置连续自动变焦模式
    try {
        captureSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO)
    } catch (error) {
        console.error('Failed to set the focus mode. errorCode = ' + error.code);
    }
}

// 获取相机支持的可变焦距比范围
let zoomRatioRange
try {
    zoomRatioRange = captureSession.getZoomRatioRange()
} catch (error) {
    console.error('Failed to get the zoom ratio range. errorCode = ' + error.code);
}

// 设置可变焦距比
try {
    captureSession.setZoomRatio(zoomRatioRange[0])
} catch (error) {
    console.error('Failed to set the zoom ratio value. errorCode = ' + error.code);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

拍照

let settings = {
    quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,                                     // 设置图片质量高
    rotation: camera.ImageRotation.ROTATION_0                                            // 设置图片旋转角度0
}
// 使用当前拍照设置进行拍照
photoOutput.capture(settings, async (err) => {
    if (err) {
        console.error('Failed to capture the photo ${err.message}');
        return;
    }
    console.log('Callback invoked to indicate the photo capture request success.');
});
1
2
3
4
5
6
7
8
9
10
11
12

录像

// 启动录像输出流
videoOutput.start(async (err) => {
    if (err) {
        console.error('Failed to start the video output ${err.message}');
        return;
    }
    console.log('Callback invoked to indicate the video output start success.');
});

// 开始录像
videoRecorder.start().then(() => {
    console.info('videoRecorder start success');
}

// 停止录像
videoRecorder.stop().then(() => {
    console.info('stop success');
}

// 停止录像输出流
videoOutput.stop((err) => {
    if (err) {
        console.error('Failed to stop the video output ${err.message}');
        return;
    }
    console.log('Callback invoked to indicate the video output stop success.');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

拍照保存接口可参考:图片处理API文档

释放资源

// 停止当前会话
captureSession.stop()

// 释放相机输入流
cameraInput.close()

// 释放预览输出流
previewOutput.release()

// 释放拍照输出流
photoOutput.release()

// 释放录像输出流
videoOutput.release()

// 释放会话
captureSession.release()

// 会话置空
captureSession = null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

流程图

应用使用相机的流程示意图如下 camera_framework process