From 447045c78b70ecf1f87329e4580165886a0998a1 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 9 Jun 2025 20:25:27 +0800 Subject: [PATCH 01/20] avrecorder ndk demo Signed-off-by: Steven --- code/DocsSample/Media/AVRecorder/.gitignore | 12 + .../Media/AVRecorder/AppScope/app.json5 | 24 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes code/DocsSample/Media/AVRecorder/README_zh.md | 67 ++ .../Media/AVRecorder/build-profile.json5 | 53 + .../Media/AVRecorder/entry/.gitignore | 6 + .../AVRecorder/entry/build-profile.json5 | 54 + .../Media/AVRecorder/entry/hvigorfile.ts | 21 + .../Media/AVRecorder/entry/oh-package.json5 | 26 + .../entry/src/main/cpp/CMakeLists.txt | 18 + .../entry/src/main/cpp/camera_manager.cpp | 1044 +++++++++++++++++ .../entry/src/main/cpp/camera_manager.h | 162 +++ .../entry/src/main/cpp/log_common.h | 31 + .../AVRecorder/entry/src/main/cpp/main.cpp | 550 +++++++++ .../AVRecorder/entry/src/main/cpp/main.h | 49 + .../AVRecorder/entry/src/main/cpp/muxer.cpp | 91 ++ .../AVRecorder/entry/src/main/cpp/muxer.h | 46 + .../entry/src/main/cpp/sample_info.h | 142 +++ .../src/main/cpp/types/libentry/index.d.ts | 38 + .../main/cpp/types/libentry/oh-package.json5 | 20 + .../src/main/cpp/video_encoder_sample.cpp | 73 ++ .../entry/src/main/cpp/video_encoder_sample.h | 36 + .../main/ets/entryability/EntryAbility.ets | 65 + .../entrybackupability/EntryBackupAbility.ets | 28 + .../entry/src/main/ets/pages/Index.ets | 163 +++ .../AVRecorder/entry/src/main/module.json5 | 137 +++ .../main/resources/base/element/color.json | 8 + .../main/resources/base/element/string.json | 56 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/media/layered_image.json | 7 + .../main/resources/base/media/startIcon.png | Bin 0 -> 6790 bytes .../resources/base/profile/main_pages.json | 5 + .../main/resources/en_US/element/string.json | 16 + .../main/resources/zh_CN/element/string.json | 16 + .../AVRecorder/hvigor/hvigor-config.json5 | 47 + .../DocsSample/Media/AVRecorder/hvigorfile.ts | 22 + code/DocsSample/Media/AVRecorder/hvigorw | 60 + code/DocsSample/Media/AVRecorder/hvigorw.bat | 63 + .../Media/AVRecorder/oh-package.json5 | 29 + code/DocsSample/Media/AVRecorder/ohosTest.md | 10 + 41 files changed, 3303 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/.gitignore create mode 100644 code/DocsSample/Media/AVRecorder/AppScope/app.json5 create mode 100644 code/DocsSample/Media/AVRecorder/AppScope/resources/base/element/string.json create mode 100644 code/DocsSample/Media/AVRecorder/AppScope/resources/base/media/app_icon.png create mode 100644 code/DocsSample/Media/AVRecorder/README_zh.md create mode 100644 code/DocsSample/Media/AVRecorder/build-profile.json5 create mode 100644 code/DocsSample/Media/AVRecorder/entry/.gitignore create mode 100644 code/DocsSample/Media/AVRecorder/entry/build-profile.json5 create mode 100644 code/DocsSample/Media/AVRecorder/entry/hvigorfile.ts create mode 100644 code/DocsSample/Media/AVRecorder/entry/oh-package.json5 create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/CMakeLists.txt create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/camera_manager.cpp create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/camera_manager.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/log_common.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.cpp create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/sample_info.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/index.d.ts create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/oh-package.json5 create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.cpp create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.h create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/ets/pages/Index.ets create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/module.json5 create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/color.json create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/string.json create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/icon.png create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/layered_image.json create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/startIcon.png create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/profile/main_pages.json create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/en_US/element/string.json create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/zh_CN/element/string.json create mode 100644 code/DocsSample/Media/AVRecorder/hvigor/hvigor-config.json5 create mode 100644 code/DocsSample/Media/AVRecorder/hvigorfile.ts create mode 100644 code/DocsSample/Media/AVRecorder/hvigorw create mode 100644 code/DocsSample/Media/AVRecorder/hvigorw.bat create mode 100644 code/DocsSample/Media/AVRecorder/oh-package.json5 create mode 100644 code/DocsSample/Media/AVRecorder/ohosTest.md diff --git a/code/DocsSample/Media/AVRecorder/.gitignore b/code/DocsSample/Media/AVRecorder/.gitignore new file mode 100644 index 0000000000..d2ff20141c --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/AppScope/app.json5 b/code/DocsSample/Media/AVRecorder/AppScope/app.json5 new file mode 100644 index 0000000000..7e5ebd28f9 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/AppScope/app.json5 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ +{ + "app": { + "bundleName": "com.samples.avrecorder", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/code/DocsSample/Media/AVRecorder/AppScope/resources/base/element/string.json b/code/DocsSample/Media/AVRecorder/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000..c341df60c8 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "AVRecorderSample" + } + ] +} diff --git a/code/DocsSample/Media/AVRecorder/AppScope/resources/base/media/app_icon.png b/code/DocsSample/Media/AVRecorder/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 g_int32ToCameraSceneMode = { + {1, Camera_SceneMode::NORMAL_PHOTO}, + {2, Camera_SceneMode::NORMAL_VIDEO}, + {12, Camera_SceneMode::SECURE_PHOTO} +}; + +NDKCamera::NDKCamera(uint32_t focusMode, uint32_t cameraDeviceIndex, uint32_t sceneMode, + char *previewId, char *photoId, char *videoId) + : focusMode_(focusMode), cameraDeviceIndex_(cameraDeviceIndex), + previewSurfaceId_(previewId), photoSurfaceId_(photoId), videoSurfaceId_(videoId), + cameras_(nullptr), profile_(nullptr), cameraOutputCapability_(nullptr), captureSession_(nullptr), + cameraInput_(nullptr), previewOutput_(nullptr), photoOutput_(nullptr), videoOutput_(nullptr), + isCameraMuted_(nullptr), metaDataObjectType_(nullptr), metadataOutput_(nullptr), + isExposureModeSupported_(false), isFocusModeSupported_(false), isSuccess_(false), + sceneMode_(NORMAL_VIDEO), exposureMode_(EXPOSURE_MODE_LOCKED), ret_(CAMERA_OK), + size_(0), minExposureBias_(0), maxExposureBias_(0), step_(0) +{ + // release camera + ReleaseCamera(); + + // create camera manager + Camera_ErrorCode ret = OH_Camera_GetCameraManager(&cameraManager_); + if (cameraManager_ == nullptr || ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Get CameraManager failed."); + } + + // create capture session + ret = OH_CameraManager_CreateCaptureSession(cameraManager_, &captureSession_); + if (captureSession_ == nullptr || ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Create captureSession failed."); + } + + // look up sceneMode + auto itr1 = g_int32ToCameraSceneMode.find(sceneMode); + if (itr1 != g_int32ToCameraSceneMode.end()) { + sceneMode_ = itr1->second; + } + + // set session mode + ret = OH_CaptureSession_SetSessionMode(captureSession_, NORMAL_VIDEO); + + // get capture session profile + GetSupportedCameras(); + GetSupportedOutputCapability(); + + // create preview output + CreatePreviewOutput(); + + // create camera input and register camera input callback + CreateCameraInput(); + CameraInputOpen(); // 设备打开 + + // config and start session + SessionFlowFn(); +} + +NDKCamera::~NDKCamera() +{ + OH_LOG_INFO(LOG_APP, "~NDKCamera"); + Camera_ErrorCode ret = CAMERA_OK; + + if (cameraManager_) { + OH_LOG_INFO(LOG_APP, "Release OH_CameraManager_DeleteSupportedCameraOutputCapability. enter"); + ret = OH_CameraManager_DeleteSupportedCameraOutputCapability(cameraManager_, cameraOutputCapability_); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Delete CameraOutputCapability failed."); + } else { + OH_LOG_INFO(LOG_APP, "Release OH_CameraManager_DeleteSupportedCameraOutputCapability. ok"); + } + + OH_LOG_ERROR(LOG_APP, "Release OH_CameraManager_DeleteSupportedCameras. enter"); + ret = OH_CameraManager_DeleteSupportedCameras(cameraManager_, cameras_, size_); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Delete Cameras failed."); + } else { + OH_LOG_INFO(LOG_APP, "Release OH_CameraManager_DeleteSupportedCameras. ok"); + } + + ret = OH_Camera_DeleteCameraManager(cameraManager_); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Delete CameraManager failed."); + } else { + OH_LOG_INFO(LOG_APP, "Release OH_Camera_DeleteCameraMananger. ok"); + } + cameraManager_ = nullptr; + } + OH_LOG_INFO(LOG_APP, "~NDKCamera exit"); +} + +Camera_ErrorCode NDKCamera::ReleaseCamera(void) +{ + OH_LOG_INFO(LOG_APP, " enter ReleaseCamera"); + if (previewOutput_ && captureSession_) { + PreviewOutputStop(); + OH_CaptureSession_RemovePreviewOutput(captureSession_, previewOutput_); + PreviewOutputRelease(); + } + if (photoOutput_) { + PhotoOutputRelease(); + } + if (videoOutput_) { + VideoOutputRelease(); + } + if (captureSession_) { + SessionRealese(); + } + if (cameraInput_) { + CameraInputClose(); + } + OH_LOG_INFO(LOG_APP, " exit ReleaseCamera"); + return ret_; +} + +Camera_ErrorCode NDKCamera::ReleaseSession(void) +{ + OH_LOG_INFO(LOG_APP, " enter ReleaseSession"); + PreviewOutputStop(); + PhotoOutputRelease(); + VideoOutputRelease(); + SessionRealese(); + OH_LOG_INFO(LOG_APP, " exit ReleaseSession"); + return ret_; +} + +Camera_ErrorCode NDKCamera::SessionRealese(void) +{ + OH_LOG_INFO(LOG_APP, " enter SessionRealese"); + Camera_ErrorCode ret = OH_CaptureSession_Release(captureSession_); + captureSession_ = nullptr; + OH_LOG_INFO(LOG_APP, " exit SessionRealese"); + return ret; +} + +Camera_ErrorCode NDKCamera::HasFlashFn(uint32_t mode) +{ + Camera_FlashMode flashMode = static_cast(mode); + // Check for flashing lights + bool hasFlash = false; + Camera_ErrorCode ret = OH_CaptureSession_HasFlash(captureSession_, &hasFlash); + if (captureSession_ == nullptr || ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_HasFlash failed."); + } + if (hasFlash) { + OH_LOG_INFO(LOG_APP, "hasFlash success-----"); + } else { + OH_LOG_ERROR(LOG_APP, "hasFlash fail-----"); + } + + // Check if the flash mode is supported + bool isSupported = false; + ret = OH_CaptureSession_IsFlashModeSupported(captureSession_, flashMode, &isSupported); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsFlashModeSupported failed."); + } + if (isSupported) { + OH_LOG_INFO(LOG_APP, "isFlashModeSupported success-----"); + } else { + OH_LOG_ERROR(LOG_APP, "isFlashModeSupported fail-----"); + } + + // Set flash mode + ret = OH_CaptureSession_SetFlashMode(captureSession_, flashMode); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_SetFlashMode success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetFlashMode failed. %{public}d ", ret); + } + + // Obtain the flash mode of the current device + ret = OH_CaptureSession_GetFlashMode(captureSession_, &flashMode); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetFlashMode success. flashMode: %{public}d ", flashMode); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetFlashMode failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::IsVideoStabilizationModeSupportedFn(uint32_t mode) +{ + Camera_VideoStabilizationMode videoMode = static_cast(mode); + // Check if the specified video anti shake mode is supported + bool isSupported = false; + Camera_ErrorCode ret = + OH_CaptureSession_IsVideoStabilizationModeSupported(captureSession_, videoMode, &isSupported); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsVideoStabilizationModeSupported failed."); + } + if (isSupported) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_IsVideoStabilizationModeSupported success-----"); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_IsVideoStabilizationModeSupported fail-----"); + } + + // Set video stabilization + ret = OH_CaptureSession_SetVideoStabilizationMode(captureSession_, videoMode); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_SetVideoStabilizationMode success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetVideoStabilizationMode failed. %{public}d ", ret); + } + + ret = OH_CaptureSession_GetVideoStabilizationMode(captureSession_, &videoMode); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetVideoStabilizationMode success. videoMode: %{public}f ", + videoMode); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetVideoStabilizationMode failed. %{public}d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::setZoomRatioFn(uint32_t zoomRatio) +{ + float zoom = float(zoomRatio); + // Obtain supported zoom range + float minZoom; + float maxZoom; + Camera_ErrorCode ret = OH_CaptureSession_GetZoomRatioRange(captureSession_, &minZoom, &maxZoom); + if (captureSession_ == nullptr || ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetZoomRatioRange failed."); + } else { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetZoomRatioRange success. minZoom: %{public}f, maxZoom:%{public}f", + minZoom, maxZoom); + } + + // Set Zoom + ret = OH_CaptureSession_SetZoomRatio(captureSession_, zoom); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_SetZoomRatio success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetZoomRatio failed. %{public}d ", ret); + } + + // Obtain the zoom value of the current device + ret = OH_CaptureSession_GetZoomRatio(captureSession_, &zoom); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_GetZoomRatio success. zoom: %{public}f ", zoom); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_GetZoomRatio failed. %{public}d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::SessionBegin(void) +{ + Camera_ErrorCode ret = OH_CaptureSession_BeginConfig(captureSession_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_BeginConfig success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_BeginConfig failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::SessionCommitConfig(void) +{ + Camera_ErrorCode ret = OH_CaptureSession_CommitConfig(captureSession_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_CommitConfig success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_CommitConfig failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::SessionStart(void) +{ + bool isSupport = false; + OH_CaptureSession_IsVideoStabilizationModeSupported(captureSession_, + Camera_VideoStabilizationMode::STABILIZATION_MODE_AUTO, &isSupport); + if (isSupport) { + OH_CaptureSession_SetVideoStabilizationMode(captureSession_, + Camera_VideoStabilizationMode::STABILIZATION_MODE_AUTO); + } + + // Start focusing + OH_LOG_INFO(LOG_APP, "IsFocusMode start"); + OH_CaptureSession_SetFocusMode(captureSession_, Camera_FocusMode::FOCUS_MODE_AUTO); + OH_LOG_INFO(LOG_APP, "IsFocusMode success"); + + Camera_ErrorCode ret = OH_CaptureSession_Start(captureSession_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_Start success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_Start failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::SessionStop(void) +{ + Camera_ErrorCode ret = OH_CaptureSession_Stop(captureSession_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_Stop success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_Stop failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::SessionFlowFn(void) +{ + OH_LOG_INFO(LOG_APP, "Start SessionFlowFn IN."); + + // start configuring session + OH_LOG_INFO(LOG_APP, "session beginConfig."); + Camera_ErrorCode ret = OH_CaptureSession_BeginConfig(captureSession_); + + // add cameraInput to the session + OH_LOG_INFO(LOG_APP, "session add Input."); + ret = OH_CaptureSession_AddInput(captureSession_, cameraInput_); + + // add previewOutput to the session + OH_LOG_INFO(LOG_APP, "session add Preview Output."); + ret = OH_CaptureSession_AddPreviewOutput(captureSession_, previewOutput_); + + if (sceneMode_ == Camera_SceneMode::NORMAL_VIDEO) { + // create video output and register video output callback + CreateVideoOutput(videoSurfaceId_); + + // add videoOutput to the Session + AddVideoOutput(); + } + + // Submit configuration information + OH_LOG_INFO(LOG_APP, "session commitConfig"); + ret = OH_CaptureSession_CommitConfig(captureSession_); + + // Start Session Work + OH_LOG_INFO(LOG_APP, "session start"); + SessionStart(); + OH_LOG_INFO(LOG_APP, "session success"); + OH_VideoOutput_Start(videoOutput_); + + return ret; +} + +Camera_ErrorCode NDKCamera::CreateCameraInput(void) +{ + OH_LOG_ERROR(LOG_APP, "enter CreateCameraInput."); + ret_ = OH_CameraManager_CreateCameraInput(cameraManager_, &cameras_[cameraDeviceIndex_], &cameraInput_); + if (cameraInput_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CreateCameraInput failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_ERROR(LOG_APP, "exit CreateCameraInput."); + + // register camera input callback + CameraInputRegisterCallback(); + return ret_; +} + +Camera_ErrorCode NDKCamera::CameraInputOpen(void) +{ + OH_LOG_ERROR(LOG_APP, "enter CameraInputOpen."); + ret_ = OH_CameraInput_Open(cameraInput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CameraInput_Open failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_ERROR(LOG_APP, "exit CameraInputOpen."); + return ret_; +} + +Camera_ErrorCode NDKCamera::CameraInputClose(void) +{ + OH_LOG_ERROR(LOG_APP, "enter CameraInput_Close."); + ret_ = OH_CameraInput_Close(cameraInput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CameraInput_Close failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_ERROR(LOG_APP, "exit CameraInput_Close."); + return ret_; +} + +Camera_ErrorCode NDKCamera::CameraInputRelease(void) +{ + OH_LOG_ERROR(LOG_APP, "enter CameraInputRelease."); + ret_ = OH_CameraInput_Release(cameraInput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CameraInput_Release failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_ERROR(LOG_APP, "exit CameraInputRelease."); + return ret_; +} + +Camera_ErrorCode NDKCamera::GetSupportedCameras(void) +{ + ret_ = OH_CameraManager_GetSupportedCameras(cameraManager_, &cameras_, &size_); + if (cameras_ == nullptr || &size_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "Get supported cameras failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::GetSupportedOutputCapability(void) +{ + ret_ = OH_CameraManager_GetSupportedCameraOutputCapability(cameraManager_, &cameras_[cameraDeviceIndex_], + &cameraOutputCapability_); + if (cameraOutputCapability_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetSupportedCameraOutputCapability failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::CreatePreviewOutput(void) +{ + DRAWING_LOGD("NDKCamera::CreatePreviewOutput start!"); + profile_ = cameraOutputCapability_->previewProfiles[0]; + + if (profile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get previewProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + + profile_->size.width = videoFrameWidth; + profile_->size.height = videoFrameHeight; + ret_ = OH_CameraManager_CreatePreviewOutput(cameraManager_, profile_, previewSurfaceId_, &previewOutput_); + + if (previewSurfaceId_ == nullptr || previewOutput_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CreatePreviewOutput failed."); + return CAMERA_INVALID_ARGUMENT; + } + + // register preview output callback + PreviewOutputRegisterCallback(); + return ret_; +} + +Camera_ErrorCode NDKCamera::CreatePhotoOutput(char *photoSurfaceId) +{ + DRAWING_LOGD("NDKCamera::CreatePhotoOutput start!"); + profile_ = cameraOutputCapability_->photoProfiles[0]; + + if (profile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get photoProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + + profile_->size.width = videoFrameWidth; + profile_->size.height = videoFrameHeight ; + ret_ = OH_CameraManager_CreatePhotoOutput(cameraManager_, profile_, photoSurfaceId, &photoOutput_); + + if (photoSurfaceId == nullptr || photoOutput_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CreatePhotoOutput failed."); + return CAMERA_INVALID_ARGUMENT; + } + + // register photo output callback + PhotoOutputRegisterCallback(); + return ret_; +} + +Camera_ErrorCode NDKCamera::CreateVideoOutput(char *videoId) +{ + DRAWING_LOGD("NDKCamera::CreateVideoOutput start!"); + videoProfile_ = cameraOutputCapability_->videoProfiles[0]; + + if (videoProfile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get videoProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput width:%{public}d", videoProfile_->size.width); + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput height:%{public}d", videoProfile_->size.height); + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput format:%{public}d", videoProfile_->format); + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput range.min:%{public}d", videoProfile_->range.min); + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput range.max:%{public}d", videoProfile_->range.max); + + videoProfile_->size.width = videoFrameWidth; + videoProfile_->size.height = videoFrameHeight; + ret_ = OH_CameraManager_CreateVideoOutput(cameraManager_, videoProfile_, videoId, &videoOutput_); + + if (videoId == nullptr || videoOutput_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CreateVideoOutput failed."); + return CAMERA_INVALID_ARGUMENT; + } + + // register video output callback + VideoOutputRegisterCallback(); + return ret_; +} + +Camera_ErrorCode NDKCamera::AddVideoOutput(void) +{ + Camera_ErrorCode ret = OH_CaptureSession_AddVideoOutput(captureSession_, videoOutput_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_AddVideoOutput success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_AddVideoOutput failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::AddPhotoOutput() +{ + Camera_ErrorCode ret = OH_CaptureSession_AddPhotoOutput(captureSession_, photoOutput_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_CaptureSession_AddPhotoOutput success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_AddPhotoOutput failed. %d ", ret); + } + return ret; +} + +Camera_ErrorCode NDKCamera::CreateMetadataOutput(void) +{ + metaDataObjectType_ = cameraOutputCapability_->supportedMetadataObjectTypes[0]; + if (metaDataObjectType_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get metaDataObjectType failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CameraManager_CreateMetadataOutput(cameraManager_, metaDataObjectType_, &metadataOutput_); + if (metadataOutput_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "CreateMetadataOutput failed."); + return CAMERA_INVALID_ARGUMENT; + } + + // register metadata output callback + MetadataOutputRegisterCallback(); + return ret_; +} + +Camera_ErrorCode NDKCamera::IsCameraMuted(void) +{ + ret_ = OH_CameraManager_IsCameraMuted(cameraManager_, isCameraMuted_); + if (isCameraMuted_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "IsCameraMuted failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::PreviewOutputStop(void) +{ + OH_LOG_ERROR(LOG_APP, "enter PreviewOutputStop."); + ret_ = OH_PreviewOutput_Stop(previewOutput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "PreviewOutputStop failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::PreviewOutputRelease(void) +{ + OH_LOG_ERROR(LOG_APP, "enter PreviewOutputRelease."); + if (previewOutput_ != nullptr) { + ret_ = OH_PreviewOutput_Release(previewOutput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "PreviewOutputRelease failed."); + return CAMERA_INVALID_ARGUMENT; + } + previewOutput_ = nullptr; + } + return CAMERA_OK; +} + +Camera_ErrorCode NDKCamera::PhotoOutputRelease(void) +{ + OH_LOG_ERROR(LOG_APP, "enter PhotoOutputRelease."); + ret_ = OH_PhotoOutput_Release(photoOutput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "PhotoOutputRelease failed."); + return CAMERA_INVALID_ARGUMENT; + } + photoOutput_ = nullptr; + return ret_; +} + +Camera_ErrorCode NDKCamera::VideoOutputStart(void) +{ + OH_LOG_INFO(LOG_APP, "VideoOutputStart begin."); + Camera_ErrorCode ret = OH_VideoOutput_Start(videoOutput_); + if (ret == CAMERA_OK) { + OH_LOG_INFO(LOG_APP, "OH_VideoOutput_Start success."); + } else { + OH_LOG_ERROR(LOG_APP, "OH_VideoOutput_Start failed. %d ", ret); + } + return ret; +} + +// exposure mode +Camera_ErrorCode NDKCamera::IsExposureModeSupportedFn(uint32_t mode) +{ + OH_LOG_INFO(LOG_APP, "IsExposureModeSupportedFn start."); + exposureMode_ = static_cast(mode); + ret_ = OH_CaptureSession_IsExposureModeSupported(captureSession_, exposureMode_, &isExposureModeSupported_); + if (&isExposureModeSupported_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "IsExposureModeSupported failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_SetExposureMode(captureSession_, exposureMode_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "SetExposureMode failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_GetExposureMode(captureSession_, &exposureMode_); + if (&exposureMode_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetExposureMode failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_INFO(LOG_APP, "IsExposureModeSupportedFn end."); + return ret_; +} + +Camera_ErrorCode NDKCamera::IsMeteringPoint(int x, int y) +{ + OH_LOG_INFO(LOG_APP, "IsMeteringPoint start."); + ret_ = OH_CaptureSession_GetExposureMode(captureSession_, &exposureMode_); + if (&exposureMode_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetExposureMode failed."); + return CAMERA_INVALID_ARGUMENT; + } + Camera_Point exposurePoint; + exposurePoint.x = x; + exposurePoint.y = y; + ret_ = OH_CaptureSession_SetMeteringPoint(captureSession_, exposurePoint); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "SetMeteringPoint failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_GetMeteringPoint(captureSession_, &exposurePoint); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetMeteringPoint failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_INFO(LOG_APP, "IsMeteringPoint end."); + return ret_; +} + +Camera_ErrorCode NDKCamera::IsExposureBiasRange(int exposureBias) +{ + OH_LOG_INFO(LOG_APP, "IsExposureBiasRange end."); + float exposureBiasValue = (float)exposureBias; + ret_ = OH_CaptureSession_GetExposureBiasRange(captureSession_, &minExposureBias_, &maxExposureBias_, &step_); + if (&minExposureBias_ == nullptr || &maxExposureBias_ == nullptr || &step_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetExposureBiasRange failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_SetExposureBias(captureSession_, exposureBiasValue); + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_SetExposureBias end."); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "SetExposureBias failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_GetExposureBias(captureSession_, &exposureBiasValue); + if (&exposureBiasValue == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetExposureBias failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_INFO(LOG_APP, "IsExposureBiasRange end."); + return ret_; +} + +// focus mode +Camera_ErrorCode NDKCamera::IsFocusModeSupported(uint32_t mode) +{ + Camera_FocusMode focusMode = static_cast(mode); + ret_ = OH_CaptureSession_IsFocusModeSupported(captureSession_, focusMode, &isFocusModeSupported_); + if (&isFocusModeSupported_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "IsFocusModeSupported failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::IsFocusMode(uint32_t mode) +{ + OH_LOG_INFO(LOG_APP, "IsFocusMode start."); + Camera_FocusMode focusMode = static_cast(mode); + ret_ = OH_CaptureSession_IsFocusModeSupported(captureSession_, focusMode, &isFocusModeSupported_); + if (&isFocusModeSupported_ == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "IsFocusModeSupported failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_SetFocusMode(captureSession_, focusMode); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "SetFocusMode failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_GetFocusMode(captureSession_, &focusMode); + if (&focusMode == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetFocusMode failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_INFO(LOG_APP, "IsFocusMode end."); + return ret_; +} + +Camera_ErrorCode NDKCamera::IsFocusPoint(float x, float y) +{ + OH_LOG_INFO(LOG_APP, "IsFocusPoint start."); + Camera_Point focusPoint; + focusPoint.x = x; + focusPoint.y = y; + ret_ = OH_CaptureSession_SetFocusPoint(captureSession_, focusPoint); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "SetFocusPoint failed."); + return CAMERA_INVALID_ARGUMENT; + } + ret_ = OH_CaptureSession_GetFocusPoint(captureSession_, &focusPoint); + if (&focusPoint == nullptr || ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "GetFocusPoint failed."); + return CAMERA_INVALID_ARGUMENT; + } + OH_LOG_INFO(LOG_APP, "IsFocusPoint end."); + return ret_; +} + +int32_t NDKCamera::GetVideoFrameWidth(void) +{ + videoProfile_ = cameraOutputCapability_->videoProfiles[0]; + if (videoProfile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get videoProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + return videoProfile_->size.width; +} + +int32_t NDKCamera::GetVideoFrameHeight(void) +{ + videoProfile_ = cameraOutputCapability_->videoProfiles[0]; + if (videoProfile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get videoProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + return videoProfile_->size.height; +} + +int32_t NDKCamera::GetVideoFrameRate(void) +{ + videoProfile_ = cameraOutputCapability_->videoProfiles[0]; + if (videoProfile_ == nullptr) { + OH_LOG_ERROR(LOG_APP, "Get videoProfiles failed."); + return CAMERA_INVALID_ARGUMENT; + } + return videoProfile_->range.min; +} + +Camera_ErrorCode NDKCamera::VideoOutputStop(void) +{ + OH_LOG_ERROR(LOG_APP, "enter VideoOutputStop."); + ret_ = OH_VideoOutput_Stop(videoOutput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "VideoOutputStop failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret_; +} + +Camera_ErrorCode NDKCamera::VideoOutputRelease(void) +{ + OH_LOG_ERROR(LOG_APP, "enter VideoOutputRelease."); + if (videoOutput_ == nullptr) { + OH_LOG_DEBUG(LOG_APP, "VideoOutputRelease videoOutput_ is already null."); + return CAMERA_OK; + } + ret_ = OH_VideoOutput_Release(videoOutput_); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "VideoOutputRelease failed."); + return CAMERA_INVALID_ARGUMENT; + } + videoOutput_ = nullptr; + return ret_; +} + +Camera_ErrorCode NDKCamera::TakePicture(void) +{ + Camera_ErrorCode ret = CAMERA_OK; + ret = OH_PhotoOutput_Capture(photoOutput_); + OH_LOG_ERROR(LOG_APP, "takePicture OH_PhotoOutput_Capture ret = %{public}d.", ret); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "startPhoto failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret; +} + +Camera_ErrorCode NDKCamera::TakePictureWithPhotoSettings(Camera_PhotoCaptureSetting photoSetting) +{ + Camera_ErrorCode ret = CAMERA_OK; + ret = OH_PhotoOutput_Capture_WithCaptureSetting(photoOutput_, photoSetting); + + OH_LOG_INFO(LOG_APP, + "TakePictureWithPhotoSettings get quality %{public}d, rotation %{public}d, mirror %{public}d, " + "latitude, %{public}d, longitude %{public}d, altitude %{public}d", + photoSetting.quality, photoSetting.rotation, photoSetting.mirror, photoSetting.location->latitude, + photoSetting.location->longitude, photoSetting.location->altitude); + + OH_LOG_ERROR(LOG_APP, "takePicture TakePictureWithPhotoSettings ret = %{public}d.", ret); + if (ret != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "startPhoto failed."); + return CAMERA_INVALID_ARGUMENT; + } + return ret; +} + +// CameraManager Callback +void CameraManagerStatusCallback(Camera_Manager *cameraManager, Camera_StatusInfo *status) +{ + OH_LOG_INFO(LOG_APP, "CameraManagerStatusCallback"); +} + +CameraManager_Callbacks *NDKCamera::GetCameraManagerListener(void) +{ + static CameraManager_Callbacks cameraManagerListener = {.onCameraStatus = CameraManagerStatusCallback}; + return &cameraManagerListener; +} + +Camera_ErrorCode NDKCamera::CameraManagerRegisterCallback(void) +{ + ret_ = OH_CameraManager_RegisterCallback(cameraManager_, GetCameraManagerListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CameraManager_RegisterCallback failed."); + } + return ret_; +} + +// CameraInput Callback +void OnCameraInputError(const Camera_Input *cameraInput, Camera_ErrorCode errorCode) +{ + OH_LOG_INFO(LOG_APP, "OnCameraInput errorCode = %{public}d", errorCode); +} + +CameraInput_Callbacks *NDKCamera::GetCameraInputListener(void) +{ + static CameraInput_Callbacks cameraInputCallbacks = {.onError = OnCameraInputError}; + return &cameraInputCallbacks; +} + +Camera_ErrorCode NDKCamera::CameraInputRegisterCallback(void) +{ + ret_ = OH_CameraInput_RegisterCallback(cameraInput_, GetCameraInputListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CameraInput_RegisterCallback failed."); + } + return ret_; +} + +// PreviewOutput Callback +void PreviewOutputOnFrameStart(Camera_PreviewOutput *previewOutput) +{ + OH_LOG_INFO(LOG_APP, "PreviewOutputOnFrameStart"); +} + +void PreviewOutputOnFrameEnd(Camera_PreviewOutput *previewOutput, int32_t frameCount) +{ + OH_LOG_INFO(LOG_APP, "PreviewOutput frameCount = %{public}d", frameCount); +} + +void PreviewOutputOnError(Camera_PreviewOutput *previewOutput, Camera_ErrorCode errorCode) +{ + OH_LOG_INFO(LOG_APP, "PreviewOutput errorCode = %{public}d", errorCode); +} + +PreviewOutput_Callbacks *NDKCamera::GetPreviewOutputListener(void) +{ + static PreviewOutput_Callbacks previewOutputListener = { + .onFrameStart = PreviewOutputOnFrameStart, + .onFrameEnd = PreviewOutputOnFrameEnd, + .onError = PreviewOutputOnError + }; + return &previewOutputListener; +} + +Camera_ErrorCode NDKCamera::PreviewOutputRegisterCallback(void) +{ + ret_ = OH_PreviewOutput_RegisterCallback(previewOutput_, GetPreviewOutputListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_PreviewOutput_RegisterCallback failed."); + } + return ret_; +} + +// PhotoOutput Callback +void PhotoOutputOnFrameStart(Camera_PhotoOutput *photoOutput) +{ + DRAWING_LOGD("PhotoOutputOnFrameStart start!"); + OH_LOG_INFO(LOG_APP, "PhotoOutputOnFrameStart"); +} + +void PhotoOutputOnFrameShutter(Camera_PhotoOutput *photoOutput, Camera_FrameShutterInfo *info) +{ + DRAWING_LOGD("PhotoOutputOnFrameShutter start!"); + OH_LOG_INFO(LOG_APP, "PhotoOutputOnFrameShutter"); +} + +void PhotoOutputOnFrameEnd(Camera_PhotoOutput *photoOutput, int32_t frameCount) +{ + DRAWING_LOGD("PhotoOutputOnFrameEnd start!"); + OH_LOG_INFO(LOG_APP, "PhotoOutput frameCount = %{public}d", frameCount); +} + +void PhotoOutputOnError(Camera_PhotoOutput *photoOutput, Camera_ErrorCode errorCode) +{ + DRAWING_LOGD("PhotoOutputOnError start!"); + OH_LOG_INFO(LOG_APP, "PhotoOutput errorCode = %{public}d", errorCode); +} + +PhotoOutput_Callbacks *NDKCamera::GetPhotoOutputListener(void) +{ + static PhotoOutput_Callbacks photoOutputListener = { + .onFrameStart = PhotoOutputOnFrameStart, + .onFrameShutter = PhotoOutputOnFrameShutter, + .onFrameEnd = PhotoOutputOnFrameEnd, + .onError = PhotoOutputOnError + }; + return &photoOutputListener; +} + +Camera_ErrorCode NDKCamera::PhotoOutputRegisterCallback(void) +{ + ret_ = OH_PhotoOutput_RegisterCallback(photoOutput_, GetPhotoOutputListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_PhotoOutput_RegisterCallback failed."); + } + return ret_; +} + +// VideoOutput Callback +void VideoOutputOnFrameStart(Camera_VideoOutput *videoOutput) +{ + OH_LOG_INFO(LOG_APP, "VideoOutputOnFrameStart"); +} + +void VideoOutputOnFrameEnd(Camera_VideoOutput *videoOutput, int32_t frameCount) +{ + OH_LOG_INFO(LOG_APP, "VideoOutput frameCount = %{public}d", frameCount); +} + +void VideoOutputOnError(Camera_VideoOutput *videoOutput, Camera_ErrorCode errorCode) +{ + OH_LOG_INFO(LOG_APP, "VideoOutput errorCode = %{public}d", errorCode); +} + +VideoOutput_Callbacks *NDKCamera::GetVideoOutputListener(void) +{ + static VideoOutput_Callbacks videoOutputListener = { + .onFrameStart = VideoOutputOnFrameStart, + .onFrameEnd = VideoOutputOnFrameEnd, + .onError = VideoOutputOnError + }; + return &videoOutputListener; +} + +Camera_ErrorCode NDKCamera::VideoOutputRegisterCallback(void) +{ + ret_ = OH_VideoOutput_RegisterCallback(videoOutput_, GetVideoOutputListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_VideoOutput_RegisterCallback failed."); + } + return ret_; +} + +// Metadata Callback +void OnMetadataObjectAvailable(Camera_MetadataOutput *metadataOutput, Camera_MetadataObject *metadataObject, + uint32_t size) +{ + OH_LOG_INFO(LOG_APP, "size = %{public}d", size); +} + +void OnMetadataOutputError(Camera_MetadataOutput *metadataOutput, Camera_ErrorCode errorCode) +{ + OH_LOG_INFO(LOG_APP, "OnMetadataOutput errorCode = %{public}d", errorCode); +} + +MetadataOutput_Callbacks *NDKCamera::GetMetadataOutputListener(void) +{ + static MetadataOutput_Callbacks metadataOutputListener = { + .onMetadataObjectAvailable = OnMetadataObjectAvailable, + .onError = OnMetadataOutputError + }; + return &metadataOutputListener; +} + +Camera_ErrorCode NDKCamera::MetadataOutputRegisterCallback(void) +{ + ret_ = OH_MetadataOutput_RegisterCallback(metadataOutput_, GetMetadataOutputListener()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_MetadataOutput_RegisterCallback failed."); + } + return ret_; +} + +// Session Callback +void CaptureSessionOnFocusStateChange(Camera_CaptureSession *session, Camera_FocusState focusState) +{ + OH_LOG_INFO(LOG_APP, "CaptureSession_Callbacks CaptureSessionOnFocusStateChange"); + OH_LOG_INFO(LOG_APP, "CaptureSessionOnFocusStateChange"); +} + +void CaptureSessionOnError(Camera_CaptureSession *session, Camera_ErrorCode errorCode) +{ + OH_LOG_INFO(LOG_APP, "CaptureSession_Callbacks CaptureSessionOnError"); + OH_LOG_INFO(LOG_APP, "CaptureSession errorCode = %{public}d", errorCode); +} + +CaptureSession_Callbacks *NDKCamera::GetCaptureSessionRegister(void) +{ + static CaptureSession_Callbacks captureSessionCallbacks = { + .onFocusStateChange = CaptureSessionOnFocusStateChange, + .onError = CaptureSessionOnError + }; + return &captureSessionCallbacks; +} + +Camera_ErrorCode NDKCamera::CaptureSessionRegisterCallback(void) +{ + ret_ = OH_CaptureSession_RegisterCallback(captureSession_, GetCaptureSessionRegister()); + if (ret_ != CAMERA_OK) { + OH_LOG_ERROR(LOG_APP, "OH_CaptureSession_RegisterCallback failed."); + } + return ret_; +} +} // namespace OHOS_CAMERA_SAMPLE \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/camera_manager.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/camera_manager.h new file mode 100644 index 0000000000..a4a00267b9 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/camera_manager.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#ifndef AVRECORDER_CAMERA_MANAGER_H +#define AVRECORDER_CAMERA_MANAGER_H + +#include "napi/native_api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iostream" +#include "mutex" + +#include "hilog/log.h" +#include "ohcamera/camera.h" +#include "ohcamera/camera_input.h" +#include "ohcamera/capture_session.h" +#include "ohcamera/photo_output.h" +#include "ohcamera/preview_output.h" +#include "ohcamera/video_output.h" +#include "napi/native_api.h" +#include "ohcamera/camera_manager.h" +#include "log_common.h" +#include "main.h" + +namespace OHOS_CAMERA_SAMPLE { +class NDKCamera { + public: + ~NDKCamera(); + NDKCamera(uint32_t focusMode, uint32_t cameraDeviceIndex, uint32_t sceneMode, + char *previewId, char *photoId, char *videoId); + + static void Destroy() + { + if (ndkCamera_ != nullptr) { + delete ndkCamera_; + ndkCamera_ = nullptr; + } + } + + Camera_ErrorCode CreateCameraInput(void); + Camera_ErrorCode CameraInputOpen(void); + Camera_ErrorCode CameraInputClose(void); + Camera_ErrorCode CameraInputRelease(void); + Camera_ErrorCode GetSupportedCameras(void); + Camera_ErrorCode GetSupportedOutputCapability(void); + Camera_ErrorCode CreatePreviewOutput(void); + Camera_ErrorCode CreatePhotoOutput(char *photoId); + Camera_ErrorCode CreateVideoOutput(char *videoId); + Camera_ErrorCode CreateMetadataOutput(void); + Camera_ErrorCode IsCameraMuted(void); + Camera_ErrorCode PreviewOutputStop(void); + Camera_ErrorCode PreviewOutputRelease(void); + Camera_ErrorCode PhotoOutputRelease(void); + Camera_ErrorCode HasFlashFn(uint32_t mode); + Camera_ErrorCode IsVideoStabilizationModeSupportedFn(uint32_t mode); + Camera_ErrorCode setZoomRatioFn(uint32_t zoomRatio); + Camera_ErrorCode SessionFlowFn(void); + Camera_ErrorCode SessionBegin(void); + Camera_ErrorCode SessionCommitConfig(void); + Camera_ErrorCode SessionStart(void); + Camera_ErrorCode SessionStop(void); + Camera_ErrorCode StartVideo(char *videoId, char *photoId); + Camera_ErrorCode AddVideoOutput(void); + Camera_ErrorCode AddPhotoOutput(); + Camera_ErrorCode VideoOutputStart(void); + Camera_ErrorCode StartPhoto(char *mSurfaceId); + Camera_ErrorCode IsExposureModeSupportedFn(uint32_t mode); + Camera_ErrorCode IsMeteringPoint(int x, int y); + Camera_ErrorCode IsExposureBiasRange(int exposureBias); + Camera_ErrorCode IsFocusMode(uint32_t mode); + Camera_ErrorCode IsFocusPoint(float x, float y); + Camera_ErrorCode IsFocusModeSupported(uint32_t mode); + Camera_ErrorCode ReleaseCamera(void); + Camera_ErrorCode SessionRealese(void); + Camera_ErrorCode ReleaseSession(void); + int32_t GetVideoFrameWidth(void); + int32_t GetVideoFrameHeight(void); + int32_t GetVideoFrameRate(void); + Camera_ErrorCode VideoOutputStop(void); + Camera_ErrorCode VideoOutputRelease(void); + Camera_ErrorCode TakePicture(void); + Camera_ErrorCode TakePictureWithPhotoSettings(Camera_PhotoCaptureSetting photoSetting); + + // callback + Camera_ErrorCode CameraManagerRegisterCallback(void); + Camera_ErrorCode CameraInputRegisterCallback(void); + Camera_ErrorCode PreviewOutputRegisterCallback(void); + Camera_ErrorCode PhotoOutputRegisterCallback(void); + Camera_ErrorCode VideoOutputRegisterCallback(void); + Camera_ErrorCode MetadataOutputRegisterCallback(void); + Camera_ErrorCode CaptureSessionRegisterCallback(void); + + // Get callback + CameraManager_Callbacks *GetCameraManagerListener(void); + CameraInput_Callbacks *GetCameraInputListener(void); + PreviewOutput_Callbacks *GetPreviewOutputListener(void); + PhotoOutput_Callbacks *GetPhotoOutputListener(void); + VideoOutput_Callbacks *GetVideoOutputListener(void); + MetadataOutput_Callbacks *GetMetadataOutputListener(void); + CaptureSession_Callbacks *GetCaptureSessionRegister(void); + + private: + NDKCamera(const NDKCamera &) = delete; + NDKCamera &operator=(const NDKCamera &) = delete; + uint32_t cameraDeviceIndex_; + Camera_Manager *cameraManager_; + Camera_CaptureSession *captureSession_ = nullptr; + Camera_Device *cameras_; + uint32_t size_; + Camera_OutputCapability *cameraOutputCapability_; + Camera_Profile *profile_; + Camera_VideoProfile *videoProfile_; + Camera_PreviewOutput *previewOutput_ = nullptr; + Camera_PhotoOutput *photoOutput_ = nullptr; + Camera_VideoOutput *videoOutput_ = nullptr; + const Camera_MetadataObjectType *metaDataObjectType_; + Camera_MetadataOutput *metadataOutput_; + Camera_Input *cameraInput_; + bool *isCameraMuted_; + Camera_Position position_; + Camera_Type type_; + char *previewSurfaceId_; + Camera_ErrorCode ret_; + uint32_t takePictureTimes = 0; + Camera_ExposureMode exposureMode_; + bool isExposureModeSupported_; + bool isFocusModeSupported_; + float minExposureBias_; + float maxExposureBias_; + float step_; + uint32_t focusMode_; + + static NDKCamera *ndkCamera_; + static std::mutex mtx_; + volatile bool valid_; + + Camera_SceneMode sceneMode_; + char *photoSurfaceId_; + char *videoSurfaceId_; + bool isSuccess_; +}; +} // namespace OHOS_CAMERA_SAMPLE +#endif //AVRECORDER_CAMERA_MANAGER_H \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/log_common.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/log_common.h new file mode 100644 index 0000000000..1440bd5059 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/log_common.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#ifndef AVRECORDER_LOG_COMMON_H +#define AVRECORDER_LOG_COMMON_H + +#ifndef LOG_COMMON_H +#define LOG_COMMON_H +#include +#define LOG_PRINT_DOMAIN 0xFF00 +#define APP_LOG_DOMAIN 0x0001 +constexpr const char *APP_LOG_TAG = "AVRecorderSample"; +#define DRAWING_LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define DRAWING_LOGD(...) ((void)OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define DRAWING_LOGW(...) ((void)OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) +#define DRAWING_LOGE(...) ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +#endif // LOG_COMMON_H +#endif //AVRECORDER_LOG_COMMON_H \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp new file mode 100644 index 0000000000..93b79dbddb --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#include +#include "main.h" +#include "napi/native_api.h" +#include "camera_manager.h" + +#define LOG_DOMAIN 0x3200 +#define LOG_TAG "MY_NDKDEMO" +#include + +using namespace std; +using namespace OHOS_CAMERA_SAMPLE; +static NDKCamera* ndkCamera_ = nullptr; + +// 设置视频分辨率 +int videoFrameWidth = 1280; +int videoFrameHeight = 720; + +// 配置参数,可选三种录制模式 +// Type 1: 音视频录制 +void SetConfig(OH_AVRecorder_Config &config) +{ + config.audioSourceType = AVRECORDER_MIC; + config.videoSourceType = AVRECORDER_SURFACE_YUV; + + config.profile.audioBitrate = 96000; + config.profile.audioChannels = 2; + config.profile.audioCodec = AVRECORDER_AUDIO_AAC; + config.profile.audioSampleRate = 48000; + + config.profile.videoBitrate = 2000000; + config.profile.videoFrameWidth = videoFrameWidth; + config.profile.videoFrameHeight = videoFrameHeight; + config.profile.videoFrameRate = 30; + config.profile.videoCodec = AVRECORDER_VIDEO_AVC; + config.profile.isHdr = false; + config.profile.enableTemporalScale = false; + + config.profile.fileFormat = AVRECORDER_CFT_MPEG_4; + config.fileGenerationMode = AVRECORDER_AUTO_CREATE_CAMERA_SCENE; + + config.metadata.videoOrientation = (char*)malloc(2); + if (config.metadata.videoOrientation != nullptr) { + strcpy(config.metadata.videoOrientation, "90"); + } + OH_LOG_INFO(LOG_APP, "==NDKDemo== videoOrientation: %{public}s", config.metadata.videoOrientation); + + config.metadata.location.latitude = 31.791863; + config.metadata.location.longitude = 64.574687; +} + +// Type 2: 只录音频 +void SetConfigAudio(OH_AVRecorder_Config &config) +{ + config.audioSourceType = AVRECORDER_MIC; + + config.profile.audioBitrate = 32000; + config.profile.audioChannels = 2; + config.profile.audioCodec = AVRECORDER_AUDIO_AAC; + config.profile.audioSampleRate = 8000; + + config.profile.fileFormat = AVRECORDER_CFT_AAC; + config.fileGenerationMode = AVRECORDER_APP_CREATE; + + config.metadata.location.latitude = 31.791863; + config.metadata.location.longitude = 64.574687; +} + +// Type 3: 只录视频 +void SetConfigVideo(OH_AVRecorder_Config &config) +{ + config.videoSourceType = AVRECORDER_SURFACE_YUV; + + config.profile.videoBitrate = 2000000; + config.profile.videoFrameWidth = videoFrameWidth; + config.profile.videoFrameHeight = videoFrameHeight; + config.profile.videoFrameRate = 30; + config.profile.videoCodec = AVRECORDER_VIDEO_AVC; + config.profile.isHdr = false; + config.profile.enableTemporalScale = false; + + config.profile.fileFormat = AVRECORDER_CFT_MPEG_4; + config.fileGenerationMode = AVRECORDER_APP_CREATE; + + config.metadata.videoOrientation = (char*)malloc(2); + if (config.metadata.videoOrientation != nullptr) { + strcpy(config.metadata.videoOrientation, "90"); + } + OH_LOG_INFO(LOG_APP, "==NDKDemo== videoOrientation: %{public}s", config.metadata.videoOrientation); + + config.metadata.location.latitude = 31.791863; + config.metadata.location.longitude = 64.574687; +} + +// 设置状态回调 +void OnStateChange(OH_AVRecorder *recorder, OH_AVRecorder_State state, + OH_AVRecorder_StateChangeReason reason, void *userData) { + (void)recorder; + (void)userData; + + // 将 reason 转换为字符串表示 + const char *reasonStr = (reason == AVRECORDER_USER) ? "USER" : (reason == AVRECORDER_BACKGROUND) ? "BACKGROUND" : "UNKNOWN"; + + if (state == AVRECORDER_IDLE) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange IDLE, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_PREPARED) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange PREPARED, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_STARTED) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange STARTED, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_PAUSED) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange PAUSED, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_STOPPED) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange STOPPED, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_RELEASED) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange RELEASED, reason: %{public}s", reasonStr); + // 处理状态变更 + } + if (state == AVRECORDER_ERROR) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnStateChange ERROR, reason: %{public}s", reasonStr); + // 处理状态变更 + } +} + +// 设置错误回调 +void OnError(OH_AVRecorder *recorder, int32_t errorCode, const char *errorMsg, void *userData) { + (void)recorder; + (void)userData; + OH_LOG_INFO(LOG_APP, "==NDKDemo== Recorder OnError errorCode: %{public}d, error message: %{public}s", + errorCode, errorMsg); +} + +// 设置生成媒体文件回调 +void OnUri(OH_AVRecorder *recorder, OH_MediaAsset *asset, void *userData) { + (void)recorder; + (void)userData; + OH_LOG_INFO(LOG_APP, "==NDKDemo== OnUri in!"); + if (asset != nullptr) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== OH_MediaAsset: %p", asset); + auto changeRequest = OH_MediaAssetChangeRequest_Create(asset); + OH_LOG_INFO(LOG_APP, "==NDKDemo== changeRequest: %p", changeRequest); + if (changeRequest == nullptr) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== changeRequest is null!"); + return; + } + OH_LOG_INFO(LOG_APP, "==NDKDemo== changeRequest is not null!"); + MediaLibrary_ImageFileType imageFileType = MEDIA_LIBRARY_FILE_VIDEO; + + uint32_t result = OH_MediaAssetChangeRequest_SaveCameraPhoto(changeRequest, imageFileType); + OH_LOG_INFO(LOG_APP, "result of OH_MediaAssetChangeRequest_SaveCameraPhoto: %d", result); + uint32_t resultChange = OH_MediaAccessHelper_ApplyChanges(changeRequest); + OH_LOG_INFO(LOG_APP, "result of OH_MediaAccessHelper_ApplyChanges: %d", resultChange); + OH_MediaAsset_Release(asset); + OH_MediaAssetChangeRequest_Release(changeRequest); + } else { + OH_LOG_ERROR(LOG_APP, "Received null media asset!"); + } + OH_LOG_INFO(LOG_APP, "==NDKDemo== OnUri out!"); +} + +// 1.准备录制 +static napi_value PrepareAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + OH_LOG_INFO(LOG_APP, "==NDKDemo== PrepareAVRecorder in!"); + g_avRecorder = OH_AVRecorder_Create(); + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder Create OK! g_avRecorder: %{public}p", g_avRecorder); + if (g_avRecorder == nullptr) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Create failed!"); + } + OH_AVRecorder_Config *config = new OH_AVRecorder_Config(); + + SetConfig(*config); + + // 1.1 设置URL + const std::string AVREORDER_ROOT = "/data/storage/el2/base/files/"; + int32_t outputFd = open((AVREORDER_ROOT + "avrecorder01.mp4").c_str(), O_RDWR | O_CREAT, 0777); // 设置文件名 + std::string fileUrl = "fd://" + std::to_string(outputFd); + config->url = const_cast(fileUrl.c_str()); + OH_LOG_INFO(LOG_APP, "config.url is: %s", const_cast(fileUrl.c_str())); + std::cout<< "config.url is:" << config->url << std::endl; + + // 1.2 回调 + // 状态回调 + OH_AVRecorder_SetStateCallback(g_avRecorder, OnStateChange, nullptr); + // 错误回调 + OH_AVRecorder_SetErrorCallback(g_avRecorder, OnError, nullptr); + // 生成媒体文件回调 + OH_LOG_INFO(LOG_APP, "==NDKDemo== OH_AVRecorder_SetUriCallback in!"); + OH_AVErrCode ret = OH_AVRecorder_SetUriCallback(g_avRecorder, OnUri, nullptr); + OH_LOG_INFO(LOG_APP, "==NDKDemo== OH_AVRecorder_SetUriCallback out!"); + if (ret == AV_ERR_OK) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== OH_AVRecorder_SetUriCallback succeed!"); + } else { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== Failed to set URI callback, error code: %d", ret); + } + + // 1.3 prepare接口 + int result = OH_AVRecorder_Prepare(g_avRecorder, config); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Prepare failed %{public}d", result); + } + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder enableTemporalScale:%{public}d", static_cast(config->profile.enableTemporalScale)); + + // 1.4 更新视频旋转角度 OH_AVRecorder_UpdateRotation + char *videoOrientation = config->metadata.videoOrientation; + if (videoOrientation == nullptr || videoOrientation[0] == '\0') { + OH_LOG_INFO(LOG_APP, "==NDKDemo== videoOrientation is null or empty!"); + napi_value res; + napi_create_int32(env, -1, &res); + return res; + } + // 使用 strtol 进行字符串到整数的转换,并进行错误检查 + char *end; + int32_t rotation= static_cast(strtol(videoOrientation, &end, 10)); // 基数 10 表示十进制 + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder rotation: %{public}d", rotation); + + // 检查转换是否成功 + if (end == videoOrientation || *end != '\0') { + // 转换失败,videoOrientation 包含无效的整数 + OH_LOG_INFO(LOG_APP, "Invalid rotation value: %s", videoOrientation); + } + // 选择是否更新旋转角度 + int32_t rotation = 90; + result = OH_AVRecorder_UpdateRotation(g_avRecorder, rotation); + if (result != AV_ERR_OK) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder UpdateRotation failed! ret=%{public}d", result); + } else { + OH_LOG_INFO(LOG_APP, "==NDKDemo== OH_AVRecorder_UpdateRotation: %{public}d", rotation); + } + + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 获取实时配置参数 +static napi_value GetAVRecorderConfig(napi_env env, napi_callback_info info) +{ + (void)info; + +// OH_AVRecorder_Config *config = new OH_AVRecorder_Config(); +// SetConfig(*config); + + OH_AVRecorder_Config *config = nullptr; + + int result = OH_AVRecorder_GetAVRecorderConfig(g_avRecorder, &config); + if (result != AV_ERR_OK || config == nullptr) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== Get AVRecorder Config failed %{public}d", result); + napi_value res; + napi_create_int32(env, result, &res); + return res; + } + + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoOrientation: %{public}s", config->metadata.videoOrientation); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig audioSourceType: %{public}d", config->audioSourceType); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoSourceType: %{public}d", config->videoSourceType); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig audioBitrate: %{public}d", config->profile.audioBitrate); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig audioChannels: %{public}d", config->profile.audioChannels); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig audioCodec: %{public}d", config->profile.audioCodec); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig audioSampleRate: %{public}d", config->profile.audioSampleRate); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig fileFormat: %{public}d", config->profile.fileFormat); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoBitrate: %{public}d", config->profile.videoBitrate); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoCodec: %{public}d", config->profile.videoCodec); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoFrameWidth: %{public}d", config->profile.videoFrameWidth); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoFrameHeight: %{public}d", config->profile.videoFrameHeight); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig videoFrameRate: %{public}d", config->profile.videoFrameRate); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig latitude: %{public}.6f", config->metadata.location.latitude); + OH_LOG_INFO(LOG_APP, "==NDKDemo== GetAVRecorderConfig longitude: %{public}.6f", config->metadata.location.longitude); + + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 获取录制支持的编码信息 +static napi_value GetAvailableEncoder(napi_env env, napi_callback_info info) +{ + (void)info; + + OH_AVRecorder_EncoderInfo *encoderInfo = nullptr; + int32_t lengthValue = 0; // 定义一个实际的 int32_t 变量 + int32_t *length = &lengthValue; + + int result = OH_AVRecorder_GetAvailableEncoder(g_avRecorder, &encoderInfo, length); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== GetAvailableEncoder failed %{public}d", result); + } else { + // 打印 encoderInfo 的内容 + if (encoderInfo != nullptr) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== Encoder Info in!"); + + // 打印 mimeType (假设是一个枚举类型或可转为字符串的类型) + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder MIME Type: %{public}d", encoderInfo->mimeType); + + // 打印 type + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder Type: %{public}s", encoderInfo->type); + + // 打印 bitRate 范围 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder BitRate Min: %{public}d, Max: %{public}d", encoderInfo->bitRate.min, encoderInfo->bitRate.max); + + // 打印 frameRate 范围 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder FrameRate Min: %{public}d, Max: %{public}d", encoderInfo->frameRate.min, encoderInfo->frameRate.max); + + // 打印 width 范围 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder Width Min: %{public}d, Max: %{public}d", encoderInfo->width.min, encoderInfo->width.max); + + // 打印 height 范围 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder Height Min: %{public}d, Max: %{public}d", encoderInfo->height.min, encoderInfo->height.max); + + // 打印 channels 范围 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder Channels Min: %{public}d, Max: %{public}d", encoderInfo->channels.min, encoderInfo->channels.max); + + // 打印 sampleRate 列表和长度 + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder SampleRate Length: %{public}d", encoderInfo->sampleRateLen); + if (encoderInfo->sampleRate != nullptr) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== SampleRates: "); + for (int i = 0; i < encoderInfo->sampleRateLen; i++) { + OH_LOG_INFO(LOG_APP, " ==NDKDemo== GetAvailableEncoder SampleRate: %{public}d", i, encoderInfo->sampleRate[i]); + } + } + } else { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== EncoderInfo is null"); + } + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 2. 启动相机 +static napi_value PrepareCamera(napi_env env, napi_callback_info info) +{ + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder PrepareCamera"); + (void)info; + + // 2.1 相机初始化(init) + + size_t argc = 6; + napi_value args[6] = {nullptr}; + size_t typeLen = 0; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + int32_t focusMode; + napi_get_value_int32(env, args[0], &focusMode); + + uint32_t cameraDeviceIndex; + napi_get_value_uint32(env, args[1], &cameraDeviceIndex); + + uint32_t sceneMode; + napi_get_value_uint32(env, args[2], &sceneMode); + + char* previewId = nullptr; + napi_get_value_string_utf8(env, args[3], nullptr, 0, &typeLen); + previewId = new char[typeLen + 1]; + napi_get_value_string_utf8(env, args[3], previewId, typeLen + 1, &typeLen); + + char* photoId = nullptr; + napi_get_value_string_utf8(env, args[4], nullptr, 0, &typeLen); + photoId = new char[typeLen + 1]; + napi_get_value_string_utf8(env, args[4], photoId, typeLen + 1, &typeLen); + + // 获取surfaceID + OHNativeWindow *window = nullptr; + + int resultCode = OH_AVRecorder_GetInputSurface(g_avRecorder, &window); + if (resultCode != AV_ERR_OK || window == nullptr) { + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder GetInputSurface failed! resultCode=%{public}d, window=%{public}p", resultCode, window); + napi_value errorResult; + napi_create_int32(env, -1, &errorResult); // -1 表示错误 + return errorResult; + } else { + // 打印 window 地址以确认是否分配成功 + OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder GetInputSurface succeeded! window address: %{public}p", window); + } + + uint64_t surfaceId; + OH_NativeWindow_GetSurfaceId(window, &surfaceId); + char videoId[30]; + OH_LOG_ERROR(LOG_APP, "InitCamera focusMode : %{public}d", focusMode); + OH_LOG_ERROR(LOG_APP, "InitCamera cameraDeviceIndex : %{public}d", cameraDeviceIndex); + OH_LOG_ERROR(LOG_APP, "InitCamera sceneMode : %{public}d", sceneMode); + OH_LOG_ERROR(LOG_APP, "InitCamera previewId : %{public}s", previewId); + OH_LOG_ERROR(LOG_APP, "InitCamera photoId : %{public}s", photoId); + OH_LOG_ERROR(LOG_APP, "InitCamera videoId : %{public}s", videoId); + + ndkCamera_ = new NDKCamera(focusMode, cameraDeviceIndex, sceneMode, previewId, photoId, videoId); + OH_LOG_INFO(LOG_APP, "InitCamera End"); + + int result = 0; + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 3. 开始录制 +static napi_value StartAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + OH_LOG_INFO(LOG_APP, "==NDKDemo== g_avRecorder start: %{public}p", g_avRecorder); + int result = OH_AVRecorder_Start(g_avRecorder); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Start failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 4. 暂停录制 +static napi_value PauseAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + int result = OH_AVRecorder_Pause(g_avRecorder); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Pause failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 5. 恢复录制 +static napi_value ResumeAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + int result = OH_AVRecorder_Resume(g_avRecorder); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Resume failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 6. 停止录制 +static napi_value StopAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + OH_LOG_INFO(LOG_APP, "==NDKDemo== g_avRecorder stop: %{public}p", g_avRecorder); + int result = OH_AVRecorder_Stop(g_avRecorder); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Stop failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 7. 重置录制 +static napi_value ResetAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + // 检查 g_avRecorder 是否有效 + if (g_avRecorder == nullptr) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== g_avRecorder is nullptr!"); + napi_value res; + napi_create_int32(env, AV_ERR_INVALID_VAL, &res); + return res; + } + + int result = OH_AVRecorder_Reset(g_avRecorder); + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Reset failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +// 8. 释放录制资源 +static napi_value ReleaseAVRecorder(napi_env env, napi_callback_info info) +{ + (void)info; + // 检查 g_avRecorder 是否有效 + if (g_avRecorder == nullptr) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== g_avRecorder is nullptr!"); + napi_value res; + napi_create_int32(env, AV_ERR_INVALID_VAL, &res); + return res; + } + + int result = OH_AVRecorder_Release(g_avRecorder); + g_avRecorder = nullptr; // 释放录制资源后,需要显式地将g_avRecorder指针置空 + + if (result != AV_ERR_OK) { + OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder Release failed %{public}d", result); + } + napi_value res; + napi_create_int32(env, result, &res); + return res; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { + {"prepareAVRecorder", nullptr, PrepareAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"getAVRecorderConfig", nullptr, GetAVRecorderConfig, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"getAvailableEncoder", nullptr, GetAvailableEncoder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"prepareCamera", nullptr, PrepareCamera, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"startAVRecorder", nullptr, StartAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"pauseAVRecorder", nullptr, PauseAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"resumeAVRecorder", nullptr, ResumeAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"stopAVRecorder", nullptr, StopAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"resetAVRecorder", nullptr, ResetAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"releaseAVRecorder", nullptr, ReleaseAVRecorder, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.h new file mode 100644 index 0000000000..8a4605c259 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#ifndef AVRECORDER_MAIN_H +#define AVRECORDER_MAIN_H + +#include "napi/native_api.h" +#include "hilog/log.h" +#include "mutex" +#include "muxer.h" +#include "ohcamera/camera.h" +#include "ohcamera/camera_input.h" +#include "ohcamera/capture_session.h" +#include "ohcamera/photo_output.h" +#include "ohcamera/preview_output.h" +#include "ohcamera/video_output.h" +#include "ohcamera/camera_manager.h" +#include "native_window/external_window.h" +#include "video_encoder_sample.h" +#include +#include +#include +#include +#include +#include "multimedia/player_framework/native_avcodec_videoencoder.h" +#include "multimedia/media_library/media_asset_change_request_capi.h" +#include "multimedia/media_library/media_access_helper_capi.h" +#include "multimedia/media_library/media_asset_capi.h" + +static struct OH_AVRecorder *g_avRecorder = {}; +OH_AVRecorder* InitRecorder(); +void StartRecording(OH_AVRecorder *recorder); + +extern int videoFrameWidth; +extern int videoFrameHeight; + +#endif //AVRECORDER_MAIN_H \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.cpp b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.cpp new file mode 100644 index 0000000000..7aebe1b616 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#include "muxer.h" +#include "hilog/log.h" + +#undef LOG_TAG +#define LOG_TAG "Muxer" + +namespace { +constexpr int32_t VERTICAL_ANGLE = 90; +constexpr int32_t HORIZONTAL_ANGLE = 0; +} + +Muxer::~Muxer() +{ + Release(); +} + +int32_t Muxer::Create(int32_t fd) +{ + muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_MPEG_4); + return 0; +} + +int32_t Muxer::Config(SampleInfo &sampleInfo) +{ + OH_LOG_INFO(LOG_APP, "==DEMO== Config"); + OH_AVFormat *formatVideo = OH_AVFormat_CreateVideoFormat(sampleInfo.codecMime.data(), + sampleInfo.videoWidth, sampleInfo.videoHeight); + + OH_AVFormat_SetDoubleValue(formatVideo, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, sampleInfo.videoWidth); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight); + OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, sampleInfo.codecMime.data()); + + int32_t ret = OH_AVMuxer_AddTrack(muxer_, &videoTrackId_, formatVideo); // ! + OH_AVFormat_Destroy(formatVideo); + + OH_LOG_INFO(LOG_APP, "==DEMO== Config End"); + return 0; +} + +int32_t Muxer::Start() +{ + int ret = OH_AVMuxer_Start(muxer_); + return 0; +} + +int32_t Muxer::WriteSample(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr) +{ + OH_LOG_INFO(LOG_APP, "==DEMO== WriteSample"); + int32_t ret = OH_AVBuffer_SetBufferAttr(buffer, &attr); + ret = OH_AVMuxer_WriteSampleBuffer(muxer_, videoTrackId_, buffer); + return 0; +} + +int32_t Muxer::WriteAudioData(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr) +{ + OH_LOG_INFO(LOG_APP, "==DEMO== WriteAudioSample"); + int32_t ret = OH_AVBuffer_SetBufferAttr(buffer, &attr); + ret = OH_AVMuxer_WriteSampleBuffer(muxer_, audioTrackId_, buffer); + return 0; +} + +int32_t Muxer::Stop() +{ + int32_t ret = OH_AVMuxer_Stop(muxer_); + return 0; +} + +int32_t Muxer::Release() +{ + if (muxer_ != nullptr) { + OH_AVMuxer_Destroy(muxer_); + muxer_ = nullptr; + } + return 0; +} diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.h new file mode 100644 index 0000000000..603cc602a3 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/muxer.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +#ifndef MUXER_H +#define MUXER_H + +#define LOG_DOMAIN 0x3200 +#define LOG_TAG "MY_NDKDEMO" +#include +#include "sample_info.h" +#include "multimedia/player_framework/native_avmuxer.h" + +class Muxer { +public: + Muxer() = default; + ~Muxer(); + + int32_t Create(int32_t fd); + int32_t Config(SampleInfo &sampleInfo); + int32_t Start(); + int32_t WriteSample(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr); + int32_t WriteAudioData(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr); + + int32_t Stop(); + int32_t Release(); + +private: + OH_AVMuxer *muxer_ = nullptr; + int32_t videoTrackId_ = -1; + int32_t audioTrackId_ = -1; + int32_t coverTrackId_ = -1; +}; + +#endif // MUXER_H \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/sample_info.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/sample_info.h new file mode 100644 index 0000000000..d9a20dadaf --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/sample_info.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ + +#ifndef AVCODEC_SAMPLE_INFO_H +#define AVCODEC_SAMPLE_INFO_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ANNEXB_INPUT_ONLY 1 + +const std::string_view MIME_VIDEO_AVC = "video/avc"; +const std::string_view MIME_VIDEO_HEVC = "video/hevc"; + +constexpr int32_t BITRATE_10M = 10 * 1024 * 1024; // 10Mbps +constexpr int32_t BITRATE_20M = 20 * 1024 * 1024; // 20Mbps +constexpr int32_t BITRATE_30M = 30 * 1024 * 1024; // 30Mbps + +struct SampleInfo { + int32_t sampleId = 0; + + int32_t inputFd = -1; + int32_t outFd = -1; + int64_t inputFileOffset = 0; + int64_t inputFileSize = 0; + std::string inputFilePath; + std::string outputFilePath; + std::string videoCodecMime = ""; + std::string audioCodecMime = ""; + std::string codecMime = MIME_VIDEO_AVC.data(); + int32_t videoWidth = 0; + int32_t videoHeight = 0; + double frameRate = 0.0; + int64_t bitrate = 10 * 1024 * 1024; // 10Mbps; + int64_t frameInterval = 0; + int32_t perfmode = 0; + int64_t durationTime = 0; + uint32_t maxFrames = UINT32_MAX; + int32_t isHDRVivid = 0; + uint32_t repeatTimes = 1; + OH_AVPixelFormat pixelFormat = AV_PIXEL_FORMAT_NV12; + bool needDumpOutput = false; + uint32_t bitrateMode = CBR; + int32_t hevcProfile = HEVC_PROFILE_MAIN; + int32_t rotation = 0; + OHNativeWindow *window = nullptr; + + + int32_t sampleRate = 44100; + int32_t channelCount = 2; + + uint32_t bufferSize = 0; + double readTime = 0; + double memcpyTime = 0; + double writeTime = 0; + void (*PlayDoneCallback)(void *context) = nullptr; + void *playDoneCallbackData = nullptr; +}; + +struct CodecBufferInfo { + uint32_t bufferIndex = 0; + uintptr_t *buffer = nullptr; + uint8_t *bufferAddr = nullptr; + OH_AVCodecBufferAttr attr = {0, 0, 0, AVCODEC_BUFFER_FLAGS_NONE}; + + CodecBufferInfo(uint8_t *addr) : bufferAddr(addr){}; + CodecBufferInfo(uint8_t *addr, int32_t bufferSize) + : bufferAddr(addr), attr({0, bufferSize, 0, AVCODEC_BUFFER_FLAGS_NONE}){}; + CodecBufferInfo(uint32_t argBufferIndex, OH_AVMemory *argBuffer, OH_AVCodecBufferAttr argAttr) + : bufferIndex(argBufferIndex), buffer(reinterpret_cast(argBuffer)), attr(argAttr){}; + CodecBufferInfo(uint32_t argBufferIndex, OH_AVMemory *argBuffer) + : bufferIndex(argBufferIndex), buffer(reinterpret_cast(argBuffer)){}; + CodecBufferInfo(uint32_t argBufferIndex, OH_AVBuffer *argBuffer) + : bufferIndex(argBufferIndex), buffer(reinterpret_cast(argBuffer)) { + OH_AVBuffer_GetBufferAttr(argBuffer, &attr); + }; +}; + +class AEncBufferSignal { +public: + std::mutex inMutex_; + std::mutex outMutex_; + std::mutex startMutex_; + std::condition_variable inCond_; + std::condition_variable outCond_; + std::condition_variable startCond_; + std::queue inQueue_; + std::queue outQueue_; + std::queue inBufferQueue_; + std::queue outBufferQueue_; + OH_AVCodecBufferAttr audioInfo = {0, 0, 0, AVCODEC_BUFFER_FLAGS_NONE}; +}; + +class CodecUserData { +public: + SampleInfo *sampleInfo = nullptr; + + uint32_t inputFrameCount_ = 0; + std::mutex inputMutex_; + std::condition_variable inputCond_; + std::queue inputBufferInfoQueue_; + + uint32_t outputFrameCount_ = 0; + std::mutex outputMutex_; + std::condition_variable outputCond_; + std::queue outputBufferInfoQueue_; + AEncBufferSignal *signal_; + + void ClearQueue() { + { + std::unique_lock lock(inputMutex_); + auto emptyQueue = std::queue(); + inputBufferInfoQueue_.swap(emptyQueue); + } + { + std::unique_lock lock(outputMutex_); + auto emptyQueue = std::queue(); + outputBufferInfoQueue_.swap(emptyQueue); + } + } +}; + +#endif // AVCODEC_SAMPLE_INFO_H \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/index.d.ts b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/index.d.ts new file mode 100644 index 0000000000..58387bb645 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/index.d.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ + +export const prepareAVRecorder: () => number; + +export const getAVRecorderConfig: () => number; + +export const getAvailableEncoder: () => number; + +export const prepareCamera: (focusMode: number, cameraDeviceIndex: number, sceneMode: number, + previewId: string, photoId: string, videoId: string) => number; + +export const startAVRecorder: () => number; + +export const pauseAVRecorder: () => number; + +export const resumeAVRecorder: () => number; + +export const stopAVRecorder: () => number; + +export const resetAVRecorder: () => number; + +export const releaseAVRecorder: () => number; + + + diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/oh-package.json5 b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000000..ce82a4c96b --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ +{ + "name": "libavrecorderndk.so", + "types": "./index.d.ts", + "version": "", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.cpp b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.cpp new file mode 100644 index 0000000000..aca43bbabb --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ + +#include "video_encoder_sample.h" +#include "hilog/log.h" + +namespace { +constexpr int LIMIT_LOGD_FREQUENCY = 50; +} + +// 设置 OnError 回调函数 +void SampleCallback::OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) { + // 回调的错误码由用户判断处理 + (void)codec; + (void)errorCode; + (void)userData; + OH_LOG_ERROR(LOG_APP, "On error, error code: %{public}d", errorCode); +} + +// 设置 OnStreamChanged 回调函数 +void SampleCallback::OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) { +// surface模式下,该回调函数无作用 + (void)codec; + (void)format; + (void)userData; +} + +// 设置 OH_AVCodecOnNeedInputBuffer 回调函数,编码输入帧送入数据队列 +void SampleCallback::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { +// surface模式下,该回调函数无作用,用户通过获取的surface输入数据 + OH_LOG_INFO(LOG_APP, "==DEMO== Video OnNeedInputBuffer"); + + (void)userData; + (void)index; + (void)buffer; + if (userData == nullptr) { + return; + } + (void)codec; + CodecUserData *codecUserData = static_cast(userData); + std::unique_lock lock(codecUserData->inputMutex_); + codecUserData->inputBufferInfoQueue_.emplace(index, buffer); + codecUserData->inputCond_.notify_all(); +} + +// 设置 OH_AVCodecOnNewOutputBuffer 回调函数,编码完成帧送入输出队列 +void SampleCallback::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { + // 完成帧buffer对应的index,送入outIndexQueue队列 + // 完成帧的数据buffer送入outBufferQueue队列 + // 数据处理,请参考: + // - 释放编码帧 + OH_LOG_INFO(LOG_APP, "==DEMO== Video OnNewOutputBuffer"); + if (userData == nullptr) { + return; + } + (void)codec; + CodecUserData *codecUserData = static_cast(userData); + std::unique_lock lock(codecUserData->outputMutex_); + codecUserData->outputBufferInfoQueue_.emplace(index, buffer); + codecUserData->outputCond_.notify_all(); +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.h b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.h new file mode 100644 index 0000000000..0324949f8b --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/video_encoder_sample.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ + +#ifndef AVCODEC_SAMPLE_CALLBACK_H +#define AVCODEC_SAMPLE_CALLBACK_H +#define LOG_DOMAIN 0x3200 +#define LOG_TAG "MY_NDKDEMO" + +#include "sample_info.h" +#include +class SampleCallback { +public: + SampleCallback() {} + SampleCallback(SampleCallback *p1) {} + static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData); + static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData); + static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData); + static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData); + + OH_AVMuxer *muxer_ = nullptr; + int32_t g_videoTrackId = -1; +}; + +#endif //AVCODEC_SAMPLE_CALLBACK_H diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entryability/EntryAbility.ets b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000..6ff5e236f7 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 Huawei Device 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 { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + let permissionNames: Array = ['ohos.permission.MEDIA_LOCATION', 'ohos.permission.READ_MEDIA', + 'ohos.permission.WRITE_MEDIA', 'ohos.permission.CAMERA', 'ohos.permission.MICROPHONE']; + globalThis.abilityWant = this.launchWant; + globalThis.abilityContext = this.context; + let atManager = abilityAccessCtrl.createAtManager(); + atManager.requestPermissionsFromUser(globalThis.abilityContext, permissionNames).then((data) => { + hilog.info(0x0000, 'testTag', '%{public}s', 'requestPermissionsFromUser called'); + }); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +}; diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000..20b967c87b --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,28 @@ + +/* + * Copyright (c) 2025 Huawei Device 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 { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(0x0000, 'testTag', 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + } +} diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/ets/pages/Index.ets b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000..a561b00e06 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2025 Huawei Device 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 { hilog } from '@kit.PerformanceAnalysisKit'; +import testNapi from 'libentry.so'; +import camera from '@ohos.multimedia.camera'; +import image from '@ohos.multimedia.image'; +import media from '@ohos.multimedia.media'; +import { BusinessError } from'@kit.BasicServicesKit'; +import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; +const permissions: Array = ['ohos.permission.CAMERA']; + +const componentType_Jpeg = image.ComponentType.JPEG + +@Entry +@Component +export struct Index { + @State message: string = 'Hello World'; + // XComponentController + private mXComponentController: XComponentController = new XComponentController(); + public videoRecorder?: media.AVRecorder; + // surfaceID value + @State focusMode: number = 2; + @State cameraDeviceIndex: number = 0; + @State sceneMode: number = 2; + @State previewId: string = ''; + @State photoId: string = ''; + @State videoId: string = ''; + @State xComponentWidth: number = 384; + @State xComponentHeight: number = 450; + + build() { + Column() { + XComponent({ + id: 'componentId', + type: XComponentType.SURFACE, + controller: this.mXComponentController + }) + .onLoad(async () => { + this.previewId = this.mXComponentController.getXComponentSurfaceId(); + hilog.info(0x0000, 'testTag', 'previewId: %{public}s', this.previewId); + + hilog.info(0x0000, 'testTag', 'Prepare AVRecorder %{public}d', testNapi.prepareAVRecorder()); + + hilog.info(0x0000, 'testTag', 'prepare Camera %{public}d', testNapi.prepareCamera(this.focusMode, this.cameraDeviceIndex, + this.sceneMode, this.previewId, this.photoId, this.videoId)); + }) + .onDestroy(() => { + // 组件销毁时释放录制资源 + hilog.info(0x0000, 'testTag', 'Application is closing, perform cleanup.'); + testNapi.releaseAVRecorder(); + }) + .backgroundColor(Color.Black) + .width('100%') + .height('70%') + Flex({ direction: FlexDirection.Row, wrap:FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly, + alignItems: ItemAlign.Center, alignContent:FlexAlign.Center }) { + + // ** 可选: 获取实时录制参数 ** + Button($r('app.string.GetConfig'), { type: ButtonType.Circle, stateEffect: true }) + .id("GetConfig") + .backgroundColor(0xF55A42) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Get AVRecorder Config %{public}d', testNapi.getAVRecorderConfig()); + }) + + // ** 可选: 获取编码信息 ** + Button($r('app.string.GetInfo'), { type: ButtonType.Circle, stateEffect: true }) + .id("GetInfo") + .backgroundColor(0xF55A42) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Get AVRecorder EncoderInfo %{public}d', testNapi.getAvailableEncoder()); + }) + } + Flex({ direction: FlexDirection.Row, wrap:FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly, + alignItems: ItemAlign.Center, alignContent:FlexAlign.Center }){ + Button($r('app.string.Prepare'), { type: ButtonType.Circle, stateEffect: true }) + .id("Prepare") + .backgroundColor(Color.Black) + .width(65) + .height(65) + .onClick(() => { + this.previewId = this.mXComponentController.getXComponentSurfaceId(); + hilog.info(0x0000, 'testTag', 'dztdztdzt previewId: %{public}s', this.previewId); + hilog.info(0x0000, 'testTag', 'Prepare AVRecorder %{public}d', testNapi.prepareAVRecorder()); + hilog.info(0x0000, 'testTag', 'prepare Camera %{public}d', testNapi.prepareCamera(this.focusMode, this.cameraDeviceIndex, + this.sceneMode, this.previewId, this.photoId, this.videoId)); + hilog.info(0x0000, 'testTag', 'Start AVRecorder %{public}d', testNapi.startAVRecorder()); + }) + Button($r('app.string.Start'), { type: ButtonType.Circle, stateEffect: true }) + .id("Start") + .backgroundColor(0xF55A42) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'After prepare surfaceId: %{public}s', this.videoId); + hilog.info(0x0000, 'testTag', 'Start AVRecorder %{public}d', testNapi.startAVRecorder()); + }) + Button($r('app.string.Pause'), { type: ButtonType.Normal, stateEffect: true }) + .id("Pause") + .backgroundColor(0x4287F5) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Pause AVRecorder %{public}d', testNapi.pauseAVRecorder()); + }) + Button($r('app.string.Resume'), { type: ButtonType.Capsule, stateEffect: true }) + .id("Resume") + .backgroundColor(0x4287F5) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Pause AVRecorder %{public}d', testNapi.resumeAVRecorder()); + }) + } + Flex({ direction: FlexDirection.Row, wrap:FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly, + alignItems: ItemAlign.Center, alignContent:FlexAlign.Center }){ + Button($r('app.string.Stop'), { type: ButtonType.Capsule, stateEffect: true }) + .id("Stop") + .backgroundColor(0x4287F5) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Stop AVRecorder %{public}d', testNapi.stopAVRecorder()); + }) + Button($r('app.string.Reset'), { type: ButtonType.Capsule, stateEffect: true }) + .id("Reset") + .backgroundColor(0x4287F5) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Reset AVRecorder %{public}d', testNapi.resetAVRecorder()); + }) + Button($r('app.string.Release'), { type: ButtonType.Capsule, stateEffect: true }) + .id("Release") + .backgroundColor(0x4287F5) + .width(65) + .height(65) + .onClick(() => { + hilog.info(0x0000, 'testTag', 'Release AVRecorder %{public}d', testNapi.releaseAVRecorder()); + }) + } + } + .width('100%') + .height('100%') + } +} diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/module.json5 b/code/DocsSample/Media/AVRecorder/entry/src/main/module.json5 new file mode 100644 index 0000000000..c1964ea1db --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/module.json5 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.INTERNET", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.CAMERA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.MEDIA_LOCATION", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.READ_MEDIA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.WRITE_MEDIA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + } + ] + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/color.json b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000..3c712962da --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/string.json b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000..4d2dc3eec6 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/element/string.json @@ -0,0 +1,56 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVRecorderSample" + }, + { + "name": "sample_label", + "value": "AVRecorderSample" + }, + { + "name": "GetConfig", + "value": "GetConfig" + }, + { + "name": "GetInfo", + "value": "GetInfo" + }, + { + "name": "Prepare", + "value": "Prepare" + }, + { + "name": "Start", + "value": "Start" + }, + { + "name": "Pause", + "value": "Pause" + }, + { + "name": "Resume", + "value": "Resume" + }, + { + "name": "Stop", + "value": "Stop" + }, + { + "name": "Reset", + "value": "Reset" + }, + { + "name": "Release", + "value": "Release" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/icon.png b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path" +fi + +# Check hvigor wrapper script +if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then + fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}" +fi + +# start hvigor-wrapper script +exec "${EXECUTABLE_NODE}" \ + "${HVIGOR_WRAPPER_SCRIPT}" "$@" diff --git a/code/DocsSample/Media/AVRecorder/hvigorw.bat b/code/DocsSample/Media/AVRecorder/hvigorw.bat new file mode 100644 index 0000000000..d77138670c --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/hvigorw.bat @@ -0,0 +1,63 @@ +@rem Copyright (C) 2025 Huawei Device Co., Ltd. +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@echo off +@rem +@rem ------------------------------------------------------------------- +@rem Hvigor startup script for Windows, version 1.0.0 +@rem +@rem Required ENV vars: +@rem ------------------ +@rem NODE_HOME - location of a Node home dir +@rem or +@rem Add %NODE_HOME%/bin to the PATH environment variable +@rem ------------------------------------------------------------------- +@rem + +set DIRNAME=%~dp0 +set APP_BASE_NAME=%~n0 +set NODE_EXE_PATH="" +set APP_HOME=. +set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js +set NODE_EXE=node.exe +@rem set NODE_OPTS="--max-old-space-size=4096" + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +if not defined NODE_OPTS set NODE_OPTS="--" + +@rem Find node.exe +if defined NODE_HOME ( + set NODE_HOME=%NODE_HOME:"=% + set "PATH=%PATH%;%NODE_HOME%" + set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE% +) + +%NODE_EXE% --version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" ( + "%NODE_EXE%" "%NODE_OPTS%" "%WRAPPER_MODULE_PATH%" %* +) else if exist "%NODE_EXE_PATH%" ( + "%NODE_EXE_PATH%" "%NODE_OPTS%" "%WRAPPER_MODULE_PATH%" %* +) else ( + echo. + echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH. + echo. + echo Please set the NODE_HOME variable in your environment to match the + echo location of your NodeJs installation. +) + +if "%ERRORLEVEL%" == "0"( + if "%OS%" == "Windows_NT" endlocal +) else ( + exit /b %ERRORLEVEL% +) diff --git a/code/DocsSample/Media/AVRecorder/oh-package.json5 b/code/DocsSample/Media/AVRecorder/oh-package.json5 new file mode 100644 index 0000000000..bf39c8a7ea --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/oh-package.json5 @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 Huawei Device 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. + */ + +{ + "modelVersion": "5.0.0", + "license": "", + "devDependencies": { + "@ohos/hypium": "1.0.21", + "@ohos/hamock": "1.0.0" + }, + "author": "", + "name": "AVRecorderSample", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": {} +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/ohosTest.md b/code/DocsSample/Media/AVRecorder/ohosTest.md new file mode 100644 index 0000000000..b419a7aba4 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/ohosTest.md @@ -0,0 +1,10 @@ +# AVRecorderSample测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 | +|----------------|-----------|---------------------------------------------------------|---------------------------|------|------| +| 拉起应用 | 设备正常运行 | | 成功拉起应用 | 是 | Pass | +| 音视频录制功能 | 进入示例应用 | 1. 点击Prepare
2. 点击Start
3. 一段时间后点击Stop | 生成录制文件到沙箱 | 是 | Pass | +| 音频录制功能 | 进入示例应用 | 1. Demo中选择音频录制模式
2. 点击Prepare
3. 点击Start
4. 一段时间后点击Stop | 生成录制文件到沙箱 | 是 | Pass | +| 录制文件保存图库功能 | 进入示例应用 | 1. Demo中fileGenerationMode选择AUTO_CREATE模式
2. 点击Prepare
3. 点击Start
4. 一段时间后点击Stop | 视频文件保存至图库 | 是 | Pass | \ No newline at end of file -- Gitee From 272c45c86be3aba7ce89ea8326b80d9e85652ae0 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:40:26 +0000 Subject: [PATCH 02/20] update code/DocsSample/Media/AVRecorder/README_zh.md. Signed-off-by: Steven --- code/DocsSample/Media/AVRecorder/README_zh.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/code/DocsSample/Media/AVRecorder/README_zh.md b/code/DocsSample/Media/AVRecorder/README_zh.md index fbe263cb3f..6feab6ef5f 100644 --- a/code/DocsSample/Media/AVRecorder/README_zh.md +++ b/code/DocsSample/Media/AVRecorder/README_zh.md @@ -64,4 +64,16 @@ ohos.permission.WRITE_MEDIA 2. 本示例支持API19版本SDK,版本号:5.1.1.62; -3. 本示例已支持使DevEco Studio 5.0.5 Beta1(构建版本:5.0.13.100,构建于:2025年4月25日)编译运行 \ No newline at end of file +3. 本示例已支持使DevEco Studio 5.0.5 Beta1(构建版本:5.0.13.100,构建于:2025年4月25日)编译运行 + +## 下载 + +如需单独下载本工程,执行如下命令: + +``` +git init +git config core.sparsecheckout true +echo code/DocsSample/Media/ScreenCapture/ScreenCaptureSample/ > .git/info/sparse-checkout +git remote add origin OpenHarmony/applications_app_samples +git pull origin master +``` \ No newline at end of file -- Gitee From 16aa7f0a4c54bcffc21fb125e439c56826e9c019 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:44:05 +0000 Subject: [PATCH 03/20] update code/DocsSample/Media/AVRecorder/README_zh.md. Signed-off-by: Steven --- code/DocsSample/Media/AVRecorder/README_zh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/DocsSample/Media/AVRecorder/README_zh.md b/code/DocsSample/Media/AVRecorder/README_zh.md index 6feab6ef5f..45da4c7134 100644 --- a/code/DocsSample/Media/AVRecorder/README_zh.md +++ b/code/DocsSample/Media/AVRecorder/README_zh.md @@ -73,7 +73,7 @@ ohos.permission.WRITE_MEDIA ``` git init git config core.sparsecheckout true -echo code/DocsSample/Media/ScreenCapture/ScreenCaptureSample/ > .git/info/sparse-checkout -git remote add origin OpenHarmony/applications_app_samples +echo code/DocsSample/Media/AVRecorder/ > .git/info/sparse-checkout +git remote add origin https://gitee.com/openharmony/applications_app_samples.git git pull origin master ``` \ No newline at end of file -- Gitee From fabdb8ded05e3adaa3c517a3476df50a760f5666 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:55:47 +0000 Subject: [PATCH 04/20] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ohosTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep new file mode 100644 index 0000000000..e69de29bb2 -- Gitee From 3983571b6453d826bf7fab2573da66c3e394e7bc Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:56:09 +0000 Subject: [PATCH 05/20] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20co?= =?UTF-8?q?de/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep deleted file mode 100644 index e69de29bb2..0000000000 -- Gitee From e2dc59ba98b38765c253c0b9a9d5ee3a9ff99e4c Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:57:41 +0000 Subject: [PATCH 06/20] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ohosTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep new file mode 100644 index 0000000000..e69de29bb2 -- Gitee From d491edec4af8eda102986d017a2fe6857178de26 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:58:02 +0000 Subject: [PATCH 07/20] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep new file mode 100644 index 0000000000..e69de29bb2 -- Gitee From 1f88b9c5b7b98a3c20a4f44e3d0424adcd8a06bc Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:58:14 +0000 Subject: [PATCH 08/20] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep new file mode 100644 index 0000000000..e69de29bb2 -- Gitee From 36bce071f2575d893ddf49f5e13cbf9aded92fa1 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 09:58:30 +0000 Subject: [PATCH 09/20] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20co?= =?UTF-8?q?de/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/.keep deleted file mode 100644 index e69de29bb2..0000000000 -- Gitee From 9e04bf612565bc8729be5b2072205db883d658df Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 10:14:06 +0000 Subject: [PATCH 10/20] add code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/Ability.test.ets. Signed-off-by: Steven --- .../src/ohosTest/ets/test/Ability.test.ets | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/Ability.test.ets diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/Ability.test.ets b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000..461d8d7fa4 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Huawei Device 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 AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import Base from '@ohos.base'; +import { Driver, ON } from '@ohos.UiTest'; +import fs from '@ohos.file.fs'; + +const TAG = 'abilityTest'; +const domain: number = 0x0000; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, async () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let want: Want = { + bundleName: 'com.sample.avrecordersample', + abilityName: 'EntryAbility' + }; + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + abilityDelegator.startAbility(want, (err: Base.BusinessError) => { + hilog.info(domain, TAG, 'StartAbility get err ' + JSON.stringify(err)); + expect(err).assertNull(); + }) + let driver = await Driver.create(); + await driver.delayMs(2000); + let allowBtn1 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Prepare')))); + await allowBtn1.click(); + await driver.delayMs(1000); + let allowBtn2 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Start')))); + await allowBtn2.click(); + await driver.delayMs(2000); + let btn3 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Pause')))); + await btn3.click(); + await driver.delayMs(2000); + let btn4 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Resume')))); + await btn4.click(); + await driver.delayMs(2000); + let allowBtn3 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Stop')))); + await allowBtn3.click(); + await driver.delayMs(2000); + let btn5 = await driver.findComponent(ON.text(getContext().resourceManager.getStringSync($r('app.string.Release')))); + await btn5.click(); + let files: string[] = fs.listFileSync('/data/storage/el2/base/files/'); + expect(files).assertContain('avrecorder01.mp4'); + }) + }) +} \ No newline at end of file -- Gitee From 54b27e9009a7e655e0fca1a11a5dcac9d8cfdd45 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 11:51:33 +0000 Subject: [PATCH 11/20] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20co?= =?UTF-8?q?de/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.kee?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/.keep deleted file mode 100644 index e69de29bb2..0000000000 -- Gitee From 05f17107d209d81bed4f1ed2feabb9fa481caacf Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 11:52:41 +0000 Subject: [PATCH 12/20] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20co?= =?UTF-8?q?de/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/.keep deleted file mode 100644 index e69de29bb2..0000000000 -- Gitee From b0d139d1267d849655dfc367d36c6d2990af5eb5 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 11:54:15 +0000 Subject: [PATCH 13/20] add code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/List.test.ets. Signed-off-by: Steven --- .../entry/src/ohosTest/ets/test/List.test.ets | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/List.test.ets diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/List.test.ets b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000..a10a79ed1d --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device 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 abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file -- Gitee From 36709c7d6d7b2c70bd0838fad53cdd88dbf6cbe2 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 11:55:02 +0000 Subject: [PATCH 14/20] add code/DocsSample/Media/AVRecorder/entry/src/ohosTest/module.json5. Signed-off-by: Steven --- .../entry/src/ohosTest/module.json5 | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/ohosTest/module.json5 diff --git a/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/module.json5 b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000..289100b777 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/ohosTest/module.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device 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. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file -- Gitee From dc955c547334fdcbf2877aa78b60525e87da0d56 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 11:58:51 +0000 Subject: [PATCH 15/20] add code/DocsSample/Media/AVRecorder/entry/src/test. Signed-off-by: Steven --- .../Media/AVRecorder/entry/src/test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test b/code/DocsSample/Media/AVRecorder/entry/src/test new file mode 100644 index 0000000000..f0304a0781 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/test @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Huawei Device 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file -- Gitee From 3a034402433b190f99a16589ad0ceaaca0d48913 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 12:00:03 +0000 Subject: [PATCH 16/20] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20co?= =?UTF-8?q?de/DocsSample/Media/AVRecorder/entry/src/test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Media/AVRecorder/entry/src/test | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test b/code/DocsSample/Media/AVRecorder/entry/src/test deleted file mode 100644 index f0304a0781..0000000000 --- a/code/DocsSample/Media/AVRecorder/entry/src/test +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device 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 localUnitTest from './LocalUnit.test'; - -export default function testsuite() { - localUnitTest(); -} \ No newline at end of file -- Gitee From c752622451c6932d12c82fc37b83f0b6485cff3b Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 12:01:08 +0000 Subject: [PATCH 17/20] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/DocsSample/Media/AVRecorder/entry/src/test/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test/.keep b/code/DocsSample/Media/AVRecorder/entry/src/test/.keep new file mode 100644 index 0000000000..e69de29bb2 -- Gitee From 1460d310c14a187637694f28f8fcd7c5a9bd2d32 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 12:01:33 +0000 Subject: [PATCH 18/20] rename test/ List.test.ets. Signed-off-by: Steven --- .../AVRecorder/entry/src/test/ List.test.ets | 20 +++++++++++++++++++ .../Media/AVRecorder/entry/src/test/.keep | 0 2 files changed, 20 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test/ List.test.ets delete mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test/.keep diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test/ List.test.ets b/code/DocsSample/Media/AVRecorder/entry/src/test/ List.test.ets new file mode 100644 index 0000000000..f1186b1f53 --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/test/ List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test/.keep b/code/DocsSample/Media/AVRecorder/entry/src/test/.keep deleted file mode 100644 index e69de29bb2..0000000000 -- Gitee From 4a0d878ca18814b6acccdec9c6ddd902dc863a73 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 12:03:42 +0000 Subject: [PATCH 19/20] add code/DocsSample/Media/AVRecorder/entry/src/test/LocalUnit.test.ets. Signed-off-by: Steven --- .../entry/src/test/LocalUnit.test.ets | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/test/LocalUnit.test.ets diff --git a/code/DocsSample/Media/AVRecorder/entry/src/test/LocalUnit.test.ets b/code/DocsSample/Media/AVRecorder/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000..05912c780d --- /dev/null +++ b/code/DocsSample/Media/AVRecorder/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Huawei Device 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file -- Gitee From b385b3789a7476170b693dfde7372248e91b0179 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 10 Jun 2025 20:38:50 +0800 Subject: [PATCH 20/20] add Signed-off-by: Steven --- .../AVRecorder/entry/src/main/cpp/main.cpp | 18 ------------------ .../main/resources/base/media/background.png | Bin 0 -> 91942 bytes .../main/resources/base/media/foreground.png | Bin 0 -> 8805 bytes .../resources/base/profile/backup_config.json | 3 +++ 4 files changed, 3 insertions(+), 18 deletions(-) create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/background.png create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/foreground.png create mode 100644 code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/profile/backup_config.json diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp index 93b79dbddb..050e0aeff7 100644 --- a/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp +++ b/code/DocsSample/Media/AVRecorder/entry/src/main/cpp/main.cpp @@ -226,24 +226,6 @@ static napi_value PrepareAVRecorder(napi_env env, napi_callback_info info) OH_LOG_ERROR(LOG_APP, "==NDKDemo== AVRecorder enableTemporalScale:%{public}d", static_cast(config->profile.enableTemporalScale)); // 1.4 更新视频旋转角度 OH_AVRecorder_UpdateRotation - char *videoOrientation = config->metadata.videoOrientation; - if (videoOrientation == nullptr || videoOrientation[0] == '\0') { - OH_LOG_INFO(LOG_APP, "==NDKDemo== videoOrientation is null or empty!"); - napi_value res; - napi_create_int32(env, -1, &res); - return res; - } - // 使用 strtol 进行字符串到整数的转换,并进行错误检查 - char *end; - int32_t rotation= static_cast(strtol(videoOrientation, &end, 10)); // 基数 10 表示十进制 - OH_LOG_INFO(LOG_APP, "==NDKDemo== AVRecorder rotation: %{public}d", rotation); - - // 检查转换是否成功 - if (end == videoOrientation || *end != '\0') { - // 转换失败,videoOrientation 包含无效的整数 - OH_LOG_INFO(LOG_APP, "Invalid rotation value: %s", videoOrientation); - } - // 选择是否更新旋转角度 int32_t rotation = 90; result = OH_AVRecorder_UpdateRotation(g_avRecorder, rotation); if (result != AV_ERR_OK) { diff --git a/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/background.png b/code/DocsSample/Media/AVRecorder/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f GIT binary patch literal 91942 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