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 image from '@ohos.multimedia.image'
18import photoAccessHelper from '@ohos.file.photoAccessHelper'
19import media from '@ohos.multimedia.media';
20import deviceInfo from '@ohos.deviceInfo';
21import type common from '@ohos.app.ability.common'
22import MediaModel from './MediaModel'
23import Logger from '../utils/Logger'
24
25const TAG: string = '[CameraModel]'
26
27const CAMERASIZE = {
28  WIDTH: 1920,
29  HEIGHT: 1080
30}
31
32/**
33 * 相机服务
34 */
35export default class CameraModel {
36  private cameraInput: camera.CameraInput;
37  private previewOutput: camera.PreviewOutput;
38  private photoAsset: photoAccessHelper.PhotoAsset;
39  private photoUri: string = '';
40  private mediaModel: MediaModel;
41  private fd: number = -1;
42  private receiver: image.ImageReceiver;
43  private context: any;
44  private cameraManager: camera.CameraManager;
45  private cameras: Array<camera.CameraDevice>;
46  private photoOutPut: camera.PhotoOutput;
47  private captureSession: camera.CaptureSession;
48  private cameraOutputCapability: camera.CameraOutputCapability;
49  private videoOutput: camera.VideoOutput;
50  private avRecorder: media.AVRecorder;
51
52  private avRecorderProfile: media.VideoRecorderProfile = {
53    audioBitrate: 48000,
54    audioChannels: 2,
55    audioCodec: media.CodecMimeType.AUDIO_AAC,
56    audioSampleRate: 48000,
57    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
58    videoBitrate: 2000000,
59    videoCodec: media.CodecMimeType.VIDEO_MPEG4,
60    videoFrameWidth: 640,
61    videoFrameHeight: 480,
62    videoFrameRate: 30
63  }
64
65  private videoSourceType = 0;
66
67  constructor(context: common.Context) {
68    this.context = context
69    this.mediaModel = MediaModel.getMediaInstance(context)
70    // 服务端代码,创建ImageReceiver
71    this.receiver = image.createImageReceiver(CAMERASIZE.WIDTH, CAMERASIZE.HEIGHT, 4, 8) // 4表示生成的图像格式,8表示用户希望同时访问的最大图像数
72    Logger.info(TAG, `createImageReceiver`)
73    // 获取Surface ID
74    this.receiver.getReceivingSurfaceId((surfaceId) => {
75      Logger.info(TAG, `getReceivingSurfaceId surfaceId is ${surfaceId}`)
76    })
77    // 注册Surface的监听,在surface的buffer准备好后触发
78    this.receiver.on('imageArrival', () => {
79      Logger.info(TAG, `imageArrival`)
80      // 去获取Surface中最新的buffer
81      this.receiver.readNextImage((err, image) => {
82        Logger.info(TAG, `readNextImage`)
83        if (err || image === undefined) {
84          Logger.error(TAG, `failed to get valid image`)
85          return
86        }
87        image.getComponent(4, (errMsg, img) => {
88          // 4表示图像类型为JPEG
89          // 消费component.byteBuffer,例如:将buffer内容保存成图片。
90          Logger.info(TAG, `getComponent`)
91          if (errMsg || img === undefined) {
92            Logger.info(TAG, `failed to get valid buffer`)
93            return
94          }
95          let buffer = new ArrayBuffer(4096)
96          if (img.byteBuffer) {
97            buffer = img.byteBuffer
98          } else {
99            Logger.error(TAG, `img.byteBuffer is undefined`)
100          }
101        })
102      })
103    })
104  }
105
106  /**
107   * 创建相机
108   */
109  async createCamera(surfaceId: string): Promise<void> {
110    Logger.info(TAG, `initCamera surfaceId:${surfaceId}`);
111    // await this.releaseCamera();
112    Logger.info(TAG, `deviceInfo.deviceType = ${deviceInfo.deviceType}`);
113    if (deviceInfo.deviceType === 'default') {
114      Logger.info(TAG, `deviceInfo.deviceType default 1 = ${deviceInfo.deviceType}`);
115      this.videoSourceType = 1;
116      Logger.info(TAG, `deviceInfo.deviceType default 2 = ${deviceInfo.deviceType}`);
117    } else {
118      Logger.info(TAG, `deviceInfo.deviceType other 1 = ${deviceInfo.deviceType}`);
119      this.videoSourceType = 0;
120      Logger.info(TAG, `deviceInfo.deviceType other 2 = ${deviceInfo.deviceType}`);
121    }
122    Logger.info(TAG, `getCameraManager begin`);
123    try {
124      Logger.info(TAG, `getCameraManager try begin`);
125      this.cameraManager = camera.getCameraManager(this.context);
126      Logger.info(TAG, `getCameraManager try end`);
127    } catch (e) {
128      Logger.info(TAG, `getCameraManager catch e:${JSON.stringify(e)}`);
129    }
130    Logger.info(TAG, `getCameraManager end`);
131    Logger.info(TAG, `getCameraManager ${JSON.stringify(this.cameraManager)}`);
132    this.cameras = this.cameraManager.getSupportedCameras();
133    Logger.info(TAG, `createCamera get cameras ${this.cameras.length}`);
134    if (this.cameras.length === 0) {
135      Logger.info(TAG, 'cannot get cameras');
136      return;
137    }
138
139    Logger.info(TAG, `createCamera cameras=${this.cameras}`);
140    let mCamera = this.cameras[0];
141    Logger.info(TAG, `createCamera mCamera=${mCamera}`);
142    this.cameraInput = this.cameraManager.createCameraInput(mCamera);
143    Logger.info(TAG, `createCamera cameraInput=${this.cameraInput}`);
144    this.cameraInput.open();
145    Logger.info(TAG, 'createCameraInput');
146    this.cameraOutputCapability = this.cameraManager.getSupportedOutputCapability(mCamera);
147    let previewProfile = this.cameraOutputCapability.previewProfiles[0];
148    this.previewOutput = this.cameraManager.createPreviewOutput(
149      previewProfile,
150      surfaceId
151    );
152    Logger.info(TAG, 'createPreviewOutput');
153    let rSurfaceId = await this.receiver.getReceivingSurfaceId();
154    let photoProfile = this.cameraOutputCapability.photoProfiles[0];
155    this.photoOutPut = this.cameraManager.createPhotoOutput(
156      photoProfile,
157      rSurfaceId
158    );
159    this.captureSession = this.cameraManager.createCaptureSession();
160    Logger.info(TAG, 'createCaptureSession');
161    this.captureSession.beginConfig();
162    Logger.info(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(TAG, 'captureSession start');
169  }
170
171  /**
172   * 开始录像
173   */
174  async startVideo(): Promise<void> {
175    Logger.info(TAG, 'startVideo begin');
176    Logger.info(TAG, 'startVideo 1');
177    await this.captureSession.stop();
178    Logger.info(TAG, 'startVideo 2');
179    this.captureSession.beginConfig();
180    Logger.info(TAG, 'startVideo 3');
181    if (this.videoOutput) {
182      try {
183        Logger.info(TAG, 'startVideo 4');
184        this.captureSession.removeOutput(this.videoOutput);
185      } catch (e) {
186        Logger.info(TAG, `startVideo catch e:${e}`);
187      }
188    }
189    if (this.videoOutput) {
190      try {
191        Logger.info(TAG, 'startVideo 5');
192        this.captureSession.removeOutput(this.videoOutput);
193        Logger.info(TAG, 'startVideo 6');
194      } catch (e) {
195        Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
196      }
197      try {
198        Logger.info(TAG, 'startVideo release');
199        await this.videoOutput.release();
200      } catch (e) {
201        Logger.info(TAG, `startVideo catch e:${JSON.stringify(e)}`);
202      }
203    }
204    Logger.info(TAG, 'startVideo 7');
205    this.photoAsset = await this.mediaModel.createAndGetUri(photoAccessHelper.PhotoType.VIDEO);
206    Logger.info(TAG, `startVideo photoAsset:${this.photoAsset}`);
207    this.fd = await this.mediaModel.getFdPath(this.photoAsset);
208    Logger.info(TAG, `startVideo fd:${this.fd}`);
209    this.avRecorder = await media.createAVRecorder();
210
211    Logger.info(TAG, `startVideo into createAVRecorder:${this.avRecorder}`);
212    if (this.avRecorder != null) {
213      Logger.info(TAG, `startVideo createAVRecorder success:${this.avRecorder}`);
214
215      let videoConfig: media.VideoRecorderConfig = {
216        audioSourceType: 1,
217        videoSourceType: this.videoSourceType,
218        profile: this.avRecorderProfile,
219        url: `fd://${this.fd}`,
220        rotation: 0
221      }
222
223      Logger.info(TAG, `startVideo videoConfig:${JSON.stringify(videoConfig)}`);
224      try {
225        await this.avRecorder.prepare(videoConfig);
226      } catch (err) {
227        Logger.info(TAG, `startVideo err:${err}`);
228      }
229
230      Logger.info(TAG, `startVideo prepare`);
231      let videoId = await this.avRecorder.getInputSurface();
232      let videoProfile = this.cameraOutputCapability.videoProfiles[0];
233      Logger.info(TAG, `startVideo capability.videoProfiles[]=: ${JSON.stringify(this.cameraOutputCapability.videoProfiles)}`);
234      Logger.info(TAG, `startVideo videoProfile:${JSON.stringify(videoProfile)}`);
235      this.videoOutput = this.cameraManager.createVideoOutput(videoProfile, videoId);
236      Logger.info(TAG, `startVideo videoOutput:${this.videoOutput}`);
237      this.captureSession.addOutput(this.videoOutput);
238      Logger.info(TAG, `startVideo addOutput`);
239      await this.captureSession.commitConfig();
240      Logger.info(TAG, `startVideo commitConfig`);
241      await this.captureSession.start();
242      Logger.info(TAG, `startVideo commitConfig captureSession`);
243      await this.videoOutput.start();
244      Logger.info(TAG, `startVideo commitConfig videoOutput`);
245      await this.avRecorder.start();
246      Logger.info(TAG, 'startVideo end');
247
248    } else {
249      Logger.info(TAG, `startVideo createAVRecorder fail, error null`);
250    }
251  }
252
253  /**
254   * 停止录像
255   */
256  async stopVideo(): Promise<string> {
257    Logger.info(TAG, 'stopVideo called');
258    await this.avRecorder.stop();
259    await this.avRecorder.release();
260    await this.videoOutput.stop();
261    // 停止结束时复制录制的视频至沙箱cache目录中
262    let fileName = await this.copyVideo();
263    await this.photoAsset.close(this.fd);
264    return fileName;
265  }
266
267  /**
268   * 复制视频至video
269   */
270  async copyVideo(): Promise<string> {
271    let url = this.photoAsset.uri;
272    Logger.info(TAG, `stopVideo uri:${url}`);
273    let fileName = await this.mediaModel.copyVideo(this.fd);
274    Logger.info(TAG, `stopVideo fileName:${fileName}`);
275    return fileName;
276  }
277
278  /**
279   * 关闭相机
280   */
281  async releaseCamera(): Promise<void> {
282    Logger.info(TAG, `releaseCamera start`);
283    if (this.cameraInput) {
284      await this.cameraInput.close();
285    }
286    if (this.previewOutput) {
287      await this.previewOutput.release();
288    }
289    if (this.photoOutPut) {
290      await this.photoOutPut.release();
291    }
292    if (this.videoOutput) {
293      await this.videoOutput.release();
294    }
295    if (this.captureSession) {
296      await this.captureSession.release();
297    }
298    Logger.info(TAG, `releaseCamera end`);
299  }
300}
301
302