1/* 2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import camera from '@ohos.multimedia.camera'; 17import deviceInfo from '@ohos.deviceInfo'; 18import fileio from '@ohos.file.fs'; 19import image from '@ohos.multimedia.image'; 20import media from '@ohos.multimedia.media'; 21import userFileManager from '@ohos.filemanagement.userFileManager'; 22import Logger from '../model/Logger'; 23import MediaUtils, { FileType } from '../model/MediaUtils'; 24import { Size } from '@kit.ArkUI'; 25 26const CameraMode = { 27 MODE_PHOTO: 0, // 拍照模式 28 MODE_VIDEO: 1 // 录像模式 29} 30 31const CameraSize = { 32 WIDTH: 1920, 33 HEIGHT: 1080 34} 35 36export default class CameraService { 37 private tag: string = 'CameraService'; 38 private context: any = undefined; 39 private mediaUtil: MediaUtils = undefined; 40 private cameraManager: camera.CameraManager = undefined; 41 private cameras: Array<camera.CameraDevice> = undefined; 42 private cameraId: string = ''; 43 private cameraInput: camera.CameraInput = undefined; 44 private previewOutput: camera.PreviewOutput = undefined; 45 private photoOutPut: camera.PhotoOutput = undefined; 46 private captureSession: camera.CaptureSession = undefined; 47 private mReceiver: image.ImageReceiver = undefined; 48 private photoUri: string = ''; 49 private fileAsset: userFileManager.FileAsset = undefined; 50 private fd: number = -1; 51 private curMode = CameraMode.MODE_PHOTO; 52 private videoRecorder: media.VideoRecorder = undefined; 53 private videoOutput: camera.VideoOutput = undefined; 54 private handleTakePicture: (photoUri: string) => void = undefined; 55 private cameraOutputCapability: camera.CameraOutputCapability = undefined; 56 private previewSize: Size = undefined; 57 private videoConfig: any = { 58 audioSourceType: 1, 59 videoSourceType: 0, 60 profile: { 61 audioBitrate: 48000, 62 audioChannels: 2, 63 audioCodec: 'audio/mp4v-es', 64 audioSampleRate: 48000, 65 durationTime: 1000, 66 fileFormat: 'mp4', 67 videoBitrate: 48000, 68 videoCodec: 'video/mp4v-es', 69 videoFrameWidth: 640, 70 videoFrameHeight: 480, 71 videoFrameRate: 30 72 }, 73 url: '', 74 orientationHint: 0, 75 location: { 76 latitude: 30, longitude: 130 77 }, 78 maxSize: 10000, 79 maxDuration: 10000 80 }; 81 82 constructor(context: any) { 83 this.context = context 84 this.mediaUtil = MediaUtils.getInstance(context) 85 this.mReceiver = image.createImageReceiver(CameraSize.WIDTH, CameraSize.HEIGHT, 4, 8) 86 Logger.info(this.tag, 'createImageReceiver') 87 this.mReceiver.on('imageArrival', () => { 88 Logger.info(this.tag, 'imageArrival') 89 this.mReceiver.readNextImage((err, image) => { 90 Logger.info(this.tag, 'readNextImage') 91 if (err || image === undefined) { 92 Logger.error(this.tag, 'failed to get valid image') 93 return 94 } 95 image.getComponent(4, (errMsg, img) => { 96 Logger.info(this.tag, 'getComponent') 97 if (errMsg || img === undefined) { 98 Logger.info(this.tag, 'failed to get valid buffer') 99 return 100 } 101 let buffer = new ArrayBuffer(4096) 102 if (img.byteBuffer) { 103 buffer = img.byteBuffer 104 } else { 105 Logger.error(this.tag, 'img.byteBuffer is undefined') 106 } 107 this.savePicture(buffer, image) 108 }) 109 }) 110 }) 111 } 112 113 async savePicture(buffer: ArrayBuffer, img: image.Image) { 114 Logger.info(this.tag, 'savePicture') 115 this.fileAsset = await this.mediaUtil.createAndGetUri(FileType.IMAGE) 116 this.photoUri = this.fileAsset.uri 117 Logger.info(this.tag, `this.photoUri = ${this.photoUri}`) 118 this.fd = await this.mediaUtil.getFdPath(this.fileAsset) 119 Logger.info(this.tag, `this.fd = ${this.fd}`) 120 await fileio.write(this.fd, buffer) 121 await this.fileAsset.close(this.fd) 122 await img.release() 123 Logger.info(this.tag, 'save image done') 124 if (this.handleTakePicture) { 125 this.handleTakePicture(this.photoUri) 126 } 127 } 128 129 async initCamera(surfaceId: string) { 130 Logger.info(this.tag, 'initCamera'); 131 await this.releaseCamera(); 132 Logger.info(this.tag, `deviceInfo.deviceType = ${deviceInfo.deviceType}`); 133 if (deviceInfo.deviceType === 'default') { 134 this.videoConfig.videoSourceType = 1; 135 } else { 136 this.videoConfig.videoSourceType = 0; 137 } 138 this.cameraManager = camera.getCameraManager(this.context); 139 Logger.info(this.tag, 'getCameraManager'); 140 this.cameras = this.cameraManager.getSupportedCameras(); 141 Logger.info(this.tag, `get cameras ${this.cameras.length}`); 142 if (this.cameras.length === 0) { 143 Logger.info(this.tag, 'cannot get cameras'); 144 return; 145 } 146 147 let cameraDevice = this.cameras[0]; 148 this.cameraInput = this.cameraManager.createCameraInput(cameraDevice); 149 this.cameraInput.open(); 150 Logger.info(this.tag, 'createCameraInput'); 151 this.cameraOutputCapability = this.cameraManager.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_VIDEO); 152 let previewProfile = this.cameraOutputCapability.previewProfiles[0]; 153 this.previewSize = previewProfile.size; 154 this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId); 155 Logger.info(this.tag, 'createPreviewOutput'); 156 let mSurfaceId = await this.mReceiver.getReceivingSurfaceId(); 157 let profile = this.cameraOutputCapability.photoProfiles[0]; 158 this.photoOutPut = this.cameraManager.createPhotoOutput(profile, mSurfaceId); 159 this.captureSession = this.cameraManager.createCaptureSession(); 160 Logger.info(this.tag, 'createCaptureSession'); 161 this.captureSession.beginConfig(); 162 Logger.info(this.tag, 'beginConfig'); 163 this.captureSession.addInput(this.cameraInput); 164 this.captureSession.addOutput(this.previewOutput); 165 this.captureSession.addOutput(this.photoOutPut); 166 await this.captureSession.commitConfig(); 167 await this.captureSession.start(); 168 Logger.info(this.tag, 'captureSession start'); 169 } 170 171 setTakePictureCallback(callback) { 172 this.handleTakePicture = callback 173 } 174 175 async takePicture() { 176 Logger.info(this.tag, 'takePicture') 177 if (this.curMode === CameraMode.MODE_VIDEO) { 178 this.curMode = CameraMode.MODE_PHOTO 179 } 180 let photoSettings = { 181 rotation: camera.ImageRotation.ROTATION_0, 182 quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM, 183 location: { // 位置信息,经纬度 184 latitude: 12.9698, 185 longitude: 77.7500, 186 altitude: 1000 187 }, 188 mirror: false 189 } 190 await this.photoOutPut.capture(photoSettings) 191 Logger.info(this.tag, 'takePicture done') 192 } 193 194 async startVideo() { 195 Logger.info(this.tag, 'startVideo begin'); 196 await this.captureSession.stop(); 197 this.captureSession.beginConfig(); 198 if (this.curMode === CameraMode.MODE_PHOTO) { 199 this.curMode = CameraMode.MODE_VIDEO; 200 if (this.photoOutPut) { 201 try { 202 this.captureSession.removeOutput(this.photoOutPut); 203 this.photoOutPut.release(); 204 } catch (err) { 205 Logger.error(this.tag, 'remove photoOutPut error: ' + JSON.stringify(err)); 206 } 207 } 208 } else { 209 if (this.videoOutput) { 210 try { 211 this.captureSession.removeOutput(this.videoOutput); 212 } catch (err) { 213 Logger.error(this.tag, 'remove videoOutput error: ' + JSON.stringify(err)); 214 } 215 } 216 } 217 if (this.videoOutput) { 218 try { 219 this.captureSession.removeOutput(this.videoOutput); 220 await this.videoOutput.release(); 221 } catch (err) { 222 Logger.error(this.tag, 'remove videoOutput error: ' + JSON.stringify(err)); 223 } 224 } 225 this.fileAsset = await this.mediaUtil.createAndGetUri(FileType.VIDEO); 226 this.fd = await this.mediaUtil.getFdPath(this.fileAsset); 227 this.videoRecorder = await media.createVideoRecorder(); 228 this.videoConfig.url = `fd://${this.fd}`; 229 this.videoConfig.profile.videoFrameWidth = this.previewSize.width; 230 this.videoConfig.profile.videoFrameHeight = this.previewSize.height; 231 await this.videoRecorder.prepare(this.videoConfig); 232 let videoId = await this.videoRecorder.getInputSurface(); 233 let profile = this.cameraOutputCapability.videoProfiles[0]; 234 this.videoOutput = this.cameraManager.createVideoOutput(profile, videoId); 235 this.captureSession.addOutput(this.videoOutput); 236 try { 237 await this.captureSession.commitConfig(); 238 await this.captureSession.start(); 239 await this.videoOutput.start(); 240 await this.videoRecorder.start(); 241 } catch (err) { 242 Logger.error(this.tag, 'record start error: ' + JSON.stringify(err)); 243 } 244 Logger.info(this.tag, 'startVideo end'); 245 } 246 247 async stopVideo() { 248 Logger.info(this.tag, 'stopVideo called'); 249 try { 250 await this.videoRecorder.stop(); 251 await this.videoOutput.stop(); 252 await this.videoRecorder.release(); 253 await this.fileAsset.close(this.fd); 254 } catch (err) { 255 Logger.error(this.tag, 'stopVideo error: ' + JSON.stringify(err)); 256 } 257 } 258 259 async releaseCamera() { 260 Logger.info(this.tag, 'releaseCamera'); 261 try { 262 if (this.cameraInput) { 263 await this.cameraInput.close(); 264 } 265 if (this.previewOutput) { 266 await this.previewOutput.release(); 267 } 268 if (this.photoOutPut) { 269 await this.photoOutPut.release(); 270 } 271 if (this.videoOutput) { 272 await this.videoOutput.release(); 273 } 274 if (this.captureSession) { 275 await this.captureSession.release(); 276 } 277 } catch (err) { 278 Logger.error(this.tag, 'releaseCamera error: ' + JSON.stringify(err)); 279 } 280 } 281}