425 lines
14 KiB
Plaintext
425 lines
14 KiB
Plaintext
/*
|
|
* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import camera from '@ohos.multimedia.camera';
|
|
import deviceInfo from '@ohos.deviceInfo';
|
|
import fileIo from '@ohos.file.fs';
|
|
import image from '@ohos.multimedia.image';
|
|
import media from '@ohos.multimedia.media';
|
|
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
|
|
import Logger from '../utils/Logger';
|
|
import MediaModel from './MediaModel';
|
|
|
|
|
|
const FOUR = 4; // format
|
|
const EIGHT = 8; // capacity
|
|
const FOUR_THOUSAND_AND_SIXTY_NINE = 4096; // buffer大小
|
|
|
|
const cameraMode = {
|
|
modePhoto: 0, // 拍照模式
|
|
modeVideo: 1, // 录像模式
|
|
};
|
|
|
|
const cameraWH = {
|
|
width: 480,
|
|
height: 360,
|
|
};
|
|
|
|
/**
|
|
* 分辨率
|
|
*/
|
|
export enum VideoFrame {
|
|
VIDEOFRAME_1920_1080,
|
|
VIDEOFRAME_1280_720,
|
|
VIDEOFRAME_800_600,
|
|
};
|
|
|
|
type VideoFrameWH = {
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
const TAG = '[CameraModel]';
|
|
|
|
export default class CameraService {
|
|
private mediaModel: MediaModel = undefined;
|
|
private fileAsset: mediaLibrary.FileAsset = undefined;
|
|
private cameraMgr: camera.CameraManager
|
|
private camerasArray: Array<camera.CameraDevice> = undefined;
|
|
private cameraInput: camera.CameraInput = undefined;
|
|
private previewOutput: camera.PreviewOutput = undefined;
|
|
private photoOutPut: camera.PhotoOutput = undefined;
|
|
private capSession: camera.CaptureSession = undefined;
|
|
private videoOutput: camera.VideoOutput = undefined;
|
|
private capability: camera.CameraOutputCapability = undefined;
|
|
|
|
private avRecorder: media.AVRecorder = undefined;
|
|
private receiver: image.ImageReceiver = undefined;
|
|
private photoPath: string = '';
|
|
private fd: number = -1;
|
|
private takePictureHandle: (photoUri: string) => void = undefined;
|
|
private currentMode:number = cameraMode.modePhoto;
|
|
|
|
private videoFrameWH: VideoFrameWH = {
|
|
width: 480,
|
|
height: 360,
|
|
}; // 视频分辨率
|
|
private imageRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0; // 图片旋转角度
|
|
|
|
private videoProfile: media.VideoRecorderProfile = {
|
|
audioChannels: 2,
|
|
audioCodec: media.CodecMimeType.AUDIO_AAC,
|
|
audioBitrate: 48000,
|
|
audioSampleRate: 48000,
|
|
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
|
|
videoBitrate: 48000,
|
|
videoCodec: media.CodecMimeType.VIDEO_MPEG4,
|
|
videoFrameWidth: 480,
|
|
videoFrameHeight: 360,
|
|
videoFrameRate: 30,
|
|
};
|
|
private videoSourceType: number = 0;
|
|
|
|
constructor() {
|
|
this.mediaModel = MediaModel.getMediaInstance();
|
|
this.receiver = image.createImageReceiver(
|
|
cameraWH.width,
|
|
cameraWH.height,
|
|
FOUR,
|
|
EIGHT
|
|
);
|
|
Logger.info(TAG, 'createImageReceiver');
|
|
this.receiver.on('imageArrival', () => {
|
|
Logger.info(TAG, 'imageArrival');
|
|
this.receiver.readNextImage((err, image) => {
|
|
Logger.info(TAG, 'readNextImage');
|
|
if (err || image === undefined) {
|
|
Logger.error(TAG, 'failed to get valid image');
|
|
return;
|
|
}
|
|
image.getComponent(FOUR, (errMsg, img) => {
|
|
Logger.info(TAG, 'getComponent');
|
|
if (errMsg || img === undefined) {
|
|
Logger.info(TAG, 'failed to get valid buffer');
|
|
return;
|
|
}
|
|
let buffer = new ArrayBuffer(FOUR_THOUSAND_AND_SIXTY_NINE);
|
|
if (img.byteBuffer) {
|
|
buffer = img.byteBuffer;
|
|
} else {
|
|
Logger.error(TAG, 'img.byteBuffer is undefined');
|
|
}
|
|
this.saveImage(buffer, image);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 设置分辨率
|
|
* @param videoFrame
|
|
*/
|
|
setVideoFrameWH(videoFrame: VideoFrame): void {
|
|
switch (videoFrame) {
|
|
case VideoFrame.VIDEOFRAME_800_600:
|
|
this.videoFrameWH = {
|
|
width: 800,
|
|
height: 600,
|
|
};
|
|
Logger.info(
|
|
TAG,
|
|
`setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}`
|
|
);
|
|
break;
|
|
case VideoFrame.VIDEOFRAME_1280_720:
|
|
this.videoFrameWH = {
|
|
width: 1280,
|
|
height: 720,
|
|
};
|
|
Logger.info(
|
|
TAG,
|
|
`setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}`
|
|
);
|
|
break;
|
|
case VideoFrame.VIDEOFRAME_1920_1080:
|
|
this.videoFrameWH = {
|
|
width: 1920,
|
|
height: 1080,
|
|
};
|
|
Logger.info(
|
|
TAG,
|
|
`setVideoFrameWH videoFrameWH:${JSON.stringify(this.videoFrameWH)}`
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
/**
|
|
* 设置图片旋转角度
|
|
* @param imageRotation
|
|
*/
|
|
setImageRotation(imageRotation: camera.ImageRotation): void {
|
|
this.imageRotation = imageRotation;
|
|
}
|
|
|
|
/**
|
|
* 保存图片
|
|
* @param buffer
|
|
* @param img
|
|
*/
|
|
async saveImage(buffer: ArrayBuffer, img: image.Image): Promise<void> {
|
|
Logger.info(TAG, 'savePicture');
|
|
this.fileAsset = await this.mediaModel.createAndGetUri(mediaLibrary.MediaType.IMAGE);
|
|
this.photoPath = this.fileAsset.uri;
|
|
Logger.info(TAG, `this.photoUri = ${this.photoPath}`);
|
|
this.fd = await this.mediaModel.getFdPath(this.fileAsset);
|
|
Logger.info(TAG, `this.fd = ${this.fd}`);
|
|
await fileIo.write(this.fd, buffer);
|
|
await this.fileAsset.close(this.fd);
|
|
await img.release();
|
|
Logger.info(TAG, 'save image done');
|
|
if (this.takePictureHandle) {
|
|
this.takePictureHandle(this.photoPath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 初始化相机
|
|
* @param surfaceId
|
|
*/
|
|
async initCamera(surfaceId: string): Promise<void> {
|
|
Logger.info(TAG, `initCamera surfaceId:${surfaceId}`);
|
|
await this.cameraRelease();
|
|
Logger.info(TAG, `deviceInfo.deviceType = ${deviceInfo.deviceType}`);
|
|
if (deviceInfo.deviceType === 'default') {
|
|
Logger.info(TAG, `deviceInfo.deviceType default 1 = ${deviceInfo.deviceType}`);
|
|
this.videoSourceType = 1;
|
|
Logger.info(TAG, `deviceInfo.deviceType default 2 = ${deviceInfo.deviceType}`);
|
|
} else {
|
|
Logger.info(TAG, `deviceInfo.deviceType other 1 = ${deviceInfo.deviceType}`);
|
|
this.videoSourceType = 0;
|
|
Logger.info(TAG, `deviceInfo.deviceType other 2 = ${deviceInfo.deviceType}`);
|
|
}
|
|
Logger.info(TAG, 'getCameraManager begin');
|
|
try {
|
|
Logger.info(TAG, 'getCameraManager try begin');
|
|
this.cameraMgr = camera.getCameraManager(globalThis.cameraContext);
|
|
Logger.info(TAG, 'getCameraManager try end');
|
|
} catch (e) {
|
|
Logger.info(TAG, `getCameraManager catch e:${JSON.stringify(e)}`);
|
|
Logger.info(TAG, `getCameraManager catch code:${JSON.stringify(e.code)}`);
|
|
Logger.info(TAG, `getCameraManager catch message:${JSON.stringify(e.message)}`);
|
|
}
|
|
Logger.info(TAG, 'getCameraManager end');
|
|
Logger.info(TAG, `getCameraManager ${JSON.stringify(this.cameraMgr)}`);
|
|
this.camerasArray = this.cameraMgr.getSupportedCameras();
|
|
Logger.info(TAG, `get cameras ${this.camerasArray.length}`);
|
|
if (this.camerasArray.length === 0) {
|
|
Logger.info(TAG, 'cannot get cameras');
|
|
return;
|
|
}
|
|
|
|
let mCamera = this.camerasArray[0];
|
|
this.cameraInput = this.cameraMgr.createCameraInput(mCamera);
|
|
this.cameraInput.open();
|
|
Logger.info(TAG, 'createCameraInput');
|
|
this.capability = this.cameraMgr.getSupportedOutputCapability(mCamera);
|
|
let previewProfile = this.capability.previewProfiles[0];
|
|
this.previewOutput = this.cameraMgr.createPreviewOutput(
|
|
previewProfile,
|
|
surfaceId
|
|
);
|
|
Logger.info(TAG, 'createPreviewOutput');
|
|
let rSurfaceId = await this.receiver.getReceivingSurfaceId();
|
|
let photoProfile = this.capability.photoProfiles[0];
|
|
this.photoOutPut = this.cameraMgr.createPhotoOutput(
|
|
photoProfile,
|
|
rSurfaceId
|
|
);
|
|
this.capSession = this.cameraMgr.createCaptureSession();
|
|
Logger.info(TAG, 'createCaptureSession');
|
|
this.capSession.beginConfig();
|
|
Logger.info(TAG, 'beginConfig');
|
|
this.capSession.addInput(this.cameraInput);
|
|
this.capSession.addOutput(this.previewOutput);
|
|
this.capSession.addOutput(this.photoOutPut);
|
|
await this.capSession.commitConfig();
|
|
await this.capSession.start();
|
|
Logger.info(TAG, 'captureSession start');
|
|
}
|
|
|
|
setTakePictureHandleCallback(callback): void {
|
|
this.takePictureHandle = callback;
|
|
}
|
|
|
|
/**
|
|
* 拍照
|
|
*/
|
|
async takePicture(): Promise<void> {
|
|
Logger.info(TAG, 'takePicture');
|
|
if (this.currentMode === cameraMode.modeVideo) {
|
|
this.currentMode = cameraMode.modePhoto;
|
|
}
|
|
Logger.info(TAG, `takePicture imageRotation:${this.imageRotation}`);
|
|
let photoSettings = {
|
|
rotation: this.imageRotation,
|
|
quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM,
|
|
location: {
|
|
// 位置信息,经纬度
|
|
latitude: 12.9698,
|
|
longitude: 77.75,
|
|
altitude: 1000,
|
|
},
|
|
mirror: false,
|
|
};
|
|
await this.photoOutPut.capture(photoSettings);
|
|
Logger.info(TAG, 'takePicture done');
|
|
AppStorage.Set('isRefresh', true);
|
|
}
|
|
|
|
/**
|
|
* 开始录像
|
|
*/
|
|
async startVideo(): Promise<void> {
|
|
Logger.info(TAG, 'startVideo begin');
|
|
Logger.info(TAG, 'startVideo 1');
|
|
await this.capSession.stop();
|
|
Logger.info(TAG, 'startVideo 2');
|
|
this.capSession.beginConfig();
|
|
Logger.info(TAG, 'startVideo 3');
|
|
if (this.currentMode === cameraMode.modePhoto) {
|
|
this.currentMode = cameraMode.modeVideo;
|
|
if (this.photoOutPut) {
|
|
this.capSession.removeOutput(this.photoOutPut);
|
|
this.photoOutPut.release();
|
|
}
|
|
} else {
|
|
if (this.videoOutput) {
|
|
try {
|
|
Logger.info(TAG, 'startVideo 4');
|
|
this.capSession.removeOutput(this.videoOutput);
|
|
Logger.info(TAG, 'startVideo 5');
|
|
} catch (e) {
|
|
Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
|
|
Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`);
|
|
Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`);
|
|
}
|
|
}
|
|
}
|
|
if (this.videoOutput) {
|
|
try {
|
|
Logger.info(TAG, 'startVideo 6');
|
|
this.capSession.removeOutput(this.videoOutput);
|
|
Logger.info(TAG, 'startVideo 7');
|
|
} catch (e) {
|
|
Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
|
|
Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`);
|
|
Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`);
|
|
}
|
|
try {
|
|
Logger.info(TAG, 'startVideo release 1');
|
|
await this.videoOutput.release();
|
|
Logger.info(TAG, 'startVideo release 2');
|
|
} catch (e) {
|
|
Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
|
|
Logger.info(TAG, `startVideo catch code:${JSON.stringify(e.code)}`);
|
|
Logger.info(TAG, `startVideo catch message:${JSON.stringify(e.message)}`);
|
|
}
|
|
}
|
|
Logger.info(TAG, 'startVideo 8');
|
|
this.fileAsset = await this.mediaModel.createAndGetUri(mediaLibrary.MediaType.VIDEO);
|
|
Logger.info(TAG, `startVideo fileAsset:${this.fileAsset}`);
|
|
this.fd = await this.mediaModel.getFdPath(this.fileAsset);
|
|
Logger.info(TAG, `startVideo fd:${this.fd}`);
|
|
media.createAVRecorder(async (error, recorder) => {
|
|
Logger.info(TAG, `startVideo into createAVRecorder:${recorder}`);
|
|
if (recorder != null) {
|
|
Logger.info(TAG, `startVideo into recorder:${recorder}`);
|
|
this.avRecorder = recorder;
|
|
Logger.info(TAG, `startVideo createAVRecorder success:${this.avRecorder}`);
|
|
// 当前录像配置
|
|
let currConfig = {
|
|
audioSourceType: 1,
|
|
videoSourceType: this.videoSourceType,
|
|
profile: this.videoProfile,
|
|
url: `fd://${this.fd}`,
|
|
rotation: 0
|
|
};
|
|
Logger.info(TAG, `startVideo into recorder:${recorder}`);
|
|
await this.avRecorder.prepare(currConfig);
|
|
Logger.info(TAG, `startVideo videoConfig:${JSON.stringify(currConfig)}`);
|
|
let videoId = await this.avRecorder.getInputSurface();
|
|
let videoProfile = this.capability.videoProfiles[0];
|
|
Logger.info(TAG, `startVideo capability.videoProfiles[]=: ${JSON.stringify(this.capability.videoProfiles)}`);
|
|
Logger.info(TAG, `startVideo videoProfile:${JSON.stringify(videoProfile)}`);
|
|
this.videoOutput = this.cameraMgr.createVideoOutput(videoProfile, videoId);
|
|
Logger.info(TAG, `startVideo videoOutput:${this.videoOutput}`);
|
|
this.capSession.addOutput(this.videoOutput);
|
|
Logger.info(TAG, 'startVideo addOutput');
|
|
await this.capSession.commitConfig();
|
|
Logger.info(TAG, 'startVideo commitConfig');
|
|
await this.capSession.start();
|
|
Logger.info(TAG, 'startVideo commitConfig capSession');
|
|
await this.videoOutput.start();
|
|
Logger.info(TAG, 'startVideo commitConfig videoOutput');
|
|
try {
|
|
Logger.info(TAG, 'startVideo avRecorder.start 1');
|
|
await this.avRecorder.start();
|
|
Logger.info(TAG, 'startVideo avRecorder.start 2');
|
|
} catch (e) {
|
|
Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
|
|
}
|
|
|
|
Logger.info(TAG, 'startVideo end');
|
|
|
|
} else {
|
|
Logger.info(TAG, `startVideo createAVRecorder fail, error:${error}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 停止录像
|
|
*/
|
|
async stopVideo(): Promise<void> {
|
|
Logger.info(TAG, 'stopVideo called');
|
|
await this.avRecorder.stop();
|
|
await this.avRecorder.release();
|
|
await this.videoOutput.stop();
|
|
await this.fileAsset.close(this.fd);
|
|
}
|
|
|
|
/**
|
|
* 资源释放
|
|
*/
|
|
async cameraRelease(): Promise<void> {
|
|
Logger.info(TAG, 'releaseCamera');
|
|
if (this.cameraInput) {
|
|
await this.cameraInput.close();
|
|
}
|
|
if (this.previewOutput) {
|
|
await this.previewOutput.release();
|
|
}
|
|
if (this.photoOutPut) {
|
|
await this.photoOutPut.release();
|
|
}
|
|
if (this.videoOutput) {
|
|
await this.videoOutput.release();
|
|
}
|
|
if (this.capSession) {
|
|
await this.capSession.release();
|
|
}
|
|
}
|
|
}
|