1/* 2 * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 photoAccessHelper from '@ohos.file.photoAccessHelper'; 22import Logger from '../utlis/Logger'; 23import MediaModel from './MediaModel'; 24 25 26const FOUR = 4; // format 27const EIGHT = 8; // capacity 28const FOUR_THOUSAND_AND_SIXTY_NINE = 4096; // buffer大小 29 30const cameraMode = { 31 modePhoto: 0, // 拍照模式 32 modeVideo: 1, // 录像模式 33}; 34 35const cameraWH = { 36 width: 480, 37 height: 360, 38}; 39 40/** 41 * 分辨率 42 */ 43export enum VideoFrame { 44 VIDEOFRAME_1920_1080, 45 VIDEOFRAME_1280_720, 46 VIDEOFRAME_800_600, 47}; 48 49type VideoFrameWH = { 50 width: number; 51 height: number; 52}; 53 54const TAG = '[CameraModel]'; 55 56export default class CameraService { 57 private mediaModel: MediaModel = undefined; 58 private fileAsset: photoAccessHelper.PhotoAsset | undefined = undefined; 59 private cameraMgr: camera.CameraManager = undefined; 60 private camerasArray: Array<camera.CameraDevice> = undefined; 61 private cameraInput: camera.CameraInput = undefined; 62 private previewOutput: camera.PreviewOutput = undefined; 63 private photoOutPut: camera.PhotoOutput = undefined; 64 private capSession: camera.CaptureSession = undefined; 65 private videoOutput: camera.VideoOutput = undefined; 66 private capability: camera.CameraOutputCapability = undefined; 67 68 private avRecorder: media.AVRecorder = undefined; 69 private receiver: image.ImageReceiver = undefined; 70 private photoPath: string = ''; 71 private fd: number = -1; 72 private takePictureHandle: (photoUri: string) => void = undefined; 73 private currentMode:number = cameraMode.modePhoto; 74 75 private videoFrameWH: VideoFrameWH = { 76 width: 480, 77 height: 360, 78 }; // 视频分辨率 79 private imageRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0; // 图片旋转角度 80 81 private videoProfile: media.VideoRecorderProfile = { 82 audioChannels: 2, 83 audioCodec: media.CodecMimeType.AUDIO_AAC, 84 audioBitrate: 48000, 85 audioSampleRate: 48000, 86 fileFormat: media.ContainerFormatType.CFT_MPEG_4, 87 videoBitrate: 48000, 88 videoCodec: media.CodecMimeType.VIDEO_AVC, 89 videoFrameWidth: 480, 90 videoFrameHeight: 360, 91 videoFrameRate: 30, 92 }; 93 private videoSourceType: number = 0; 94 95 constructor() { 96 this.mediaModel = MediaModel.getMediaInstance(); 97 this.receiver = image.createImageReceiver( 98 cameraWH.width, 99 cameraWH.height, 100 FOUR, 101 EIGHT 102 ); 103 Logger.info(TAG, 'createImageReceiver'); 104 this.receiver.on('imageArrival', () => { 105 Logger.info(TAG, 'imageArrival'); 106 this.receiver.readNextImage((err, image) => { 107 Logger.info(TAG, 'readNextImage'); 108 if (err || image === undefined) { 109 Logger.error(TAG, 'failed to get valid image'); 110 return; 111 } 112 image.getComponent(FOUR, (errMsg, img) => { 113 Logger.info(TAG, 'getComponent'); 114 if (errMsg || img === undefined) { 115 Logger.info(TAG, 'failed to get valid buffer'); 116 return; 117 } 118 let buffer = new ArrayBuffer(FOUR_THOUSAND_AND_SIXTY_NINE); 119 if (img.byteBuffer) { 120 buffer = img.byteBuffer; 121 } else { 122 Logger.error(TAG, 'img.byteBuffer is undefined'); 123 } 124 this.saveImage(buffer, image); 125 }); 126 }); 127 }); 128 } 129 130 /** 131 * 设置分辨率 132 * @param videoFrame 133 */ 134 setVideoFrameWH(videoFrame: VideoFrame): void { 135 switch (videoFrame) { 136 case VideoFrame.VIDEOFRAME_800_600: 137 this.videoFrameWH = { 138 width: 800, 139 height: 600, 140 }; 141 Logger.info( 142 TAG, 143 `setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}` 144 ); 145 break; 146 case VideoFrame.VIDEOFRAME_1280_720: 147 this.videoFrameWH = { 148 width: 1280, 149 height: 720, 150 }; 151 Logger.info( 152 TAG, 153 `setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}` 154 ); 155 break; 156 case VideoFrame.VIDEOFRAME_1920_1080: 157 this.videoFrameWH = { 158 width: 1920, 159 height: 1080, 160 }; 161 Logger.info( 162 TAG, 163 `setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}` 164 ); 165 break; 166 } 167 } 168 /** 169 * 设置图片旋转角度 170 * @param imageRotation 171 */ 172 setImageRotation(imageRotation: camera.ImageRotation): void { 173 this.imageRotation = imageRotation; 174 } 175 176 /** 177 * 保存图片 178 * @param buffer 179 * @param img 180 */ 181 async saveImage(buffer: ArrayBuffer, img: image.Image): Promise<void> { 182 Logger.info(TAG, 'savePicture'); 183 this.fileAsset = await this.mediaModel.createAndGetUri(photoAccessHelper.PhotoType.IMAGE); 184 this.photoPath = this.fileAsset.uri; 185 Logger.info(TAG, `this.photoUri = ${this.photoPath}`); 186 this.fd = await this.mediaModel.getFdPath(this.fileAsset); 187 Logger.info(TAG, `this.fd = ${this.fd}`); 188 await fileIo.write(this.fd, buffer); 189 await this.fileAsset.close(this.fd); 190 await img.release(); 191 Logger.info(TAG, 'save image done'); 192 if (this.takePictureHandle) { 193 this.takePictureHandle(this.photoPath); 194 } 195 } 196 197 /** 198 * 初始化相机 199 * @param surfaceId 200 */ 201 async initCamera(surfaceId: string): Promise<void> { 202 Logger.info(TAG, `initCamera surfaceId:${surfaceId}`); 203 await this.cameraRelease(); 204 Logger.info(TAG, `deviceInfo.deviceType = ${deviceInfo.deviceType}`); 205 if (deviceInfo.deviceType === 'default') { 206 Logger.info(TAG, `deviceInfo.deviceType default 1 = ${deviceInfo.deviceType}`); 207 this.videoSourceType = 1; 208 Logger.info(TAG, `deviceInfo.deviceType default 2 = ${deviceInfo.deviceType}`); 209 } else { 210 Logger.info(TAG, `deviceInfo.deviceType other 1 = ${deviceInfo.deviceType}`); 211 this.videoSourceType = 0; 212 Logger.info(TAG, `deviceInfo.deviceType other 2 = ${deviceInfo.deviceType}`); 213 } 214 Logger.info(TAG, 'getCameraManager begin'); 215 try { 216 Logger.info(TAG, 'getCameraManager try begin'); 217 this.cameraMgr = camera.getCameraManager(globalThis.cameraContext); 218 Logger.info(TAG, 'getCameraManager try end'); 219 } catch (e) { 220 Logger.info(TAG, `getCameraManager catch e:${JSON.stringify(e)}`); 221 Logger.info(TAG, `getCameraManager catch code:${JSON.stringify(e.code)}`); 222 Logger.info(TAG, `getCameraManager catch message:${JSON.stringify(e.message)}`); 223 } 224 Logger.info(TAG, 'getCameraManager end'); 225 Logger.info(TAG, `getCameraManager ${JSON.stringify(this.cameraMgr)}`); 226 this.camerasArray = this.cameraMgr.getSupportedCameras(); 227 Logger.info(TAG, `get cameras ${this.camerasArray.length}`); 228 if (this.camerasArray.length === 0) { 229 Logger.info(TAG, 'cannot get cameras'); 230 return; 231 } 232 233 let mCamera = this.camerasArray[0]; 234 this.cameraInput = this.cameraMgr.createCameraInput(mCamera); 235 this.cameraInput.open(); 236 Logger.info(TAG, 'createCameraInput'); 237 this.capability = this.cameraMgr.getSupportedOutputCapability(mCamera); 238 let previewProfile = this.capability.previewProfiles[0]; 239 this.previewOutput = this.cameraMgr.createPreviewOutput( 240 previewProfile, 241 surfaceId 242 ); 243 Logger.info(TAG, 'createPreviewOutput'); 244 let rSurfaceId = await this.receiver.getReceivingSurfaceId(); 245 let photoProfile = this.capability.photoProfiles[0]; 246 this.photoOutPut = this.cameraMgr.createPhotoOutput( 247 photoProfile, 248 rSurfaceId 249 ); 250 this.capSession = this.cameraMgr.createCaptureSession(); 251 Logger.info(TAG, 'createCaptureSession'); 252 this.capSession.beginConfig(); 253 Logger.info(TAG, 'beginConfig'); 254 this.capSession.addInput(this.cameraInput); 255 this.capSession.addOutput(this.previewOutput); 256 this.capSession.addOutput(this.photoOutPut); 257 await this.capSession.commitConfig(); 258 await this.capSession.start(); 259 Logger.info(TAG, 'captureSession start'); 260 } 261 262 setTakePictureHandleCallback(callback): void { 263 this.takePictureHandle = callback; 264 } 265 266 /** 267 * 拍照 268 */ 269 async takePicture(): Promise<void> { 270 Logger.info(TAG, 'takePicture'); 271 if (this.currentMode === cameraMode.modeVideo) { 272 this.currentMode = cameraMode.modePhoto; 273 } 274 Logger.info(TAG, `takePicture imageRotation:${this.imageRotation}`); 275 let photoSettings = { 276 rotation: this.imageRotation, 277 quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM, 278 location: { 279 // 位置信息,经纬度 280 latitude: 12.9698, 281 longitude: 77.75, 282 altitude: 1000, 283 }, 284 mirror: false, 285 }; 286 await this.photoOutPut.capture(photoSettings); 287 Logger.info(TAG, 'takePicture done'); 288 } 289 290 /** 291 * 开始录像 292 */ 293 async startVideo(): Promise<void> { 294 Logger.info(TAG, 'startVideo begin'); 295 Logger.info(TAG, 'startVideo 1'); 296 await this.capSession.stop(); 297 Logger.info(TAG, 'startVideo 2'); 298 this.capSession.beginConfig(); 299 Logger.info(TAG, 'startVideo 3'); 300 if (this.currentMode === cameraMode.modePhoto) { 301 this.currentMode = cameraMode.modeVideo; 302 if (this.photoOutPut) { 303 this.capSession.removeOutput(this.photoOutPut); 304 this.photoOutPut.release(); 305 } 306 } else { 307 if (this.videoOutput) { 308 try { 309 Logger.info(TAG, 'startVideo 4'); 310 this.capSession.removeOutput(this.videoOutput); 311 Logger.info(TAG, 'startVideo 5'); 312 } catch (e) { 313 Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`); 314 Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`); 315 Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`); 316 } 317 } 318 } 319 if (this.videoOutput) { 320 try { 321 Logger.info(TAG, 'startVideo 6'); 322 this.capSession.removeOutput(this.videoOutput); 323 Logger.info(TAG, 'startVideo 7'); 324 } catch (e) { 325 Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`); 326 Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`); 327 Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`); 328 } 329 try { 330 Logger.info(TAG, 'startVideo release 1'); 331 await this.videoOutput.release(); 332 Logger.info(TAG, 'startVideo release 2'); 333 } catch (e) { 334 Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`); 335 Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`); 336 Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`); 337 } 338 } 339 Logger.info(TAG, 'startVideo 8'); 340 this.fileAsset = await this.mediaModel.createAndGetUri(photoAccessHelper.PhotoType.VIDEO); 341 Logger.info(TAG, `startVideo fileAsset:${this.fileAsset}`); 342 this.fd = await this.mediaModel.getFdPath(this.fileAsset); 343 Logger.info(TAG, `startVideo fd:${this.fd}`); 344 media.createAVRecorder(async (error, recorder) => { 345 Logger.info(TAG, `startVideo into createAVRecorder:${recorder}`); 346 if (recorder != null) { 347 Logger.info(TAG, `startVideo into recorder:${recorder}`); 348 this.avRecorder = recorder; 349 Logger.info(TAG, `startVideo createAVRecorder success:${this.avRecorder}`); 350 // 当前录像配置 351 let currConfig = { 352 audioSourceType: 1, 353 videoSourceType: this.videoSourceType, 354 profile: this.videoProfile, 355 url: `fd://${this.fd}`, 356 rotation: 0 357 }; 358 Logger.info(TAG, `startVideo into recorder:${recorder}`); 359 await this.avRecorder.prepare(currConfig); 360 Logger.info(TAG, `startVideo videoConfig:${JSON.stringify(currConfig)}`); 361 let videoId = await this.avRecorder.getInputSurface(); 362 let videoProfile = this.capability.videoProfiles[0]; 363 Logger.info(TAG, `startVideo capability.videoProfiles[]=: ${JSON.stringify(this.capability.videoProfiles)}`); 364 Logger.info(TAG, `startVideo videoProfile:${JSON.stringify(videoProfile)}`); 365 this.videoOutput = this.cameraMgr.createVideoOutput(videoProfile, videoId); 366 Logger.info(TAG, `startVideo videoOutput:${this.videoOutput}`); 367 this.capSession.addOutput(this.videoOutput); 368 Logger.info(TAG, 'startVideo addOutput'); 369 await this.capSession.commitConfig(); 370 Logger.info(TAG, 'startVideo commitConfig'); 371 await this.capSession.start(); 372 Logger.info(TAG, 'startVideo commitConfig capSession'); 373 await this.videoOutput.start(); 374 Logger.info(TAG, 'startVideo commitConfig videoOutput'); 375 try { 376 Logger.info(TAG, 'startVideo avRecorder.start 1'); 377 await this.avRecorder.start(); 378 Logger.info(TAG, 'startVideo avRecorder.start 2'); 379 } catch (e) { 380 Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`); 381 } 382 383 Logger.info(TAG, 'startVideo end'); 384 385 } else { 386 Logger.info(TAG, `startVideo createAVRecorder fail, error:${error}`); 387 } 388 }); 389 } 390 391 /** 392 * 停止录像 393 */ 394 async stopVideo(): Promise<void> { 395 Logger.info(TAG, 'stopVideo called'); 396 await this.avRecorder.stop(); 397 await this.avRecorder.release(); 398 await this.videoOutput.stop(); 399 await this.fileAsset.close(this.fd); 400 } 401 402 /** 403 * 资源释放 404 */ 405 async cameraRelease(): Promise<void> { 406 Logger.info(TAG, 'releaseCamera'); 407 if (this.cameraInput) { 408 await this.cameraInput.close(); 409 } 410 if (this.previewOutput) { 411 await this.previewOutput.release(); 412 } 413 if (this.photoOutPut) { 414 await this.photoOutPut.release(); 415 } 416 if (this.videoOutput) { 417 await this.videoOutput.release(); 418 } 419 if (this.capSession) { 420 await this.capSession.release(); 421 } 422 } 423} 424