diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..936a19d71a6ab216a06d6f34e800e9891f00784d --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.samples.AVCodecBufferMode", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..3ad20b7fd31f38697cfc5215c2767eac5c39e3b3 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "AVCodecBufferMode" + } + ] +} diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3 Binary files /dev/null and b/AppScope/resources/base/media/app_icon.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..18795a48d6b12fcdc1aa7bac9a9cb99f83815267 --- /dev/null +++ b/LICENSE @@ -0,0 +1,78 @@ + Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved. + + 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. + +Apache License, Version 2.0 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +1.You must give any other recipients of the Work or Derivative Works a copy of this License; and +2.You must cause any modified files to carry prominent notices stating that You changed the files; and +3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 4216cbd39a4af96e4b48eb00866c1cb770a5a526..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# AVCodecBufferMode - -#### Description -基于buffer模式进行视频转码 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index d0dcda5f6a68c720ccacb497fdca98f47b3b6d91..fce3d661d03fc891b75494534eecb0203febafc8 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,94 @@ -# AVCodecBufferMode +# 基于Buffer模式进行视频转码 -#### 介绍 -基于buffer模式进行视频转码 +### 介绍 +本实例基于AVCodec能力,实现了Buffer模式下的视频转码功能。通过调用Native侧的编码器,解码器,以及封装和解封装功能,完成从视频解封装、解码、编码、封装的过程。基于本实例可以帮助开发者理解Buffer模式,并通过Buffer模式进行转码。 -#### 软件架构 -软件架构说明 +### 效果预览 +| 应用主界面 | +|------------------------------------------------------------| +| ![AVCodec_Index.png](screenshots/device/AVCodec_Index.png) | +### 使用说明 +1. 进入首页后,配置视频转码的参数。 +2. 点击开始转码后即可开始转码,等待转码完成。 +3. 在转码完成后,跳转到下一个页面,可以查看转码完成的视频。 -#### 安装教程 +### 工程目录 -1. xxxx -2. xxxx -3. xxxx +``` +├──entry/src/main/cpp // Native层 +│ ├──capbilities // 能力接口和实现 +│ │ ├──include // 能力接口 +│ │ ├──Demuxer.cpp // 解封装实现 +│ │ ├──Muxer.cpp // 封装实现 +│ │ ├──VideoDecoder.cpp // 视频解码实现 +│ │ └──VideoEncoder.cpp // 视频编码实现 +│ ├──common // 公共模块 +│ │ ├──dfx // 日志 +│ │ ├──SampleCallback.cpp // 编解码回调实现 +│ │ ├──SampleCallback.h // 编解码回调定义 +│ │ └──SampleInfo.h // 功能实现公共类 +│ ├──sample // Native层 +│ │ └──transcoding // Native层转码接口和实现 +│ │ ├──Transcoding.cpp // Native层转码功能调用逻辑的实现 +│ │ ├──Transcoding.h // Native层转码功能调用逻辑的接口 +│ │ ├──TranscodingNative.cpp // Native层转码的入口 +│ │ └──TranscodingNative.h +│ ├──types // Native层暴露上来的接口 +│ │ └──libtranscoding // 转码模块暴露给UI层的接口 +│ └──CMakeLists.txt // 编译入口 +├──ets // UI层 +│ ├──common // 公共模块 +│ │ ├──utils // 共用的工具类 +│ │ │ ├──TimeUtils.ets // 获取当前时间 +│ │ │ └──Logger.ets // 日志工具 +│ │ └──CommonConstants.ets // 参数常量 +│ ├──entryability // 应用的入口 +│ │ └──EntryAbility.ets +│ ├──entrybackupability +│ │ └──EntryBackupAbility.ets +│ ├──model +│ │ └──VideoDataModel.ets // 参数数据类 +│ └──pages // EntryAbility 包含的页面 +│ ├──Index.ets // 首页/视频转码页面 +│ └──VideoPlayer.ets // 视频播放页面 +├──resources // 用于存放应用所用到的资源文件 +│ ├──base // 该目录下的资源文件会被赋予唯一的ID +│ │ ├──element // 用于存放字体和颜色 +│ │ ├──media // 用于存放图片 +│ │ └──profile // 应用入口首页 +│ ├──en_US // 设备语言是美式英文时,优先匹配此目录下资源 +│ └──zh_CN // 设备语言是简体中文时,优先匹配此目录下资源 +└──module.json5 // 模块配置信息 +``` -#### 使用说明 +### 具体实现 -1. xxxx -2. xxxx -3. xxxx +#### UI层 +1. 在ArkTS侧包含两个页面,首页和转码完成的页面。 +2. 首页是参数配置页面,调用了Video组件播放需要转码的视频,并调用showTextPickerDialog弹窗配置转码的参数。再点击转码的按钮后,调用Native转码的接口。 +3. 视频播放页面通过Video组件播放转码前和转码后的视频。 -#### 参与贡献 +#### Native层 +1. 在开始转码前,需要对环境进行初始化,包括解封装器、封装器、编码器、解码器。同时,将需要用到的上下文参数进行保存。 +2. 开启解码子线程,将视频数据进行解码,解码子线程包括输入子线程、输出子线程。在解码输入子线程中,用户需手动把帧buffer、index存入输入队列中,并通知解码其进行解码。 +3. 在解码输出线程中,将解码器解码后的视频数据进行数据拷贝。将拷贝的数据存入编码的输入队列,并同步释放编码的视频地址。 +4. 在编码输出线程中,将输出队列的bufferInfo进行出栈,并将对应的数据通过封装器写入到视频文件中。 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +### 相关权限 +- 无 -#### 特技 +### 依赖 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +- 不涉及 + +### 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:华为手机; + +2. HarmonyOS系统:HarmonyOS 5.0.0 Release及以上; + +3. DevEco Studio版本:DevEco Studio 5.0.0 Release及以上; + +4. HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上。 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7987d71fc3a8939a8ed503a16557d17082fa5693 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,43 @@ +/* + * 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": { + "signingConfigs": [ + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.1.0(18)", + "runtimeOS": "HarmonyOS", + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6e8afeb0fbff7c2a8ab30149bf0d44e42d872777 --- /dev/null +++ b/entry/build-profile.json5 @@ -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. + */ + +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": ["arm64-v8a", "x86_64"] + } + }, + "targets": [ + { + "name": "default" + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..78b2917ae94256df8aa64570ad71793f13595d45 --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,26 @@ +/* + * 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": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libtranscoding.so": "file:./src/main/cpp/types/libtranscoding" + } +} \ No newline at end of file diff --git a/entry/src/main/cpp/CMakeLists.txt b/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..bb2e559a34781a5849c4c5e424af94d2cfe7cf35 --- /dev/null +++ b/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,30 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.4.1) +project(videoCodecSample) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/capbilities/include + ${NATIVERENDER_ROOT_PATH}/common + ${NATIVERENDER_ROOT_PATH}/common/dfx/err + ${NATIVERENDER_ROOT_PATH}/common/dfx/log + ${NATIVERENDER_ROOT_PATH}/sample/transcoding +) + +set(BASE_LIBRARY + libace_napi.z.so libEGL.so libGLESv3.so libace_ndk.z.so libuv.so libhilog_ndk.z.so + libnative_media_codecbase.so libnative_media_core.so libnative_media_vdec.so libnative_window.so + libnative_media_venc.so libnative_media_acodec.so libnative_media_avdemuxer.so libnative_media_avsource.so libnative_media_avmuxer.so + libohaudio.so +) +add_library(transcoding SHARED sample/transcoding/TranscodingNative.cpp + sample/transcoding/Transcoding.cpp + capbilities/Demuxer.cpp + capbilities/VideoDecoder.cpp + capbilities/Muxer.cpp + capbilities/VideoEncoder.cpp + common/SampleCallback.cpp +) + +target_link_libraries(transcoding PUBLIC ${BASE_LIBRARY}) \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/Demuxer.cpp b/entry/src/main/cpp/capbilities/Demuxer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1f2f928e90a4659d5c2926322a20fe777f7f2d2d --- /dev/null +++ b/entry/src/main/cpp/capbilities/Demuxer.cpp @@ -0,0 +1,129 @@ +/* + * 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 "Demuxer.h" + +#undef LOG_TAG +#define LOG_TAG "Demuxer" + +Demuxer::~Demuxer() { Release(); } + +// [Start create_demuxer] +int32_t Demuxer::Create(SampleInfo &info) { + source_ = OH_AVSource_CreateWithFD(info.inputFd, info.inputFileOffset, info.inputFileSize); + CHECK_AND_RETURN_RET_LOG(source_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, + "Create demuxer source failed, fd: %{public}d, offset: %{public}" PRId64 + ", file size: %{public}" PRId64, + info.inputFd, info.inputFileOffset, info.inputFileSize); + demuxer_ = OH_AVDemuxer_CreateWithSource(source_); + CHECK_AND_RETURN_RET_LOG(demuxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create demuxer failed"); + + auto sourceFormat = std::shared_ptr(OH_AVSource_GetSourceFormat(source_), OH_AVFormat_Destroy); + CHECK_AND_RETURN_RET_LOG(sourceFormat != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Get source format failed"); + + int32_t ret = GetTrackInfo(sourceFormat, info); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Get video track info failed"); + + return AVCODEC_SAMPLE_ERR_OK; +} +// [End create_demuxer] + +int32_t Demuxer::ReadSample(int32_t trackId, OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr) { + CHECK_AND_RETURN_RET_LOG(demuxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Demuxer is null"); + int32_t ret = OH_AVDemuxer_ReadSampleBuffer(demuxer_, trackId, buffer); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Read sample failed"); + ret = OH_AVBuffer_GetBufferAttr(buffer, &attr); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "GetBufferAttr failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Demuxer::Release() { + if (demuxer_ != nullptr) { + OH_AVDemuxer_Destroy(demuxer_); + demuxer_ = nullptr; + } + if (source_ != nullptr) { + OH_AVSource_Destroy(source_); + source_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start get_track_info] +int32_t Demuxer::GetTrackInfo(std::shared_ptr sourceFormat, SampleInfo &info) { + int32_t trackCount = 0; + OH_AVFormat_GetIntValue(sourceFormat.get(), OH_MD_KEY_TRACK_COUNT, &trackCount); + for (int32_t index = 0; index < trackCount; index++) { + int trackType = -1; + auto trackFormat = + std::shared_ptr(OH_AVSource_GetTrackFormat(source_, index), OH_AVFormat_Destroy); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_TRACK_TYPE, &trackType); + if (trackType == MEDIA_TYPE_VID) { + OH_AVDemuxer_SelectTrackByID(demuxer_, index); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_WIDTH, &info.videoWidth); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_HEIGHT, &info.videoHeight); + OH_AVFormat_GetDoubleValue(trackFormat.get(), OH_MD_KEY_FRAME_RATE, &info.frameRate); + OH_AVFormat_GetLongValue(trackFormat.get(), OH_MD_KEY_BITRATE, &info.bitrate); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_ROTATION, &info.rotation); + char *videoCodecMime; + OH_AVFormat_GetStringValue(trackFormat.get(), OH_MD_KEY_CODEC_MIME, + const_cast(&videoCodecMime)); + info.videoCodecMime = videoCodecMime; + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_PROFILE, &info.hevcProfile); + videoTrackId_ = index; + + AVCODEC_SAMPLE_LOGI("====== Demuxer Video config ======"); + AVCODEC_SAMPLE_LOGI("Mime: %{public}s", videoCodecMime); + AVCODEC_SAMPLE_LOGI("%{public}d*%{public}d, %{public}.1ffps, %{public}" PRId64 "kbps", info.videoWidth, + info.videoHeight, info.frameRate, info.bitrate / 1024); + AVCODEC_SAMPLE_LOGI("====== Demuxer Video config ======"); + } else if (trackType == MEDIA_TYPE_AUD) { + OH_AVDemuxer_SelectTrackByID(demuxer_, index); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUDIO_SAMPLE_FORMAT, &info.audioSampleForamt); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUD_CHANNEL_COUNT, &info.audioChannelCount); + OH_AVFormat_GetLongValue(trackFormat.get(), OH_MD_KEY_CHANNEL_LAYOUT, &info.audioChannelLayout); + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUD_SAMPLE_RATE, &info.audioSampleRate); + char *audioCodecMime; + OH_AVFormat_GetStringValue(trackFormat.get(), OH_MD_KEY_CODEC_MIME, + const_cast(&audioCodecMime)); + uint8_t *codecConfig = nullptr; + OH_AVFormat_GetBuffer(trackFormat.get(), OH_MD_KEY_CODEC_CONFIG, &codecConfig, &info.codecConfigLen); + if (info.codecConfigLen > 0 && info.codecConfigLen < sizeof(info.codecConfig)) { + memcpy(info.codecConfig, codecConfig, info.codecConfigLen); + AVCODEC_SAMPLE_LOGI( + "codecConfig:%{public}p, len:%{public}i, 0:0x%{public}02x 1:0x:%{public}02x, bufLen:%{public}u", + info.codecConfig, (int)info.codecConfigLen, info.codecConfig[0], info.codecConfig[1], + sizeof(info.codecConfig)); + } + OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AAC_IS_ADTS, &info.aacAdts); + + info.audioCodecMime = audioCodecMime; + audioTrackId_ = index; + + AVCODEC_SAMPLE_LOGI("====== Demuxer Audio config ======"); + AVCODEC_SAMPLE_LOGI( + "audioMime:%{public}s sampleForamt:%{public}d " + "sampleRate:%{public}d channelCount:%{public}d channelLayout:%{public}d adts:%{public}i", + info.audioCodecMime.c_str(), info.audioSampleForamt, info.audioSampleRate, info.audioChannelCount, + info.audioChannelLayout, info.aacAdts); + AVCODEC_SAMPLE_LOGI("====== Demuxer Audio config ======"); + } + } + + return AVCODEC_SAMPLE_ERR_OK; +} +// [End get_track_info] + +int32_t Demuxer::GetVideoTrackId() { return videoTrackId_; } \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/Muxer.cpp b/entry/src/main/cpp/capbilities/Muxer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4dd17c21e26a97f1a2c87554287259e3459e0cf --- /dev/null +++ b/entry/src/main/cpp/capbilities/Muxer.cpp @@ -0,0 +1,97 @@ +/* + * 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 "dfx/error/AVCodecSampleError.h" +#include + +#undef LOG_TAG +#define LOG_TAG "Muxer" + +namespace { +constexpr int32_t SAMPLE_RATE = 16000; +} // namespace + +Muxer::~Muxer() { Release(); } + +// Create an encapsulator instance object and set the encapsulation format to mp4 +int32_t Muxer::Create(int32_t fd) { + muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_MPEG_4); + CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer create failed, fd: %{public}d", fd); + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start config_muxer] +int32_t Muxer::Config(SampleInfo &sampleInfo) { + CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer is null"); + OH_AVFormat *formatVideo = + OH_AVFormat_CreateVideoFormat(sampleInfo.outputVideoCodecMime.data(), sampleInfo.videoWidth, sampleInfo.videoHeight); + CHECK_AND_RETURN_RET_LOG(formatVideo != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create video format failed"); + + OH_AVFormat_SetDoubleValue(formatVideo, OH_MD_KEY_FRAME_RATE, sampleInfo.outputFrameRate); + 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.outputVideoCodecMime.data()); + if (sampleInfo.isHDRVivid) { + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_VIDEO_IS_HDR_VIVID, 1); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_RANGE_FLAG, sampleInfo.rangFlag); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_COLOR_PRIMARIES, sampleInfo.primary); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_TRANSFER_CHARACTERISTICS, sampleInfo.transfer); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_MATRIX_COEFFICIENTS, sampleInfo.matrix); + } + + int32_t ret = OH_AVMuxer_AddTrack(muxer_, &videoTrackId_, formatVideo); + OH_AVFormat_Destroy(formatVideo); + formatVideo = nullptr; + OH_AVMuxer_SetRotation(muxer_, sampleInfo.rotation); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "AddTrack failed"); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End config_muxer] + +int32_t Muxer::Start() { + CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer is null"); + + int ret = OH_AVMuxer_Start(muxer_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start write_sample] +int32_t Muxer::WriteSample(int32_t trackId, OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr){ + std::lock_guard lock(writeMutex_); + + CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer is null"); + CHECK_AND_RETURN_RET_LOG(buffer != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Get a empty buffer"); + + int32_t ret = OH_AVBuffer_SetBufferAttr(buffer, &attr); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "SetBufferAttr failed"); + + ret = OH_AVMuxer_WriteSampleBuffer(muxer_, trackId, buffer); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Write sample failed"); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End write_sample] + +int32_t Muxer::Release() { + if (muxer_ != nullptr) { + OH_AVMuxer_Destroy(muxer_); + muxer_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Muxer::GetVideoTrackId() { return videoTrackId_; } +int32_t Muxer::GetAudioTrackId() { return audioTrackId_; } \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/VideoDecoder.cpp b/entry/src/main/cpp/capbilities/VideoDecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8adea4e6397f8dcb05824c5158b84035f8b275fb --- /dev/null +++ b/entry/src/main/cpp/capbilities/VideoDecoder.cpp @@ -0,0 +1,127 @@ +/* + * 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 "VideoDecoder.h" + +#undef LOG_TAG +#define LOG_TAG "VideoDecoder" + +namespace { +constexpr int LIMIT_LOGD_FREQUENCY = 50; +} // namespace + +VideoDecoder::~VideoDecoder() { Release(); } + +// Development using the system codec AVCodec +// Create a decoder instance object +int32_t VideoDecoder::Create(const std::string &videoCodecMime) { + decoder_ = OH_VideoDecoder_CreateByMime(videoCodecMime.c_str()); + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start config_decoder] +// Setting the callback function +int32_t VideoDecoder::SetCallback(CodecUserData *codecUserData) { + int32_t ret = AV_ERR_OK; + ret = OH_VideoDecoder_RegisterCallback(decoder_, + {SampleCallback::OnCodecError, SampleCallback::OnCodecFormatChange, + SampleCallback::OnNeedInputBuffer, SampleCallback::OnNewOutputBuffer}, + codecUserData); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Set callback failed, ret: %{public}d", ret); + + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoDecoder::Configure(const SampleInfo &sampleInfo) { + OH_AVFormat *format = OH_AVFormat_Create(); + CHECK_AND_RETURN_RET_LOG(format != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "AVFormat create failed"); + + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, sampleInfo.videoWidth); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight); + OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, sampleInfo.pixelFormat); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_ROTATION, sampleInfo.rotation); + + AVCODEC_SAMPLE_LOGI("====== VideoDecoder config ======"); + AVCODEC_SAMPLE_LOGI("%{public}d*%{public}d, %{public}.1ffps", sampleInfo.videoWidth, sampleInfo.videoHeight, + sampleInfo.frameRate); + AVCODEC_SAMPLE_LOGI("====== VideoDecoder config ======"); + int ret = OH_VideoDecoder_Configure(decoder_, format); + OH_AVFormat_Destroy(format); + format = nullptr; + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Config failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoDecoder::Config(const SampleInfo &sampleInfo, CodecUserData *codecUserData) { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + CHECK_AND_RETURN_RET_LOG(codecUserData != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Invalid param: codecUserData"); + + // Configure video decoder + int32_t ret = Configure(sampleInfo); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Configure failed"); + + // SetCallback for video decoder + ret = SetCallback(codecUserData); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, + "Set callback failed, ret: %{public}d", ret); + + // Prepare video decoder + ret = OH_VideoDecoder_Prepare(decoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Prepare failed, ret: %{public}d", ret); + + return AVCODEC_SAMPLE_ERR_OK; +} +// [End config_decoder] + +int32_t VideoDecoder::Start() { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + + int ret = OH_VideoDecoder_Start(decoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoDecoder::PushInputBuffer(CodecBufferInfo &info) { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + int32_t ret = OH_VideoDecoder_PushInputBuffer(decoder_, info.bufferIndex); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Push input data failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoDecoder::FreeOutputBuffer(uint32_t bufferIndex, bool render) { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + + int32_t ret = AVCODEC_SAMPLE_ERR_OK; + if (render) { + ret = OH_VideoDecoder_RenderOutputBuffer(decoder_, bufferIndex); + } else { + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_, bufferIndex); + } + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Free output data failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +// Destroy the decoder instance and release resources +int32_t VideoDecoder::Release() { + if (decoder_ != nullptr) { + OH_VideoDecoder_Flush(decoder_); + OH_VideoDecoder_Stop(decoder_); + OH_VideoDecoder_Destroy(decoder_); + decoder_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/VideoEncoder.cpp b/entry/src/main/cpp/capbilities/VideoEncoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f06ea93bdb29b53630a9f9f0f980fd3547f53ca6 --- /dev/null +++ b/entry/src/main/cpp/capbilities/VideoEncoder.cpp @@ -0,0 +1,157 @@ +/* + * 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 "VideoEncoder.h" + +#undef LOG_TAG +#define LOG_TAG "VideoEncoder" + +VideoEncoder::~VideoEncoder() { Release(); } + +// [Start encoder_initialization] +// Create a video coder and initialize it +int32_t VideoEncoder::Create(const std::string &videoCodecMime) { + encoder_ = OH_VideoEncoder_CreateByMime(videoCodecMime.c_str()); + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create failed"); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End encoder_initialization] + +// [Start config_encoder] +int32_t VideoEncoder::Config(SampleInfo &sampleInfo, CodecUserData *codecUserData) { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null"); + CHECK_AND_RETURN_RET_LOG(codecUserData != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Invalid param: codecUserData"); + + // Configure video encoder + int32_t ret = Configure(sampleInfo); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Configure failed"); + + // SetCallback for video encoder + ret = SetCallback(codecUserData); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, + "Set callback failed, ret: %{public}d", ret); + + // Prepare video encoder + ret = OH_VideoEncoder_Prepare(encoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Prepare failed, ret: %{public}d", ret); + + return AVCODEC_SAMPLE_ERR_OK; +} + +// [StartExclude config_encoder] +// [Start start_encoder] +// Start Encoder +int32_t VideoEncoder::Start() { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null"); + + int ret = OH_VideoEncoder_Start(encoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End start_encoder] + +int32_t VideoEncoder::FreeOutputBuffer(uint32_t bufferIndex) { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null"); + + int32_t ret = OH_VideoEncoder_FreeOutputBuffer(encoder_, bufferIndex); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Free output data failed, ret: %{public}d", + ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoEncoder::NotifyEndOfStream() { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null"); + + int32_t ret = OH_VideoEncoder_NotifyEndOfStream(encoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Notify end of stream failed, ret: %{public}d", + ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoEncoder::Stop() { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null"); + + int ret = OH_VideoEncoder_Flush(encoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Flush failed, ret: %{public}d", ret); + + ret = OH_VideoEncoder_Stop(encoder_); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Stop failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoEncoder::Release() { + if (encoder_ != nullptr) { + OH_VideoEncoder_Destroy(encoder_); + encoder_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoEncoder::SetCallback(CodecUserData *codecUserData) { + int32_t ret = + OH_VideoEncoder_RegisterCallback(encoder_, + {SampleCallback::OnCodecError, SampleCallback::OnCodecFormatChange, + SampleCallback::EncOnNeedInputBuffer, SampleCallback::EncOnNewOutputBuffer}, + codecUserData); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Set callback failed, ret: %{public}d", ret); + + return AVCODEC_SAMPLE_ERR_OK; +} +// [EndExclude config_encoder] + +int32_t VideoEncoder::Configure(const SampleInfo &sampleInfo) { + OH_AVFormat *format = OH_AVFormat_Create(); + CHECK_AND_RETURN_RET_LOG(format != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "AVFormat create failed"); + + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, sampleInfo.videoWidth); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight); + OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, sampleInfo.outputFrameRate); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, sampleInfo.pixelFormat); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, sampleInfo.bitrateMode); + OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, sampleInfo.outputBitrate); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, sampleInfo.hevcProfile); + // Setting HDRVivid-related parameters + if (sampleInfo.isHDRVivid) { + OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL, sampleInfo.iFrameInterval); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_RANGE_FLAG, sampleInfo.rangFlag); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_COLOR_PRIMARIES, sampleInfo.primary); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_TRANSFER_CHARACTERISTICS, sampleInfo.transfer); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_MATRIX_COEFFICIENTS, sampleInfo.matrix); + } + AVCODEC_SAMPLE_LOGI("====== VideoEncoder config ======"); + AVCODEC_SAMPLE_LOGI("%{public}d*%{public}d, %{public}.1ffps", sampleInfo.videoWidth, sampleInfo.videoHeight, + sampleInfo.frameRate); + // 1024: ratio of kbps to bps + AVCODEC_SAMPLE_LOGI("BitRate Mode: %{public}d, BitRate: %{public}" PRId64 "kbps", sampleInfo.bitrateMode, + sampleInfo.bitrate / 1024); + AVCODEC_SAMPLE_LOGI("====== VideoEncoder config ======"); + + // Setting the Encoder + int ret = OH_VideoEncoder_Configure(encoder_, format); + OH_AVFormat_Destroy(format); + format = nullptr; + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Config failed, ret: %{public}d", ret); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End config_encoder] + +// [End push_input_buffer] +int32_t VideoEncoder::PushInputBuffer(CodecBufferInfo &info) { + CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + int32_t ret = OH_VideoEncoder_PushInputBuffer(encoder_, info.bufferIndex); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Push input data failed"); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End push_input_buffer] \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/include/Demuxer.h b/entry/src/main/cpp/capbilities/include/Demuxer.h new file mode 100644 index 0000000000000000000000000000000000000000..96adf51fdd7daf4ed64d0820cccb4e0ddd294481 --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/Demuxer.h @@ -0,0 +1,44 @@ +/* + * 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 DEMUXER_H +#define DEMUXER_H + +#include +#include "napi/native_api.h" +#include "multimedia/player_framework/native_avdemuxer.h" +#include "SampleInfo.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class Demuxer { +public: + Demuxer() = default; + ~Demuxer(); + int32_t Create(SampleInfo &sampleInfo); + int32_t ReadSample(int32_t trackId, OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr); + int32_t Release(); + int32_t GetVideoTrackId(); + +private: + int32_t GetTrackInfo(std::shared_ptr sourceFormat, SampleInfo &info); + + OH_AVSource *source_; + OH_AVDemuxer *demuxer_; + int32_t videoTrackId_; + int32_t audioTrackId_; +}; + +#endif // DEMUXER_H \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/include/Muxer.h b/entry/src/main/cpp/capbilities/include/Muxer.h new file mode 100644 index 0000000000000000000000000000000000000000..e9a040a11c8ff85f2e71b1cd26f9ac687e422fff --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/Muxer.h @@ -0,0 +1,44 @@ +/* + * 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 + +#include "AVCodecSampleLog.h" +#include "SampleInfo.h" +#include "multimedia/player_framework/native_avmuxer.h" +#include + +class Muxer { +public: + Muxer() = default; + ~Muxer(); + + int32_t Create(int32_t fd); + int32_t Config(SampleInfo &sampleInfo); + int32_t Start(); + int32_t WriteSample(int32_t trackId, OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr); + int32_t Release(); + int32_t GetVideoTrackId(); + int32_t GetAudioTrackId(); + +private: + OH_AVMuxer *muxer_ = nullptr; + int32_t videoTrackId_ = -1; + int32_t audioTrackId_ = -1; + std::mutex writeMutex_; +}; + +#endif // MUXER_H \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/include/VideoDecoder.h b/entry/src/main/cpp/capbilities/include/VideoDecoder.h new file mode 100644 index 0000000000000000000000000000000000000000..45ee99029e06edbd70ea1cab6ef3a7363b22f92e --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/VideoDecoder.h @@ -0,0 +1,45 @@ +/* + * 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 VIDEODECODER_H +#define VIDEODECODER_H + +#include "multimedia/player_framework/native_avcodec_videodecoder.h" +#include "multimedia/player_framework/native_avbuffer_info.h" +#include "SampleInfo.h" +#include "SampleCallback.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class VideoDecoder { +public: + VideoDecoder() = default; + ~VideoDecoder(); + + int32_t Create(const std::string &videoCodecMime); + int32_t Config(const SampleInfo &sampleInfo, CodecUserData *codecUserData); + int32_t PushInputBuffer(CodecBufferInfo &info); + int32_t FreeOutputBuffer(uint32_t bufferIndex, bool render); + int32_t Start(); + int32_t Release(); + +private: + int32_t SetCallback(CodecUserData *codecUserData); + int32_t Configure(const SampleInfo &sampleInfo); + + bool isAVBufferMode_ = false; + OH_AVCodec *decoder_; +}; +#endif // VIDEODECODER_H \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/include/VideoEncoder.h b/entry/src/main/cpp/capbilities/include/VideoEncoder.h new file mode 100644 index 0000000000000000000000000000000000000000..287f551259b0dfe0917200aa16ab9291f9bee6ac --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/VideoEncoder.h @@ -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. + */ + +#ifndef VIDEOENCODER_H +#define VIDEOENCODER_H + +#include "multimedia/player_framework/native_avcodec_videoencoder.h" +#include "multimedia/player_framework/native_avbuffer_info.h" +#include "SampleInfo.h" +#include "native_window/external_window.h" +#include "native_window/buffer_handle.h" +#include "SampleCallback.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class VideoEncoder { +public: + VideoEncoder() = default; + ~VideoEncoder(); + + int32_t Create(const std::string &videoCodecMime); + int32_t Config(SampleInfo &sampleInfo, CodecUserData *codecUserData); + int32_t Start(); + int32_t FreeOutputBuffer(uint32_t bufferIndex); + int32_t PushInputBuffer(CodecBufferInfo &info); + int32_t NotifyEndOfStream(); + int32_t Stop(); + int32_t Release(); + +private: + int32_t SetCallback(CodecUserData *codecUserData); + int32_t Configure(const SampleInfo &sampleInfo); + bool isAVBufferMode_ = false; + OH_AVCodec *encoder_ = nullptr; +}; +#endif // VIDEOENCODER_H \ No newline at end of file diff --git a/entry/src/main/cpp/common/SampleCallback.cpp b/entry/src/main/cpp/common/SampleCallback.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99a46e1be0ec4db857e44aaa0b5a45ebd00c8539 --- /dev/null +++ b/entry/src/main/cpp/common/SampleCallback.cpp @@ -0,0 +1,95 @@ +/* + * 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 "SampleCallback.h" +#include "AVCodecSampleLog.h" +#include "multimedia/player_framework/native_avcodec_videodecoder.h" + +namespace { +constexpr int LIMIT_LOGD_FREQUENCY = 50; +} + +void SampleCallback::OnCodecError(OH_AVCodec *codec, int32_t errorCode, void *userData) { + (void)codec; + (void)errorCode; + (void)userData; + AVCODEC_SAMPLE_LOGI("On codec error, error code: %{public}d", errorCode); +} + +void SampleCallback::OnCodecFormatChange(OH_AVCodec *codec, OH_AVFormat *format, void *userData) { + AVCODEC_SAMPLE_LOGI("On codec format change"); +} + +void SampleCallback::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { + 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(); +} + +// [Start new_output_buffer] +void SampleCallback::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { + if (userData == nullptr) { + return; + } + CodecUserData *codecUserData = static_cast(userData); + if(codecUserData->isDecFirstFrame) { + OH_AVFormat *format = OH_VideoDecoder_GetOutputDescription(codec); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &codecUserData->width); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &codecUserData->height); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &codecUserData->widthStride); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, &codecUserData->heightStride); + OH_AVFormat_Destroy(format); + codecUserData->isDecFirstFrame = false; + } + std::unique_lock lock(codecUserData->outputMutex); + codecUserData->outputBufferInfoQueue.emplace(index, buffer); + codecUserData->outputCond.notify_all(); +} +// [End new_output_buffer] + +void SampleCallback::EncOnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { + if (userData == nullptr) { + return; + } + CodecUserData *codecUserData = static_cast(userData); + if (codecUserData->isEncFirstFrame) { + OH_AVFormat *format = OH_VideoDecoder_GetOutputDescription(codec); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &codecUserData->width); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &codecUserData->height); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &codecUserData->widthStride); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, &codecUserData->heightStride); + OH_AVFormat_Destroy(format); + codecUserData->isEncFirstFrame = false; + } + std::unique_lock lock(codecUserData->inputMutex); + codecUserData->inputBufferInfoQueue.emplace(index, buffer); + codecUserData->inputCond.notify_all(); +} + +void SampleCallback::EncOnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) { + 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/entry/src/main/cpp/common/SampleCallback.h b/entry/src/main/cpp/common/SampleCallback.h new file mode 100644 index 0000000000000000000000000000000000000000..6f8c5be1bd51391c8729c6f74c8f13b0cdd44b84 --- /dev/null +++ b/entry/src/main/cpp/common/SampleCallback.h @@ -0,0 +1,30 @@ +/* + * 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 +#include "SampleInfo.h" + +class SampleCallback { +public: + static void OnCodecError(OH_AVCodec *codec, int32_t errorCode, void *userData); + static void OnCodecFormatChange(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); + static void EncOnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData); + static void EncOnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData); +}; + +#endif // AVCODEC_SAMPLE_CALLBACK_H \ No newline at end of file diff --git a/entry/src/main/cpp/common/SampleInfo.h b/entry/src/main/cpp/common/SampleInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..72cad0d9863c472a6a8ce863fe8f61478c65f3d2 --- /dev/null +++ b/entry/src/main/cpp/common/SampleInfo.h @@ -0,0 +1,125 @@ +/* + * 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 "multimedia/player_framework/native_avcodec_base.h" +#include "multimedia/player_framework/native_avbuffer.h" + +const std::string_view MIME_VIDEO_AVC = "video/avc"; +const std::string_view MIME_VIDEO_HEVC = "video/hevc"; +const std::string_view MIME_AUDIO_MPEG = "audio/mpeg"; + +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 inputFd = -1; + int32_t outputFd = -1; + int64_t inputFileOffset = 0; + int64_t inputFileSize = 0; + std::string inputFilePath; + std::string videoCodecMime = ""; + std::string audioCodecMime = ""; + int32_t videoWidth = 0; + int32_t videoHeight = 0; + + double outputFrameRate = 0.0; + std::string outputVideoCodecMime = ""; + int64_t outputBitrate = 10 * 1024 * 1024; // 10Mbps; + + double frameRate = 0.0; + int64_t bitrate = 10 * 1024 * 1024; // 10Mbps; + int64_t frameInterval = 0; + OH_AVPixelFormat pixelFormat = AV_PIXEL_FORMAT_NV12; + uint32_t bitrateMode = CBR; + int32_t iFrameInterval = 100; + int32_t rangFlag = 1; + + int32_t audioSampleForamt = 0; + int32_t audioSampleRate = 0; + int32_t audioChannelCount = 0; + int64_t audioChannelLayout = 0; + int32_t audioBitRate = 0; + uint8_t audioCodecConfig[100] = { 0 }; + size_t audioCodecSize = 0; + int32_t audioMaxInputSize = 0; + OH_AVFormat *audioFormat; + + + int32_t isHDRVivid = 0; + int32_t hevcProfile = HEVC_PROFILE_MAIN; + OH_ColorPrimary primary = COLOR_PRIMARY_BT2020; + OH_TransferCharacteristic transfer = TRANSFER_CHARACTERISTIC_PQ; + OH_MatrixCoefficient matrix = MATRIX_COEFFICIENT_BT2020_CL; + + int32_t rotation = 0; + OHNativeWindow *window = nullptr; + + void (*playDoneCallback)(void *context) = nullptr; + void *playDoneCallbackData = nullptr; + uint8_t codecConfig[1024]; + size_t codecConfigLen = 0; + int32_t aacAdts = -1; +}; + +struct CodecBufferInfo { + uint32_t bufferIndex = 0; + uintptr_t *buffer = nullptr; + uint8_t *bufferAddr = nullptr; + OH_AVCodecBufferAttr attr = {0, 0, 0, AVCODEC_BUFFER_FLAGS_NONE}; + + explicit 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_AVBuffer *argBuffer) + : bufferIndex(argBufferIndex), buffer(reinterpret_cast(argBuffer)) + { + OH_AVBuffer_GetBufferAttr(argBuffer, &attr); + }; +}; + +struct 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::condition_variable renderCond; + std::queue outputBufferInfoQueue; + + int32_t width = 0; + int32_t height = 0; + int32_t widthStride = 0; + int32_t heightStride = 0; + bool isEncFirstFrame = true; + bool isDecFirstFrame = true; +}; + +#endif // AVCODEC_SAMPLE_INFO_H \ No newline at end of file diff --git a/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h b/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h new file mode 100644 index 0000000000000000000000000000000000000000..a219a31336f1b05d45867caa9f29ce36133698c9 --- /dev/null +++ b/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h @@ -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. + */ + +#ifndef AVCODEC_SAMPLE_ERROE_H +#define AVCODEC_SAMPLE_ERROE_H + +enum AVCodecSampleError : int { + AVCODEC_SAMPLE_ERR_OK = 0, + AVCODEC_SAMPLE_ERR_ERROR = -1, +}; + +#endif // AVCODEC_SAMPLE_ERROE_H \ No newline at end of file diff --git a/entry/src/main/cpp/common/dfx/log/AVCodecSampleLog.h b/entry/src/main/cpp/common/dfx/log/AVCodecSampleLog.h new file mode 100644 index 0000000000000000000000000000000000000000..d1b5add9cf7e75d966d7cc0fd06e3457ad9ff370 --- /dev/null +++ b/entry/src/main/cpp/common/dfx/log/AVCodecSampleLog.h @@ -0,0 +1,83 @@ +/* + * 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_LOG_H +#define AVCODEC_SAMPLE_LOG_H + +#include +#include + +#undef LOG_DOMAIN +#define LOG_DOMAIN 0x0002B66 + +#define AVCODEC_SAMPLE_LOG_FREQ_LIMIT(frequency) \ + if (1) { \ + thread_local uint64_t currentTimes = 0; \ + if (currentTimes++ % ((uint64_t)(frequency)) != 0) { \ + break; \ + } \ + } + +#define AVCODEC_SAMPLE_LOG(func, fmt, args...) \ + do { \ + (void)func(LOG_APP, "{%{public}s():%{public}d} " fmt, __FUNCTION__, __LINE__, ##args); \ + } while (0) + +#define AVCODEC_SAMPLE_LOGF(fmt, ...) AVCODEC_SAMPLE_LOG(OH_LOG_FATAL, fmt, ##__VA_ARGS__) +#define AVCODEC_SAMPLE_LOGE(fmt, ...) AVCODEC_SAMPLE_LOG(OH_LOG_ERROR, fmt, ##__VA_ARGS__) +#define AVCODEC_SAMPLE_LOGW(fmt, ...) AVCODEC_SAMPLE_LOG(OH_LOG_WARN, fmt, ##__VA_ARGS__) +#define AVCODEC_SAMPLE_LOGI(fmt, ...) AVCODEC_SAMPLE_LOG(OH_LOG_INFO, fmt, ##__VA_ARGS__) +#define AVCODEC_SAMPLE_LOGD(fmt, ...) AVCODEC_SAMPLE_LOG(OH_LOG_DEBUG, fmt, ##__VA_ARGS__) +#define AVCODEC_SAMPLE_LOGD_LIMIT(frequency, fmt, ...) \ + do { \ + AVCODEC_SAMPLE_LOG_FREQ_LIMIT(frequency); \ + AVCODEC_SAMPLE_LOGD(fmt, ##__VA_ARGS__); \ + } while (0) + +#define CHECK_AND_RETURN_RET_LOG(cond, ret, fmt, ...) \ + do { \ + if (!(cond)) { \ + AVCODEC_SAMPLE_LOGE(fmt, ##__VA_ARGS__); \ + return ret; \ + } \ + } while (0) + +#define CHECK_AND_RETURN_LOG(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + AVCODEC_SAMPLE_LOGE(fmt, ##__VA_ARGS__); \ + return; \ + } \ + } while (0) + +#define CHECK_AND_BREAK_LOG(cond, fmt, ...) \ + if (1) { \ + if (!(cond)) { \ + AVCODEC_SAMPLE_LOGW(fmt, ##__VA_ARGS__); \ + break; \ + } \ + } else \ + void(0) + +#define CHECK_AND_CONTINUE_LOG(cond, fmt, ...) \ + if (1) { \ + if (!(cond)) { \ + AVCODEC_SAMPLE_LOGW(fmt, ##__VA_ARGS__); \ + continue; \ + } \ + } else \ + void(0) + +#endif // AVCODEC_SAMPLE_LOG_H \ No newline at end of file diff --git a/entry/src/main/cpp/sample/transcoding/Transcoding.cpp b/entry/src/main/cpp/sample/transcoding/Transcoding.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5111594ad87f8a95057591760bd7ce57db89047a --- /dev/null +++ b/entry/src/main/cpp/sample/transcoding/Transcoding.cpp @@ -0,0 +1,393 @@ +/* + * 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 "Transcoding.h" +#include "AVCodecSampleLog.h" +#include "dfx/error/AVCodecSampleError.h" +#include "multimedia/player_framework/native_avbuffer.h" +#include + +#undef LOG_TAG +#define LOG_TAG "transcoding" + +namespace { +constexpr int BALANCE_VALUE = 2; +using namespace std::chrono_literals; +constexpr int8_t YUV420_SAMPLE_RATIO = 2; +} // namespace + +Transcoding::~Transcoding() { Transcoding::StartRelease(); } + +int32_t Transcoding::CreateVideoDecoder() { + AVCODEC_SAMPLE_LOGW("video mime:%{public}s", sampleInfo_.videoCodecMime.c_str()); + int32_t ret = videoDecoder_->Create(sampleInfo_.videoCodecMime); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + AVCODEC_SAMPLE_LOGW("Create video decoder failed, mime:%{public}s", sampleInfo_.videoCodecMime.c_str()); + } else { + videoDecContext_ = new CodecUserData; + ret = videoDecoder_->Config(sampleInfo_, videoDecContext_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Video Decoder config failed"); + } + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Transcoding::Init(SampleInfo &sampleInfo) { + std::unique_lock lock(mutex_); + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(demuxer_ == nullptr && videoDecoder_ == nullptr, + AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + + sampleInfo_ = sampleInfo; + + int32_t ret = InitDecoder(); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + AVCODEC_SAMPLE_LOGE("Create video decoder failed"); + return AVCODEC_SAMPLE_ERR_ERROR; + } + + ret = InitEncoder(); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + AVCODEC_SAMPLE_LOGE("Create video encoder failed"); + doneCond_.notify_all(); + lock.unlock(); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + + AVCODEC_SAMPLE_LOGI("Succeed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start init_decoder] +int32_t Transcoding::InitDecoder() { + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(demuxer_ == nullptr && videoDecoder_ == nullptr, + AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + + videoDecoder_ = std::make_unique(); + demuxer_ = std::make_unique(); + + isReleased_ = false; + int32_t ret = demuxer_->Create(sampleInfo_); + + if (ret == AVCODEC_SAMPLE_ERR_OK) { + ret = CreateVideoDecoder(); + } else { + AVCODEC_SAMPLE_LOGE("Create audio decoder failed"); + } + return ret; +} +// [End init_decoder] + +int32_t Transcoding::CreateVideoEncoder() { + int32_t ret = videoEncoder_->Create(sampleInfo_.outputVideoCodecMime); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create video encoder failed"); + + videoEncContext_ = new CodecUserData; + ret = videoEncoder_->Config(sampleInfo_, videoEncContext_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Encoder config failed"); + + return AVCODEC_SAMPLE_ERR_OK; +} + +// [Start init_encoder] +int32_t Transcoding::InitEncoder() { + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(muxer_ == nullptr && videoEncoder_ == nullptr, + AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + + videoEncoder_ = std::make_unique(); + muxer_ = std::make_unique(); + + int32_t ret = muxer_->Create(sampleInfo_.outputFd); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create muxer with fd(%{public}d) failed", + sampleInfo_.outputFd); + ret = muxer_->Config(sampleInfo_); + + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create audio encoder failed"); + + ret = CreateVideoEncoder(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create video encoder failed"); + + AVCODEC_SAMPLE_LOGI("Succeed"); + return AVCODEC_SAMPLE_ERR_OK; +} +// [End init_encoder] + +// [Start start_transcoding] +int32_t Transcoding::Start() { + std::unique_lock lock(mutex_); + int32_t ret; + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(demuxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + if (videoDecContext_) { + ret = videoDecoder_->Start(); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + AVCODEC_SAMPLE_LOGE("Video Decoder start failed"); + lock.unlock(); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + isStarted_ = true; + videoDecInputThread_ = std::make_unique(&Transcoding::VideoDecInputThread, this); + videoDecOutputThread_ = std::make_unique(&Transcoding::VideoDecOutputThread, this); + + if (videoDecInputThread_ == nullptr || videoDecOutputThread_ == nullptr) { + AVCODEC_SAMPLE_LOGE("Create thread failed"); + lock.unlock(); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + } + + if (videoEncContext_) { + CHECK_AND_RETURN_RET_LOG(videoEncoder_ != nullptr && muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, + "Already started."); + int32_t ret = muxer_->Start(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Muxer start failed"); + ret = videoEncoder_->Start(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Encoder start failed"); + videoEncOutputThread_ = std::make_unique(&Transcoding::VideoEncOutputThread, this); + if (videoEncOutputThread_ == nullptr) { + AVCODEC_SAMPLE_LOGE("Create thread failed"); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + } + + AVCODEC_SAMPLE_LOGI("Succeed"); + doneCond_.notify_all(); + return AVCODEC_SAMPLE_ERR_OK; +} +// [Start end_transcoding] + +void Transcoding::Stop() { + StartRelease(); +} + +void Transcoding::StartRelease() { + if (!isReleased_) { + isReleased_ = true; + Release(); + } + AVCODEC_SAMPLE_LOGI("StartRelease Done"); +} + +void Transcoding::Release() { + std::lock_guard lock(mutex_); + isStarted_ = false; + if (videoDecInputThread_ && videoDecInputThread_->joinable()) { + videoDecInputThread_->detach(); + videoDecInputThread_.reset(); + } + if (videoDecOutputThread_ && videoDecOutputThread_->joinable()) { + videoDecOutputThread_->detach(); + videoDecOutputThread_.reset(); + } + if (videoEncOutputThread_ && videoEncOutputThread_->joinable()) { + videoEncOutputThread_->detach(); + videoEncOutputThread_.reset(); + } + if (muxer_ != nullptr) { + muxer_->Release(); + muxer_.reset(); + AVCODEC_SAMPLE_LOGI("Muxer release successful"); + } + if (demuxer_ != nullptr) { + demuxer_->Release(); + demuxer_.reset(); + } + if (videoDecoder_ != nullptr) { + videoDecoder_->Release(); + videoDecoder_.reset(); + } + if (videoEncoder_ != nullptr) { + videoEncoder_->Stop(); + videoEncoder_->Release(); + videoEncoder_.reset(); + AVCODEC_SAMPLE_LOGI("Video encoder release successful"); + } + if (videoEncContext_ != nullptr) { + delete videoEncContext_; + videoEncContext_ = nullptr; + } + if (videoDecContext_ != nullptr) { + delete videoDecContext_; + videoDecContext_ = nullptr; + } + doneCond_.notify_all(); + sampleInfo_.playDoneCallback(sampleInfo_.playDoneCallbackData); + AVCODEC_SAMPLE_LOGI("Release Succeed"); +} + +// [Start copy_yuv] +void Transcoding::CopyStrideYUV420SP(CodecBufferInfo &encBufferInfo, CodecBufferInfo &bufferInfo) { + int32_t videoWidth = videoDecContext_->width; + int32_t &stride = videoDecContext_->widthStride; + int32_t size = 0; + uint8_t *tempBufferAddr = encBufferInfo.bufferAddr; + + size += videoDecContext_->height * videoWidth * 3 / 2; + if (videoWidth == videoDecContext_->widthStride && videoDecContext_->heightStride == videoDecContext_->height) { + std::memcpy(tempBufferAddr, bufferInfo.bufferAddr, size); + } else { + // copy Y + for (int32_t row = 0; row < videoDecContext_->height; row++) { + std::memcpy(tempBufferAddr, bufferInfo.bufferAddr, videoWidth); + tempBufferAddr += videoWidth; + bufferInfo.bufferAddr += stride; + } + bufferInfo.bufferAddr += (videoDecContext_->heightStride - videoDecContext_->height) * stride; + + // copy U/V + for (int32_t row = 0; row < (videoDecContext_->height / 2); row++) { + std::memcpy(tempBufferAddr, bufferInfo.bufferAddr, videoWidth); + tempBufferAddr += videoWidth; + bufferInfo.bufferAddr += stride; + } + } + + encBufferInfo.attr.size = size; + encBufferInfo.attr.flags = bufferInfo.attr.flags; + encBufferInfo.attr.offset = bufferInfo.attr.offset; + encBufferInfo.attr.pts = bufferInfo.attr.pts; + + tempBufferAddr = nullptr; + delete tempBufferAddr; +} +// [End copy_yuv] + +// [Start start_decoder_input_thread] +void Transcoding::VideoDecInputThread() { + while (true) { + CHECK_AND_BREAK_LOG(isStarted_, "Decoder input thread out"); + std::unique_lock lock(videoDecContext_->inputMutex); + bool condRet = videoDecContext_->inputCond.wait_for( + lock, 5s, [this]() { return !isStarted_ || !videoDecContext_->inputBufferInfoQueue.empty(); }); + CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out"); + CHECK_AND_CONTINUE_LOG(!videoDecContext_->inputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = videoDecContext_->inputBufferInfoQueue.front(); + videoDecContext_->inputBufferInfoQueue.pop(); + videoDecContext_->inputFrameCount++; + lock.unlock(); + + demuxer_->ReadSample(demuxer_->GetVideoTrackId(), reinterpret_cast(bufferInfo.buffer), + bufferInfo.attr); + + int32_t ret = videoDecoder_->PushInputBuffer(bufferInfo); + CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Push data failed, thread out"); + + CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), + "VideoDecInputThread Catch EOS, thread out"); + } +} +// [End start_decoder_input_thread] + +// [Start start_decoder_output_thread] +void Transcoding::VideoDecOutputThread() { + sampleInfo_.frameInterval = MICROSECOND / sampleInfo_.frameRate; + while (true) { + CHECK_AND_BREAK_LOG(isStarted_, "Decoder output thread out"); + std::unique_lock lock(videoDecContext_->outputMutex); + bool condRet = videoDecContext_->outputCond.wait_for(lock, 5s, [this]() { + return !isStarted_ || + !(videoDecContext_->outputBufferInfoQueue.empty() && videoEncContext_->inputBufferInfoQueue.empty()); + }); + CHECK_AND_BREAK_LOG(isStarted_, "Decoder output thread out"); + CHECK_AND_CONTINUE_LOG(!videoDecContext_->outputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + CHECK_AND_CONTINUE_LOG(!videoEncContext_->inputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = videoDecContext_->outputBufferInfoQueue.front(); + videoDecContext_->outputBufferInfoQueue.pop(); + videoDecContext_->outputFrameCount++; + AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64, + videoDecContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags, + bufferInfo.attr.pts); + lock.unlock(); + + // get Buffer from inputBufferInfoQueue + CodecBufferInfo encBufferInfo = videoEncContext_->inputBufferInfoQueue.front(); + videoEncContext_->inputBufferInfoQueue.pop(); + videoEncContext_->inputFrameCount++; + + AVCODEC_SAMPLE_LOGW( + "Out bufferInfo flags: %{public}u, offset: %{public}d, pts: %{public}u, size: %{public}" PRId64, + bufferInfo.attr.flags, bufferInfo.attr.offset, bufferInfo.attr.pts, bufferInfo.attr.size); + + + encBufferInfo.bufferAddr = OH_AVBuffer_GetAddr(reinterpret_cast(encBufferInfo.buffer)); + bufferInfo.bufferAddr = OH_AVBuffer_GetAddr(reinterpret_cast(bufferInfo.buffer)); + CopyStrideYUV420SP(encBufferInfo, bufferInfo); + + AVCODEC_SAMPLE_LOGW( + "Out encBufferInfo flags: %{public}u, offset: %{public}d, pts: %{public}u, size: %{public}d" PRId64, + encBufferInfo.attr.flags, encBufferInfo.attr.offset, encBufferInfo.attr.pts, encBufferInfo.attr.size); + + OH_AVBuffer_SetBufferAttr(reinterpret_cast(encBufferInfo.buffer), &encBufferInfo.attr); + + // Free Decoder's output buffer + int32_t ret = videoDecoder_->FreeOutputBuffer(bufferInfo.bufferIndex, false); + CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Decoder output thread out"); + + // Push input buffer to Encoder + videoEncoder_->PushInputBuffer(encBufferInfo); + + if (bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS) { + AVCODEC_SAMPLE_LOGW("VideoDecOutputThread Catch EOS, thread out" PRId64); + break; + } + } +} +// [End start_decoder_output_thread] + +// [Start start_Encoder_output_thread] +void Transcoding::VideoEncOutputThread() { + while (true) { + std::unique_lock lock(videoEncContext_->outputMutex); + bool condRet = videoEncContext_->outputCond.wait_for( + lock, 5s, [this]() { return !isStarted_ || !videoEncContext_->outputBufferInfoQueue.empty(); }); + CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out"); + CHECK_AND_CONTINUE_LOG(!videoEncContext_->outputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = videoEncContext_->outputBufferInfoQueue.front(); + videoEncContext_->outputBufferInfoQueue.pop(); + CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), + "VideoEncOutputThread Catch EOS, thread out"); + lock.unlock(); + if ((bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) || + (bufferInfo.attr.flags == AVCODEC_BUFFER_FLAGS_NONE)) { + videoEncContext_->outputFrameCount++; + bufferInfo.attr.pts = videoEncContext_->outputFrameCount * MICROSECOND / sampleInfo_.frameRate; + } else { + bufferInfo.attr.pts = 0; + } + AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64, + videoEncContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags, + bufferInfo.attr.pts); + + muxer_->WriteSample(muxer_->GetVideoTrackId(), reinterpret_cast(bufferInfo.buffer), + bufferInfo.attr); + int32_t ret = videoEncoder_->FreeOutputBuffer(bufferInfo.bufferIndex); + CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Encoder output thread out"); + } + AVCODEC_SAMPLE_LOGI("Exit, frame count: %{public}u", videoEncContext_->outputFrameCount); + StartRelease(); +} +// [End start_Encoder_output_thread] \ No newline at end of file diff --git a/entry/src/main/cpp/sample/transcoding/Transcoding.h b/entry/src/main/cpp/sample/transcoding/Transcoding.h new file mode 100644 index 0000000000000000000000000000000000000000..d24a8f8a9a4c9659d685b8e5a51ba5ee730ba9d0 --- /dev/null +++ b/entry/src/main/cpp/sample/transcoding/Transcoding.h @@ -0,0 +1,97 @@ +/* + * 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 VIDEO_CODEC_PLAYER_H +#define VIDEO_CODEC_PLAYER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "VideoDecoder.h" +#include "multimedia/player_framework/native_avbuffer.h" +#include "Demuxer.h" +#include "SampleInfo.h" +#include "VideoEncoder.h" +#include "Muxer.h" + +class Transcoding { +public: + Transcoding(){}; + ~Transcoding(); + + static Transcoding &GetInstance() { + static Transcoding transcoding; + return transcoding; + } + + int32_t Init(SampleInfo &sampleInfo); + int32_t InitDecoder(); + int32_t InitEncoder(); + int32_t Start(); + void Stop(); + +private: + void VideoDecInputThread(); + void VideoDecOutputThread(); + void VideoEncOutputThread(); + void CopyStrideYUV420SP(CodecBufferInfo &encBufferInfo, CodecBufferInfo &bufferInfo); + void Release(); + void StartRelease(); + int32_t CreateVideoDecoder(); + int32_t CreateVideoEncoder(); + + std::unique_ptr videoDecoder_ = nullptr; + std::unique_ptr demuxer_ = nullptr; + + std::unique_ptr videoEncoder_ = nullptr; + std::unique_ptr muxer_ = nullptr; + + std::mutex mutex_; + std::mutex doneMutex; + std::atomic isStarted_{false}; + std::atomic isReleased_{false}; + std::atomic isVideoDone{true}; + std::atomic isAudioDone{true}; + std::unique_ptr videoDecInputThread_ = nullptr; + std::unique_ptr videoDecOutputThread_ = nullptr; + std::unique_ptr audioDecInputThread_ = nullptr; + std::unique_ptr audioDecOutputThread_ = nullptr; + + std::unique_ptr videoEncOutputThread_ = nullptr; + std::unique_ptr audioEncInputThread_ = nullptr; + std::unique_ptr audioEncOutputThread_ = nullptr; + std::condition_variable doneCond_; + SampleInfo sampleInfo_; + CodecUserData *videoDecContext_ = nullptr; + CodecUserData *audioDecContext_ = nullptr; + CodecUserData *videoEncContext_ = nullptr; + CodecUserData *audioEncContext_ = nullptr; + + std::mutex yuvMutex; + std::condition_variable yuvCond; + std::queue yuvBufferQueue; + + OH_AudioStreamBuilder *builder_ = nullptr; + OH_AudioRenderer *audioRenderer_ = nullptr; + static constexpr int64_t MICROSECOND = 1000000; +}; + +#endif \ No newline at end of file diff --git a/entry/src/main/cpp/sample/transcoding/TranscodingNative.cpp b/entry/src/main/cpp/sample/transcoding/TranscodingNative.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0be4ef4b681631d1ba722eb7486c6d6757e3e1e1 --- /dev/null +++ b/entry/src/main/cpp/sample/transcoding/TranscodingNative.cpp @@ -0,0 +1,110 @@ +/* + * 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 "TranscodingNative.h" +#include +#include "dfx/error/AVCodecSampleError.h" +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xFF00 +#define LOG_TAG "transcoding" + +struct CallbackContext { + napi_env env = nullptr; + napi_ref callbackRef = nullptr; +}; + +void Callback(void *asyncContext) { + uv_loop_s *loop = nullptr; + CallbackContext *context = (CallbackContext *)asyncContext; + napi_get_uv_event_loop(context->env, &loop); + uv_work_t *work = new uv_work_t; + work->data = context; + uv_queue_work( + loop, work, [](uv_work_t *work) {}, + [](uv_work_t *work, int status) { + CallbackContext *context = (CallbackContext *)work->data; + napi_handle_scope scope = nullptr; + // Manage the lifecycle of napi_value to prevent memory leaks. + napi_open_handle_scope(context->env, &scope); + napi_value callback = nullptr; + napi_get_reference_value(context->env, context->callbackRef, &callback); + // Callback to UI side. + napi_call_function(context->env, nullptr, callback, 0, nullptr, nullptr); + napi_close_handle_scope(context->env, scope); + delete context; + delete work; + }); +} + +napi_value TranscodingNative::Start(napi_env env, napi_callback_info info) { + SampleInfo sampleInfo; + size_t argc = 8; + napi_value args[8] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + napi_get_value_int32(env, args[0], &sampleInfo.inputFd); + napi_get_value_int32(env, args[1], &sampleInfo.outputFd); + napi_get_value_int64(env, args[2], &sampleInfo.inputFileOffset); + napi_get_value_int64(env, args[3], &sampleInfo.inputFileSize); + char videoCodecMime[20] = {0}; + size_t videoCodecMimeStrlen = 0; + size_t len = 20; + napi_get_value_string_utf8(env, args[4], videoCodecMime, len, &videoCodecMimeStrlen); + sampleInfo.outputVideoCodecMime = videoCodecMime; + napi_get_value_double(env, args[5], &sampleInfo.outputFrameRate); + napi_get_value_int64(env, args[6], &sampleInfo.outputBitrate); + + auto asyncContext = new CallbackContext(); + asyncContext->env = env; + napi_create_reference(env, args[7], 1, &asyncContext->callbackRef); + + sampleInfo.playDoneCallback = &Callback; + sampleInfo.playDoneCallbackData = asyncContext; + int32_t ret = Transcoding::GetInstance().Init(sampleInfo); + if (ret == AVCODEC_SAMPLE_ERR_OK) { + Transcoding::GetInstance().Start(); + } + return nullptr; +} + +napi_value TranscodingNative::Stop(napi_env env, napi_callback_info info) { + Transcoding::GetInstance().Stop(); + return nullptr; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor classProp[] = { + {"startNative", nullptr, TranscodingNative::Start, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"stopNative", nullptr, TranscodingNative::Stop, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + + napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp); + return exports; +} +EXTERN_C_END + +static napi_module TranscodingModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "transcoding", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterPlayerModule(void) { napi_module_register(&TranscodingModule); } \ No newline at end of file diff --git a/entry/src/main/cpp/sample/transcoding/TranscodingNative.h b/entry/src/main/cpp/sample/transcoding/TranscodingNative.h new file mode 100644 index 0000000000000000000000000000000000000000..c854e2bc8d4faac489463272000f992c2d28a29a --- /dev/null +++ b/entry/src/main/cpp/sample/transcoding/TranscodingNative.h @@ -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. + */ + +#ifndef VIDEO_CODEC_SAMPLE_PLAYER_NATIVE_H +#define VIDEO_CODEC_SAMPLE_PLAYER_NATIVE_H +#include +#include +#include "napi/native_api.h" +#include "Transcoding.h" + +class TranscodingNative { +public: + static napi_value Start(napi_env env, napi_callback_info info); + static napi_value Stop(napi_env env, napi_callback_info info); +}; +#endif \ No newline at end of file diff --git a/entry/src/main/cpp/types/libtranscoding/index.d.ts b/entry/src/main/cpp/types/libtranscoding/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..63a1e779fde7811df4b07e2fc3def6bbe9151e99 --- /dev/null +++ b/entry/src/main/cpp/types/libtranscoding/index.d.ts @@ -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. + */ + +export const startNative: ( + inputFileFd: number, + outputFileFd: number, + inputFileOffset: number, + inputFileSize: number, + videoCodecMime: string, + frameRate: number, + bitRate: number, + cbFn: () => void +) => void; + +export const stopNative: () => void; \ No newline at end of file diff --git a/entry/src/main/cpp/types/libtranscoding/oh-package.json5 b/entry/src/main/cpp/types/libtranscoding/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5696eafda6e2c3f0aef238fbe17f21f871a127cd --- /dev/null +++ b/entry/src/main/cpp/types/libtranscoding/oh-package.json5 @@ -0,0 +1,21 @@ +/* + * 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": "libtranscoding.so", + "types": "./index.d.ts", + "version": "1.0.0", + "decreiption": "Transcoding interface." +} \ No newline at end of file diff --git a/entry/src/main/ets/common/CommonConstants.ets b/entry/src/main/ets/common/CommonConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..0b8ef4e392be0a301186f65d919d4155a41b2344 --- /dev/null +++ b/entry/src/main/ets/common/CommonConstants.ets @@ -0,0 +1,167 @@ +/* + * 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 { camera } from '@kit.CameraKit'; + +export class CommonConstants { + /** + * Index page Tag. + */ + static readonly INDEX_TAG: string = 'INDEX'; + /** + * Recorder page Tag. + */ + static readonly RECORDER_TAG: string = 'RECORDER'; + /** + * Default ID. + */ + static readonly DEFAULT_ID: string = '-1'; + /** + * Default time. + */ + static readonly DEFAULT_TIME: string = '00:00'; + /** + * PX. + */ + static readonly PX: string = 'px'; + /** + * Default value. + */ + static readonly DEFAULT_VALUE: number = 0; + /** + * Default profile. + */ + static readonly DEFAULT_PROFILE: camera.Profile = { + format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, + size: { + width: 1920, + height: 1080 + } + }; + /** + * Video avc mime type. + */ + static readonly MIME_VIDEO_AVC: string = 'video/avc'; + /** + * Video hevc mime type. + */ + static readonly MIME_VIDEO_HEVC: string = 'video/hevc'; + /** + * Default width. + */ + static readonly DEFAULT_WIDTH: number = 1920; + /** + * Default height. + */ + static readonly DEFAULT_HEIGHT: number = 1080; + /** + * 4K video width. + */ + static readonly VIDEO_WIDTH_4K: number = 3840; + /** + * 4K video height. + */ + static readonly VIDEO_HEIGHT_4K: number = 2160; + /** + * 1080P video width. + */ + static readonly VIDEO_WIDTH_1080P: number = 1920; + /** + * 1080P video height. + */ + static readonly VIDEO_HEIGHT_1080P: number = 1080; + /** + * 720P video width. + */ + static readonly VIDEO_WIDTH_720P: number = 1280; + /** + * 720P video height. + */ + static readonly VIDEO_HEIGHT_720P: number = 720; + /** + * 10M bitrate. + */ + static readonly BITRATE_VIDEO_10M: number = 10 * 1024 * 1024; + /** + * 20M bitrate. + */ + static readonly BITRATE_VIDEO_20M: number = 20 * 1024 * 1024; + /** + * 30M bitrate. + */ + static readonly BITRATE_VIDEO_30M: number = 30 * 1024 * 1024; + /** + * 30 FPS. + */ + static readonly FRAMERATE_VIDEO_30FPS: number = 30; + /** + * 60 FPS. + */ + static readonly FRAMERATE_VIDEO_60FPS: number = 60; + /** + * Duration. + */ + static readonly DURATION: number = 2000; + /** + * The distance between toast dialog box and the bottom of screen. + */ + static readonly BOTTOM: number = 80; + /** + * Default picker item height. + */ + static readonly DEFAULT_PICKER_ITEM_HEIGHT: number = 30; + /** + * Selected text style font size. + */ + static readonly SELECTED_TEXT_STYLE_FONT_SIZE: number = 15; + /** + * Video mime type. + */ + static readonly VIDEO_MIMETYPE: string[] = ['H264', 'H265']; + /** + * Video resolution. + */ + static readonly VIDEO_RESOLUTION: string[] = ['30M', '20M', '10M']; + /** + * Video framerate. + */ + static readonly VIDEO_FRAMERATE: string[] = ['30Fps', '60Fps']; + /** + * Video recorderInfo. + */ + static readonly RECORDER_INFO: string[][] = [ + CommonConstants.VIDEO_MIMETYPE, CommonConstants.VIDEO_RESOLUTION, CommonConstants.VIDEO_FRAMERATE + ]; + /** + * The number corresponding to true. + */ + static readonly TRUE: number = 1; + /** + * The number corresponding to false. + */ + static readonly FALSE: number = 0; + /** + * Min range. + */ + static readonly MIN_RANGE: number = 1; + /** + * Max range. + */ + static readonly MAX_RANGE: number = 30; + /** + * Full size. + */ + static readonly FULL_SIZE: string = '100%'; +} \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/Logger.ets b/entry/src/main/ets/common/utils/Logger.ets new file mode 100644 index 0000000000000000000000000000000000000000..82525da9ada62154d39805784a9fa05750ac19ce --- /dev/null +++ b/entry/src/main/ets/common/utils/Logger.ets @@ -0,0 +1,43 @@ +/* + * 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'; + +class Logger { + private domain: number; + private format: string = '%{public}s'; + + constructor() { + this.domain = 0xFF00; + } + + debug(...arg: string[]): void { + hilog.debug(this.domain, arg[0], this.format, arg[1]); + } + + info(...arg: string[]): void { + hilog.info(this.domain, arg[0], this.format, arg[1]); + } + + warn(...arg: string[]): void { + hilog.warn(this.domain, arg[0], this.format, arg[1]); + } + + error(...arg: string[]): void { + hilog.error(this.domain, arg[0], this.format, arg[1]); + } +} + +export default new Logger(); \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/TimeUtils.ets b/entry/src/main/ets/common/utils/TimeUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..16e0cf7ad82afaa62643905b42e637ee6223cb70 --- /dev/null +++ b/entry/src/main/ets/common/utils/TimeUtils.ets @@ -0,0 +1,26 @@ +/* + * 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. + */ +/** + * Time handling function. + * @param time + * @returns + */ +export function formatVideoTime(totalSeconds: number): string { + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; +} \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..a9c6343263d842ff073d9ae054ed1380e9235dc4 --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,60 @@ +/* + * 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 { abilityAccessCtrl, AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import Logger from '../common/utils/Logger'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + 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.getMainWindowSync().setWindowKeepScreenOn(true); + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + AppStorage.setOrCreate("context", windowStage.getMainWindowSync().getUIContext()); + 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'); + } +}; \ No newline at end of file diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..7b815b479cc363e7c9f0db13131d879fa9b790f9 --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -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. + */ + +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)); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/model/VideoDataModel.ets b/entry/src/main/ets/model/VideoDataModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..dca2f013d3320ea2e514c54d08d0788e2e68dd91 --- /dev/null +++ b/entry/src/main/ets/model/VideoDataModel.ets @@ -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. + */ + +import { camera } from '@kit.CameraKit'; +import { CommonConstants as Const } from '../common/CommonConstants'; + +export class VideoDataModel { + public surfaceId: string = ''; + public isHDRVivid: number = Const.DEFAULT_VALUE; + public outputfd: number = -1; + public frameRate: number = Const.FRAMERATE_VIDEO_30FPS; + public previewProfile: camera.Profile = Const.DEFAULT_PROFILE; + public videoCodecMime: string | null = Const.MIME_VIDEO_AVC; + public bitRate: number = Const.BITRATE_VIDEO_20M; + + setCodecFormat(isHDR: number, codecMime: string): void { + this.isHDRVivid = isHDR; + this.videoCodecMime = codecMime; + } + + setResolution(bit: number): void { + this.bitRate = bit; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..b842f3a2a1a1f1a009e4a9e3d504532e302cc64d --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,244 @@ +/* + * 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 { CommonConstants as Const } from '../common/CommonConstants'; +import { VideoPlayer } from './VideoPlayer'; +import { LoadingDialog } from '@kit.ArkUI'; +import { VideoDataModel } from '../model/VideoDataModel'; +import { fileIo } from '@kit.CoreFileKit'; +import transcoding from 'libtranscoding.so'; +import { formatVideoTime } from '../common/utils/TimeUtils'; + +@Entry +@Component +struct Index { + @Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack(); + @State currentTime: number = 0; + @State durationTime: number = 0; + @State isStart: boolean = true; + @State isTranscoding: boolean = false; + isConfig: boolean = false; + private videoDataModel: VideoDataModel = new VideoDataModel(); + private controller: VideoController = new VideoController(); + customDialogId: number = 0; + dialogController: CustomDialogController = new CustomDialogController({ + builder: LoadingDialog({ + content: $r('app.string.transcoding') + }), + autoCancel: false + }); + + @Builder + PagesMap(name: string) { + if (name === 'VideoPlayer') { + NavDestination() { + VideoPlayer() + } + .title($r('app.string.video_player')) + .backgroundColor('#F1F3F5') + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + } + + build() { + Navigation(this.pageStack) { + Column() { + Text($r('app.string.buffer_mode')) + .width('100%') + .fontColor('#E6000000') + .fontSize(26) + .fontWeight(700) + .lineHeight(40) + .textAlign(TextAlign.Start) + .padding(16) + + Blank() + + Column() { + Stack() { + Video({ src: $rawfile('video_sample.mp4'), controller: this.controller }) + .width('100%') + .height('100%') + .autoPlay(true) + .controls(false) + .objectFit(1) + .zIndex(0) + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + console.info(this.durationTime.toString()); + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onFinish(() => { + this.isStart = !this.isStart; + }) + .transition(TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Sharp })) + + Row() { + Image(this.isStart ? $r('app.media.pause') : $r('app.media.play')) + .width(18) + .height(18) + .onClick(() => { + if (this.isStart) { + this.controller.pause(); + this.isStart = !this.isStart; + } else { + this.controller.start(); + this.isStart = !this.isStart; + } + }) + + Text(formatVideoTime(this.currentTime)) + .fontColor(Color.White) + .fontSize(12) + .margin({ left: '12vp' }) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .onChange((value: number, mode: SliderChangeMode) => { + // Set the video playback progress to jump to the value + this.controller.setCurrentTime(value); + }) + .layoutWeight(1) + Text(formatVideoTime(this.durationTime)) + .fontColor(Color.White) + .fontSize(12) + } + .padding({ left: '16vp', right: '16vp' }) + .zIndex(1) + .height(43) + .width('100%') + } + .alignContent(Alignment.Bottom) + .width('100%') + .layoutWeight(1) + } + .backgroundColor('') + .width('100%') + .layoutWeight(1) + + Blank() + + Column() { + Button($r('app.string.config_video')) + .id('config') + .onClick(() => { + this.getUIContext().showTextPickerDialog({ + defaultPickerItemHeight: Const.DEFAULT_PICKER_ITEM_HEIGHT, + selectedTextStyle: ({ + font: ({ + size: Const.SELECTED_TEXT_STYLE_FONT_SIZE + }) + }), + range: Const.RECORDER_INFO, + canLoop: false, + alignment: DialogAlignment.Center, + onAccept: (value: TextPickerResult) => { + switch (value.value[0]) { + case Const.VIDEO_MIMETYPE[0]: { + this.videoDataModel.setCodecFormat(Const.TRUE, Const.MIME_VIDEO_AVC); + break; + } + case Const.VIDEO_MIMETYPE[1]: { + this.videoDataModel.setCodecFormat(Const.FALSE, Const.MIME_VIDEO_HEVC); + break; + } + default: + break; + } + + switch (value.value[1]) { + case Const.VIDEO_RESOLUTION[0]: { + this.videoDataModel.setResolution(Const.BITRATE_VIDEO_30M); + break; + } + case Const.VIDEO_RESOLUTION[1]: { + this.videoDataModel.setResolution(Const.BITRATE_VIDEO_20M); + break; + } + case Const.VIDEO_RESOLUTION[2]: { + this.videoDataModel.setResolution(Const.BITRATE_VIDEO_10M); + break; + } + default: + break; + } + + switch (value.value[2]) { + case Const.VIDEO_FRAMERATE[0]: { + this.videoDataModel.frameRate = Const.FRAMERATE_VIDEO_30FPS; + break; + } + case Const.VIDEO_FRAMERATE[1]: { + this.videoDataModel.frameRate = Const.FRAMERATE_VIDEO_60FPS; + break; + } + default: + break; + } + } + }); + + }) + .size({ + width: '100%', + height: 40 + }) + .margin({ + bottom: 16 + }) + + Button($r('app.string.start_transcoding')) + .onClick(() => { + this.dialogController.open(); + this.transcoding(); + }) + .width('100%') + } + .padding(16) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Start) + } + .hideTitleBar(true) + .mode(NavigationMode.Stack) + .backgroundColor('#F1F3F5') + .navDestination(this.PagesMap) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + transcoding() { + this.getUIContext().getHostContext()?.resourceManager.getRawFd('video_sample.mp4').then((inputDesc) => { + let outputFile = fileIo.openSync(this.getUIContext().getHostContext()?.cacheDir + '/transcoding.mp4', + fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + this.isTranscoding = true; + transcoding.startNative(inputDesc.fd, outputFile.fd, inputDesc.offset, inputDesc.length, + this.videoDataModel.videoCodecMime, this.videoDataModel.frameRate, this.videoDataModel.bitRate, + () => { + this.isTranscoding = false; + this.dialogController.close(); + this.pageStack.pushPathByName('VideoPlayer', ''); + }) + }); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/VideoPlayer.ets b/entry/src/main/ets/pages/VideoPlayer.ets new file mode 100644 index 0000000000000000000000000000000000000000..c9238b1a1e949b30d52e5690e97e767b5c37df4a --- /dev/null +++ b/entry/src/main/ets/pages/VideoPlayer.ets @@ -0,0 +1,240 @@ +/* + * 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 { formatVideoTime } from '../common/utils/TimeUtils'; + +@Component +export struct VideoPlayer { + @Consume('NavPathStack') pageInfos: NavPathStack; + @State currentTime: number = 0 + @State durationTime: number = 0 + @State isStart: boolean = true; + @State transCurrentTime: number = 0 + @State transDurationTime: number = 0 + @State transIsStart: boolean = true; + @State currentIndex: number = 0; + @State tabColorOne: string = '#191919'; + @State tabColorTwo: string = '#666666'; + @State backgroundColorOne: string = '#FFFFFF'; + @State backgroundColorTwo: string = '#E5E5E5'; + private tabsController: TabsController = new TabsController(); + private controller: VideoController = new VideoController(); + private transController: VideoController = new VideoController(); + + build() { + Column() { + Row() { + Button($r('app.string.before_transcoding')) + .onClick(() => { + this.tabsController.changeIndex(1); + this.currentIndex = 1; + this.changeIndex(); + }) + .fontColor(this.tabColorTwo) + .backgroundColor(this.backgroundColorTwo) + .margin(2) + .width(80) + Button($r('app.string.after_transcoding')) + .onClick(() => { + this.tabsController.changeIndex(0); + this.currentIndex = 0; + this.changeIndex(); + }) + .fontColor(this.tabColorOne) + .backgroundColor(this.backgroundColorOne) + .margin(2) + .width(80) + } + .borderRadius(20) + .backgroundColor('#E5E5E5') + .margin({ bottom: 16 }) + .width(168) + + Tabs({ index: this.currentIndex, controller: this.tabsController }) { + TabContent() { + Column() { + Stack() { + Video({ + src: 'file://' + this.getUIContext().getHostContext()?.cacheDir + '/transcoding.mp4', + controller: this.transController + }) + .width('100%') + .height('100%') + .autoPlay(true) + .controls(false) + .objectFit(1) + .zIndex(0) + .onPrepared((event) => { + if (event) { + this.transDurationTime = event.duration + console.info(this.transDurationTime.toString()); + } + }) + .onUpdate((event) => { + if (event) { + this.transCurrentTime = event.time + } + }) + .onFinish(() => { + this.transIsStart = !this.transIsStart; + }) + .transition(TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Sharp })) + + Row() { + Image(this.transIsStart ? $r('app.media.pause') : $r('app.media.play')) + .width(18) + .height(18) + .onClick(() => { + if (this.transIsStart) { + this.transController.pause(); + this.transIsStart = !this.transIsStart; + } else { + this.transController.start(); + this.transIsStart = !this.transIsStart; + } + }) + + Text(formatVideoTime(this.transCurrentTime)) + .fontColor(Color.White) + .fontSize(12) + .margin({ left: '12vp' }) + Slider({ + value: this.transCurrentTime, + min: 0, + max: this.transDurationTime + }) + .onChange((value: number, mode: SliderChangeMode) => { + // Set the video playback progress to jump to the value + this.transController.setCurrentTime(value); + }) + .layoutWeight(1) + Text(formatVideoTime(this.transDurationTime)) + .fontColor(Color.White) + .fontSize(12) + } + .padding({ left: '16vp', right: '16vp' }) + .zIndex(1) + .height(43) + .width('100%') + } + .alignContent(Alignment.Bottom) + .width('100%') + .layoutWeight(1) + } + .width('100%') + .layoutWeight(1) + } + + TabContent() { + Column() { + Stack() { + Video({ src: $rawfile('video_sample.mp4'), controller: this.controller }) + .width('100%') + .height('100%') + .autoPlay(true) + .controls(false) + .objectFit(1) + .zIndex(0) + .onPrepared((event) => { + if (event) { + this.durationTime = event.duration + console.info(this.durationTime.toString()); + } + }) + .onUpdate((event) => { + if (event) { + this.currentTime = event.time + } + }) + .onFinish(() => { + this.isStart = !this.isStart; + }) + .transition(TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Sharp })) + + Row() { + Image(this.isStart ? $r('app.media.pause') : $r('app.media.play')) + .width(18) + .height(18) + .onClick(() => { + if (this.isStart) { + this.controller.pause(); + this.isStart = !this.isStart; + } else { + this.controller.start(); + this.isStart = !this.isStart; + } + }) + + Text(formatVideoTime(this.currentTime)) + .fontColor(Color.White) + .fontSize(12) + .margin({ left: '12vp' }) + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime + }) + .onChange((value: number, mode: SliderChangeMode) => { + // Set the video playback progress to jump to the value + this.controller.setCurrentTime(value); + }) + .layoutWeight(1) + Text(formatVideoTime(this.durationTime)) + .fontColor(Color.White) + .fontSize(12) + } + .padding({ left: '16vp', right: '16vp' }) + .zIndex(1) + .height(43) + .width('100%') + } + .alignContent(Alignment.Bottom) + .width('100%') + .layoutWeight(1) + } + .width('100%') + .layoutWeight(1) + } + } + .barHeight(0) + .scrollable(false) + .barMode(BarMode.Fixed) + .height('80%') + + Blank() + + Column() { + Button($r('app.string.return')) + .width('100%') + .onClick(() => { + this.pageInfos.popToIndex(-1); + }) + } + .margin({ top: '12vp' }) + .justifyContent(FlexAlign.End) + .padding({ left: 16, right: 16, bottom: 16 }) + .width('100%') + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Start) + } + + changeIndex() { + this.tabColorOne = this.currentIndex === 0 ? '#191919' : '#666666'; + this.backgroundColorOne = this.currentIndex === 0 ? '#FFFFFF' : '#E5E5E5'; + this.tabColorTwo = this.currentIndex === 1 ? '#191919' : '#666666'; + this.backgroundColorTwo = this.currentIndex === 1 ? '#FFFFFF' : '#E5E5E5'; + } +} diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4faca83d009170629404f772c65c6412b3d243c2 --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,52 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "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": [ + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..9461eae3f64a855b89b972faaa32a97b38c3b784 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,12 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + }, + { + "name": "set_button_background", + "value": "#E5E7E9" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..d7b10009523147139f2f24426a73d6af5f8665be --- /dev/null +++ b/entry/src/main/resources/base/element/float.json @@ -0,0 +1,72 @@ +{ + "float": [ + { + "name": "set_image_width", + "value": "24vp" + }, + { + "name": "set_image_height", + "value": "24vp" + }, + { + "name": "set_button_width", + "value": "40vp" + }, + { + "name": "set_button_height", + "value": "40vp" + }, + { + "name": "set_button_margin_right", + "value": "16vp" + }, + { + "name": "set_row_height", + "value": "40vp" + }, + { + "name": "set_row_margin_top", + "value": "8vp" + }, + { + "name": "window_margin_top", + "value": "150vp" + }, + { + "name": "index_button_width", + "value": "288vp" + }, + { + "name": "index_button_height", + "value": "40vp" + }, + { + "name": "button_margin_bottom", + "value": "12vp" + }, + { + "name": "last_button_margin_bottom", + "value": "16vp" + }, + { + "name": "index_column_height", + "value": "200vp" + }, + { + "name": "recorder_time_margin_bottom", + "value": "500vp" + }, + { + "name": "recorder_image_width", + "value": "60vp" + }, + { + "name": "recorder_image_height", + "value": "60vp" + }, + { + "name": "recorder_image_margin_top", + "value": "550vp" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..5bf7538fd0a5105ef9ca76a73393808cc4f689af --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,80 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecBufferMode" + }, + { + "name": "alert", + "value": "None video selected" + }, + { + "name": "reason", + "value": "allows an app to use the microphone" + }, + { + "name": "video_transcoding", + "value": "Video Transcoding" + }, + { + "name": "video_player", + "value": "Video Player" + }, + { + "name": "buffer_mode", + "value": "Video Transcoding Based On Buffer Mode" + }, + { + "name": "select_video", + "value": "Select Video" + }, + { + "name": "config_video", + "value": "Configuration" + }, + { + "name": "start_transcoding", + "value": "Video Transcoding" + }, + { + "name": "transcoding", + "value": "Transcoding..." + }, + { + "name": "before_transcoding", + "value": "Before" + }, + { + "name": "after_transcoding", + "value": "After" + }, + { + "name": "return", + "value": "Return" + }, + { + "name": "alert_setting", + "value": "The setting is not supported by the current device." + }, + { + "name": "saveButtonNote", + "value": "Allow AVCodecVideo to save captured video to gallery?" + }, + { + "name": "saveButtonCancel", + "value": "Cancel" + }, + { + "name": "saveButtonTitle", + "value": "Confirm the video storage location" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/camera_pause_video_4x.png b/entry/src/main/resources/base/media/camera_pause_video_4x.png new file mode 100644 index 0000000000000000000000000000000000000000..84059db71f51f23c8f1151416a35807c245065c7 Binary files /dev/null and b/entry/src/main/resources/base/media/camera_pause_video_4x.png differ diff --git a/entry/src/main/resources/base/media/gearshape.svg b/entry/src/main/resources/base/media/gearshape.svg new file mode 100644 index 0000000000000000000000000000000000000000..75dde1ac740b1d00d1258826c8584f808e2f2f14 --- /dev/null +++ b/entry/src/main/resources/base/media/gearshape.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a354ba394b7575c510700847d6473ecd0b1e740 Binary files /dev/null and b/entry/src/main/resources/base/media/icon.png differ diff --git a/entry/src/main/resources/base/media/pause.svg b/entry/src/main/resources/base/media/pause.svg new file mode 100644 index 0000000000000000000000000000000000000000..e5c48232301dfafc105d1933c751d2188eff93e1 --- /dev/null +++ b/entry/src/main/resources/base/media/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/play.svg b/entry/src/main/resources/base/media/play.svg new file mode 100644 index 0000000000000000000000000000000000000000..d7ad35ed1acb6275e50670a2dc88f3920a421a28 --- /dev/null +++ b/entry/src/main/resources/base/media/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/transcoding_success.svg b/entry/src/main/resources/base/media/transcoding_success.svg new file mode 100644 index 0000000000000000000000000000000000000000..521ba4289dd5934285ff717e7faf58dcaabf7a71 --- /dev/null +++ b/entry/src/main/resources/base/media/transcoding_success.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8ff1556fb842d316597c850c38970469070e217f --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,80 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecBufferMode" + }, + { + "name": "alert", + "value": "None video selected" + }, + { + "name": "reason", + "value": "allows an app to use the microphone" + }, + { + "name": "video_transcoding", + "value": "Video Transcoding" + }, + { + "name": "transcoding", + "value": "Transcoding..." + }, + { + "name": "before_transcoding", + "value": "Before" + }, + { + "name": "after_transcoding", + "value": "After" + }, + { + "name": "video_player", + "value": "Video Player" + }, + { + "name": "buffer_mode", + "value": "Video Transcoding Based On Buffer Mode" + }, + { + "name": "select_video", + "value": "Select Video" + }, + { + "name": "config_video", + "value": "Configuration" + }, + { + "name": "start_transcoding", + "value": "Start Transcoding" + }, + { + "name": "alert_setting", + "value": "The setting is not supported by the current device." + }, + { + "name": "saveButtonNote", + "value": "Allow AVCodecVideo to save captured video to gallery?" + }, + { + "name": "saveButtonCancel", + "value": "Cancel" + }, + { + "name": "return", + "value": "Return" + }, + { + "name": "saveButtonTitle", + "value": "Confirm the video storage location" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/rawfile/video_sample.mp4 b/entry/src/main/resources/rawfile/video_sample.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b529170de5433cb2ec4a9d092d923ffd9e3583a4 Binary files /dev/null and b/entry/src/main/resources/rawfile/video_sample.mp4 differ diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..fd51cc4535dabf2fab249e65f3f2a146b35c2248 --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,81 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecBufferMode" + }, + { + "name": "alert", + "value": "未选择视频!" + }, + + { + "name": "reason", + "value": "允许应用使用麦克风" + }, + { + "name": "video_transcoding", + "value": "视频转码" + }, + { + "name": "transcoding", + "value": "视频转码中" + }, + { + "name": "video_player", + "value": "视频播放" + }, + { + "name": "buffer_mode", + "value": "基于Buffer模式进行转码" + }, + { + "name": "select_video", + "value": "选择视频文件" + }, + { + "name": "config_video", + "value": "参数设置" + }, + { + "name": "start_transcoding", + "value": "开始转码" + }, + { + "name": "before_transcoding", + "value": "转码前" + }, + { + "name": "after_transcoding", + "value": "转码后" + }, + { + "name": "alert_setting", + "value": "当前设备不支持该设置。" + }, + { + "name": "saveButtonNote", + "value": "是否允许AVCodecVideo保存拍摄的视频到图库?" + }, + { + "name": "saveButtonCancel", + "value": "取消" + }, + { + "name": "return", + "value": "返回首页" + }, + { + "name": "saveButtonTitle", + "value": "视频保存位置确认" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..85e8d4b2175fc4747650344f025e7d145bc3d361 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.1.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d9771b7e2f4c20e0b1b51392d8c95e9d6450b1c8 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,22 @@ +/* + * 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.1.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": {} +} diff --git a/screenshots/device/AVCodec_Index.png b/screenshots/device/AVCodec_Index.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c3fc0122d8aed4e889183360470cbb65197436 Binary files /dev/null and b/screenshots/device/AVCodec_Index.png differ