diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c6f6a66c8320fc1185725068fb80e8d88613e369 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.samples.AVCodecSample", + "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..daca7dfe04f11a3b64c73b3dbb3d59aaa12df831 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "AVCodecVideo" + } + ] +} 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..338e5b0bc22082e0ffcc7121c2ed3897a3ddccb0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,78 @@ + Copyright (c) 2024 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 73be0d5688a6d9b1ffe11b255de538e317c32047..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# AVCodecVideo - -#### Description -本实例基于AVCodec能力,提供基于视频编解码的视频播放和录制的功能。 - -#### 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 8c3f9de664f332e4da6170ca7a8b5d4f00f1a7a6..ebd7f4ff51612a7458b03c6611ec02d262c93fed 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,169 @@ -# AVCodecVideo +# 基于AVCodec能力的视频编解码 -#### 介绍 +### 介绍 本实例基于AVCodec能力,提供基于视频编解码的视频播放和录制的功能。 +- 视频播放的主要流程是将视频文件通过解封装->解码->送显/播放。 +- 视频录制的主要流程是相机采集->编码->封装成mp4文件。 -#### 软件架构 -软件架构说明 +### 播放支持的原子能力规格 +| 媒体格式 | 封装格式 | 码流格式 | +|------|:--------|:------------------------------------| +| 视频 | mp4 | 视频码流:H.264/H.265, 音频码流:AudioVivid | +| 视频 | mkv | 视频码流:H.264/H.265, 音频码流:aac/mp3/opus | +| 视频 | mpeg-ts | 视频码流:H.264, 音频码流:AudioVivid | +### 录制支持的原子能力规格 -#### 安装教程 +| 封装格式 | 视频编解码类型 | +|------|-------------| +| mp4 | H.264/H.265 | AAC、MPEG(MP3) | +| m4a | AVC(H.264) | -1. xxxx -2. xxxx -3. xxxx +注意,本示例仅支持视频录制,未集成音频能力 -#### 使用说明 +### 效果预览 +| 应用主界面 | 应用使用展示 | +|------------------------------------------------------------|------------------------------------------------------------| +| ![AVCodec_Index.png](screenshots/device/AVCodec_Index.png) | ![AVCodecSample.gif](screenshots/device/AVCodecSample.gif) | -1. xxxx -2. xxxx -3. xxxx +### 使用说明 +1. 弹出是否允许“AVCodec”访问图片与视频?点击“允许” -#### 参与贡献 +2. 弹出是否允许“AVCodec”访问文件?点击“允许” -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +3. 弹出是否允许“AVCodec”使用相机?点击“允许” +#### 录制 -#### 特技 +1. 点击“录制” -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/) +2. 选取视频输出路径,默认为【我的手机】文件夹下 + +3. 录制完成后点击“停止录制” + +#### 播放 + +1. 推送视频文件至storage/media/100/local/files/Docs下或点击下方“开始录制”,录制一个视频文件(无音频) + +2. 点击播放按钮,选择文件,开始播放 + +### 工程目录 + +``` +├──entry/src/main/cpp # Native层 +│ ├──capbilities # 能力接口和实现 +│ │ ├──include # 能力接口 +│ │ ├──AudioDecoder.cpp # 音频解码实现 +│ │ ├──Demuxer.cpp # 解封装实现 +│ │ ├──Muxer.cpp # 封装实现 +│ │ ├──VideoDecoder.cpp # 视频解码实现 +│ │ └──VideoEncoder.cpp # 视频编码实现 +│ ├──common # 公共模块 +│ │ ├──dfx # 日志 +│ │ ├──SampleCallback.cpp # 编解码回调实现 +│ │ ├──SampleCallback.h # 编解码回调定义 +│ │ └──SampleInfo.h # 功能实现公共类 +│ ├──render # 送显模块接口和实现 +│ │ ├──include # 送显模块接口 +│ │ ├──EglCore.cpp # 送显参数设置 +│ │ ├──PluginManager.cpp # 送显模块管理实现 +│ │ └──PluginRender.cpp # 送显逻辑实现 +│ ├──sample # Native层 +│ │ ├──player # Native层播放接口和实现 +│ │ │ ├──Player.cpp # Native层播放功能调用逻辑的实现 +│ │ │ ├──Player.h # Native层播放功能调用逻辑的接口 +│ │ │ ├──PlayerNative.cpp # Native层 播放的入口 +│ │ │ └──PlayerNative.h +│ │ └──recorder # Native层录制接口和实现 +│ │ ├──Recorder.cpp # Native层录制功能调用逻辑的实现 +│ │ ├──Recorder.h # Native层录制功能调用逻辑的接口 +│ │ ├──RecorderNative.cpp # Native层 录制的入口 +│ │ └──RecorderNative.h +│ ├──types # Native层暴露上来的接口 +│ │ ├──libplayer # 播放模块暴露给UI层的接口 +│ │ └──librecorder # 录制模块暴露给UI层的接口 +│ └──CMakeLists.txt # 编译入口 +├──ets # UI层 +│ ├──common # 公共模块 +│ │ ├──utils # 共用的工具类 +│ │ │ ├──DateTimeUtils.ets # 获取当前时间 +│ │ │ ├──Logger.ets # 日志工具 +│ │ │ └──SaveAsset.ets # 选取文件保持位置 +│ │ └──CommonConstants.ets # 参数常量 +│ ├──entryability # 应用的入口 +│ │ └──EntryAbility.ets +│ ├──entrybackupability +│ │ └──EntryBackupAbility.ets +│ └──pages # EntryAbility 包含的页面 +│ ├──Index.ets # 首页/播放页面 +│ └──Recorder.ets # 录制页面 +├──resources # 用于存放应用所用到的资源文件 +│ ├──base # 该目录下的资源文件会被赋予唯一的ID +│ │ ├──element # 用于存放字体和颜色 +│ │ ├──media # 用于存放图片 +│ │ └──profile # 应用入口首页 +│ ├──en_US # 设备语言是美式英文时,优先匹配此目录下资源 +│ └──zh_CN # 设备语言是简体中文时,优先匹配此目录下资源 +└──module.json5 # 模块配置信息 +``` + +### 具体实现 + +#### *录制* +##### UI层 +1. 在UI层Index页面,用户点击“录制”后,会调起文件管理,用户选择一个输出地址。录制结束后,文件会存放于此。 +2. 选择好文件后,会用刚刚打开的fd,和用户预设的录制参数,掉起ArkTS的initNative,待初始化结束后,调用OH_NativeWindow_GetSurfaceId接口,得到NativeWindow的surfaceId,并把surfaceId回调回UI层。 +3. UI层拿到编码器给的surfaceId后,调起页面路由,携带该surfaceId,跳转到Recorder页面; +4. 录制页面XComponent构建时,会调起.onLoad()方法,此方法首先会拿到XComponent的surfaceId,然后调起createDualChannelPreview(),此函数会建立一个相机生产,XComponent和编码器的surface消费的生产消费模型。 + +##### Native层 +1. 进入录制界面后,编码器启动,开始对UI层相机预览流进行编码。 +2. 编码器每编码成功一帧,sample_callback.cpp的输出回调OnNewOutputBuffer()就会调起一次,此时用户会拿到AVCodec框架给出的OH_AVBuffer; +3. 在输出回调中,用户需手动把帧buffer、index存入输出队列中,并通知输出线程解锁; +4. 在输出线程中,把上一步的帧信息储存为bufferInfo后,pop出队; +5. 在输出线程中,使用上一步的bufferInfo,调用封装接口WriteSample后,这一帧被封装入MP4中; +6. 最后调用FreeOutputBuffer接口后,这一帧buffer释放回AVCodec框架,实现buffer轮转。 + +#### *播放* +##### UI层 +1. 在UI层Index页面,用户点击播放按钮后,触发点击事件,调起selectFile()函数,该函数会调起文件管理的选择文件模块,拿到用户选取文件的路径; +2. 用户选择文件成功后,调起play()函数,该函数会根据上一步获取到的路径,打开一个文件,并获取到该文件的大小,改变按钮状态为不可用,之后调起ArkTS层暴露给应用层的playNative()接口; +3. 根据playNative字段,调起PlayerNative::Play()函数,此处会注册播放结束的回调。 +4. 播放结束时,Callback()中napi_call_function()接口调起,通知应用层,恢复按钮状态为可用。 + +##### ArkTS层 +1. 在PlayerNative.cpp的Init()中调用PluginManager()中的Export()方法,注册OnSurfaceCreatedCB()回调,当屏幕上出现新的XComponent时,将其转换并赋给单例类PluginManager中的pluginWindow_; + +##### Native层 +1. 具体实现原理: + - 解码器Start后,解码器每拿到一帧,OnNeedInputBuffer就会被调起一次,AVCodec框架会给用户一个OH_AVBuffer。 + - 在输入回调中,用户需手动把帧buffer、index存入输入队列中,并同时输入线程解锁。 + - 在输入线程中,把上一步的帧信息储存为bufferInfo后,pop出队。 + - 在输入线程中,使用上一步的bufferInfo,调用ReadSample接口解封装帧数据。 + - 在输入线程中,使用解封装后的bufferInfo,调用解码的PushInputData接口,此时这片buffer用完,返回框架,实现buffer轮转。 + - PushInputData后,这一帧开始解码,每解码完成一帧,输出回调会被调起一次,用户需手动把帧buffer、index存入输出队列中。 + - 在输出线程中,把上一步的帧信息储存为bufferInfo后,pop出队。 + - 在输出线程中,调用FreeOutputData接口后,就会送显并释放buffer。释放的buffer会返回框架,实现buffer轮转。 +2. 解码器config阶段,OH_VideoDecoder_SetSurface接口的入参OHNativeWindow*,即为PluginManager中的pluginWindow_。 +3. 解码器config阶段,SetCallback接口,sample_callback.cpp的输入输出回调需将回调上来的帧buffer和index存入一个用户自定义容器sample_info.h中,方便后续操作。 +4. Player.cpp的Start()起两个专门用于输入和输出的线程。 + +### 相关权限 + +- 允许应用读取图片和视频: ohos.permission.MEDIA_LOCATION; +- 允许应用访问文件: ohos.permission.READ_MEDIA, ohos.permission.WRITE_MEDIA; +- 允许应用使用相机: ohos.permission.CAMERA。 + +### 依赖 + +- 不涉及 + +### 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:华为手机; + +2. HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上; + +3. DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上; + +4. HarmonyOS SDK版本:HarmonyOS NEXT Developer Bata1 SDK及以上。 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..01ef137f920b7763b51f7b10e0b7c752792fed62 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 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.0.0(12)", + "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..03fc13dcb36ec64b78b3334e1a6a45c4077145e7 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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": "", + } + }, + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ 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..304cb2a400aebd7ba0485a3b235baa19149ca48e --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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": { + "libplayer.so": "file:./src/main/cpp/types/libplayer", + "librecorder.so": "file:./src/main/cpp/types/librecorder" + } +} \ 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..e498f02e07801619a12e1182c24ebf6615cf55bd --- /dev/null +++ b/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,42 @@ +# 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}/render/include + ${NATIVERENDER_ROOT_PATH}/sample/player + ${NATIVERENDER_ROOT_PATH}/sample/recorder +) + +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(player SHARED sample/player/PlayerNative.cpp + sample/player/Player.cpp + capbilities/Demuxer.cpp + capbilities/VideoDecoder.cpp + capbilities/AudioDecoder.cpp + render/EglCore.cpp + render/PluginRender.cpp + render/PluginManager.cpp + common/SampleCallback.cpp +) + +add_library(recorder SHARED sample/recorder/RecorderNative.cpp + sample/recorder/Recorder.cpp + capbilities/Muxer.cpp + capbilities/VideoEncoder.cpp + common/SampleCallback.cpp +) + +target_link_libraries(player PUBLIC ${BASE_LIBRARY}) +target_link_libraries(recorder PUBLIC ${BASE_LIBRARY}) \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/AudioDecoder.cpp b/entry/src/main/cpp/capbilities/AudioDecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d429e7d912ce9ad5f1b38ea4d590e006d9b70f8 --- /dev/null +++ b/entry/src/main/cpp/capbilities/AudioDecoder.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 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 "AudioDecoder.h" + +#undef LOG_TAG +#define LOG_TAG "AudioDecoder" + +AudioDecoder::~AudioDecoder() { Release(); } + +int32_t AudioDecoder::Create(const std::string &codecMime) { + decoder_ = OH_AudioCodec_CreateByMime(codecMime.c_str(), false); + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t AudioDecoder::SetCallback(CodecUserData *codecUserData) { + int32_t ret = AV_ERR_OK; + ret = OH_AudioCodec_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 AudioDecoder::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_AUDIO_SAMPLE_FORMAT, SAMPLE_S16LE); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, sampleInfo.audioChannelCount); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, sampleInfo.audioSampleRate); + OH_AVFormat_SetLongValue(format, OH_MD_KEY_CHANNEL_LAYOUT, sampleInfo.audioChannelLayout); + AVCODEC_SAMPLE_LOGI("====== AudioDecoder config ======"); + + int ret = OH_AudioCodec_Configure(decoder_, format); + AVCODEC_SAMPLE_LOGI("====== AudioDecoder config ======"); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Config failed, ret: %{public}d", ret); + OH_AVFormat_Destroy(format); + format = nullptr; + + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t AudioDecoder::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 audio decoder + int32_t ret = Configure(sampleInfo); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Configure failed"); + + // SetCallback for audio 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 audio decoder + { + int ret = OH_AudioCodec_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; +} + +int32_t AudioDecoder::Start() { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + + int ret = OH_AudioCodec_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 AudioDecoder::PushInputBuffer(CodecBufferInfo &info) { + CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null"); + int32_t ret = OH_AVBuffer_SetBufferAttr(reinterpret_cast(info.buffer), &info.attr); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Set avbuffer attr failed"); + ret = OH_AudioCodec_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 AudioDecoder::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; + ret = OH_AudioCodec_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; +} + +int32_t AudioDecoder::Release() { + if (decoder_ != nullptr) { + OH_AudioCodec_Flush(decoder_); + OH_AudioCodec_Stop(decoder_); + OH_AudioCodec_Destroy(decoder_); + decoder_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} \ 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..2b5501e4749a1bf6ec7230f45db946b6dbf52ae9 --- /dev/null +++ b/entry/src/main/cpp/capbilities/Demuxer.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 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(); } + +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; +} + +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; +} + +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)); + info.audioCodecMime = audioCodecMime; + audioTrackId_ = index; + + AVCODEC_SAMPLE_LOGI("====== Demuxer Audio config ======"); + AVCODEC_SAMPLE_LOGI("Mime: %{public}s", audioCodecMime); + AVCODEC_SAMPLE_LOGI("audioMime:%{public}s sampleForamt:%{public}d " + "sampleRate:%{public}d channelCount:%{public}d channelLayout:%{public}d", + info.audioCodecMime.c_str(), info.audioSampleForamt, info.audioSampleRate, + info.audioChannelCount, (int)info.audioChannelLayout); + AVCODEC_SAMPLE_LOGI("====== Demuxer Audio config ======"); + } + } + + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Demuxer::GetVideoTrackId() { return videoTrackId_; } +int32_t Demuxer::GetAudioTrackId() { return audioTrackId_; } \ 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..59eb4abb4a65167339907c0edec07c8967443333 --- /dev/null +++ b/entry/src/main/cpp/capbilities/Muxer.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 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" + +#undef LOG_TAG +#define LOG_TAG "Muxer" + +namespace { +constexpr int32_t VERTICAL_ANGLE = 90; +constexpr int32_t HORIZONTAL_ANGLE = 0; +} + +Muxer::~Muxer() +{ + Release(); +} + +int32_t Muxer::Create(int32_t fd) +{ + muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_MPEG_4); + CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer create failed, fd: %{public}d", fd); + return AVCODEC_SAMPLE_ERR_OK; +} + +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.videoCodecMime.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.frameRate); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, sampleInfo.videoWidth); + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight); + OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, sampleInfo.videoCodecMime.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); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "AddTrack failed"); + OH_AVFormat_Destroy(formatVideo); + OH_AVMuxer_SetRotation(muxer_, sampleInfo.videoHeight > sampleInfo.videoWidth ? VERTICAL_ANGLE : HORIZONTAL_ANGLE); + return AVCODEC_SAMPLE_ERR_OK; +} + +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; +} + +int32_t Muxer::WriteSample(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr) +{ + 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_, videoTrackId_, buffer); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Write sample failed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Muxer::Release() +{ + if (muxer_ != nullptr) { + OH_AVMuxer_Destroy(muxer_); + muxer_ = nullptr; + } + return AVCODEC_SAMPLE_ERR_OK; +} \ 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..879b372758043ae58e378919110b3286857f8234 --- /dev/null +++ b/entry/src/main/cpp/capbilities/VideoDecoder.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 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; +constexpr int ROTATION_ANGLE = 90; +} // namespace + +VideoDecoder::~VideoDecoder() +{ + Release(); +} + +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; +} + +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); + if (sampleInfo.videoHeight > sampleInfo.videoWidth) { + OH_AVFormat_SetIntValue(format, OH_MD_KEY_ROTATION, sampleInfo.rotation + ROTATION_ANGLE); + } + + 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); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Config failed, ret: %{public}d", ret); + OH_AVFormat_Destroy(format); + format = nullptr; + + 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"); + + // SetSurface from video decoder + if (sampleInfo.window != nullptr) { + int ret = OH_VideoDecoder_SetSurface(decoder_, sampleInfo.window); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK && sampleInfo.window, AVCODEC_SAMPLE_ERR_ERROR, + "Set surface failed, ret: %{public}d", ret); + } + + // 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 + { + int 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; +} + +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; +} + +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..ac2c5560443859811fa7b34a74d93c435399c3b8 --- /dev/null +++ b/entry/src/main/cpp/capbilities/VideoEncoder.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2024 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" + +namespace { + +int32_t ToGraphicPixelFormat(int32_t avPixelFormat, bool isHDRVivid) +{ + if (isHDRVivid) { + return NATIVEBUFFER_PIXEL_FMT_YCBCR_P010; + } + switch (avPixelFormat) { + case AV_PIXEL_FORMAT_RGBA: + return NATIVEBUFFER_PIXEL_FMT_RGBA_8888; + case AV_PIXEL_FORMAT_YUVI420: + return NATIVEBUFFER_PIXEL_FMT_YCBCR_420_P; + case AV_PIXEL_FORMAT_NV21: + return NATIVEBUFFER_PIXEL_FMT_YCRCB_420_SP; + default: // NV12 and others + return NATIVEBUFFER_PIXEL_FMT_YCRCB_420_SP; + } +} +} // namespace + +VideoEncoder::~VideoEncoder() +{ + Release(); +} + +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; +} + +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"); + + // GetSurface from video encoder + ret = GetSurface(sampleInfo); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Get surface 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; +} + +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; +} + +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::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 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.frameRate); + 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.bitrate); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, sampleInfo.hevcProfile); + 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 ======"); + + int ret = OH_VideoEncoder_Configure(encoder_, format); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Config failed, ret: %{public}d", ret); + OH_AVFormat_Destroy(format); + format = nullptr; + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t VideoEncoder::GetSurface(SampleInfo &sampleInfo) +{ + int32_t ret = OH_VideoEncoder_GetSurface(encoder_, &sampleInfo.window); + CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK && sampleInfo.window, AVCODEC_SAMPLE_ERR_ERROR, + "Get surface failed, ret: %{public}d", ret); + (void)OH_NativeWindow_NativeWindowHandleOpt(sampleInfo.window, SET_BUFFER_GEOMETRY, sampleInfo.videoWidth, + sampleInfo.videoHeight); + (void)OH_NativeWindow_NativeWindowHandleOpt(sampleInfo.window, SET_USAGE, 16425); // 16425: Window usage + (void)OH_NativeWindow_NativeWindowHandleOpt(sampleInfo.window, SET_FORMAT, + ToGraphicPixelFormat(sampleInfo.pixelFormat, sampleInfo.isHDRVivid)); + return AVCODEC_SAMPLE_ERR_OK; +} \ No newline at end of file diff --git a/entry/src/main/cpp/capbilities/include/AudioDecoder.h b/entry/src/main/cpp/capbilities/include/AudioDecoder.h new file mode 100644 index 0000000000000000000000000000000000000000..6d89f32655b1b9b0b1644dd0c83a611d685daf82 --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/AudioDecoder.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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 AUDIODECODER_H +#define AUDIODECODER_H + +#include "multimedia/player_framework/native_avcodec_audiocodec.h" +#include "multimedia/player_framework/native_avbuffer_info.h" +#include "SampleCallback.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class AudioDecoder { +public: + AudioDecoder() = default; + ~AudioDecoder(); + + int32_t Create(const std::string &codecMime); + int32_t Config(const SampleInfo &sampleInfo, CodecUserData *codecUserData); + int32_t Start(); + int32_t PushInputBuffer(CodecBufferInfo &info); + int32_t FreeOutputBuffer(uint32_t bufferIndex, bool render); + int32_t Release(); + +private: + int32_t SetCallback(CodecUserData *codecUserData); + int32_t Configure(const SampleInfo &sampleInfo); + + bool isAVBufferMode_ = false; + OH_AVCodec *decoder_; +}; +#endif // AUDIODECODER_H \ 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..e5d45b06f3706fd7643102a41cd963f7c58d6c8c --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/Demuxer.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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(); + int32_t GetAudioTrackId(); + +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..8354a9cdbfcd730cabcb03a96c18a9e12f12949a --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/Muxer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 +#include "multimedia/player_framework/native_avmuxer.h" +#include "SampleInfo.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class Muxer { +public: + Muxer() = default; + ~Muxer(); + + int32_t Create(int32_t fd); + int32_t Config(SampleInfo &sampleInfo); + int32_t Start(); + int32_t WriteSample(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr); + int32_t Release(); + +private: + OH_AVMuxer *muxer_ = nullptr; + int32_t videoTrackId_ = -1; +}; + +#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..295592764eb2a7d5029609df92ae70acdfad1149 --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/VideoDecoder.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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..6941a1a909e89f1cc08ae857ae9b8b0195b2f0a8 --- /dev/null +++ b/entry/src/main/cpp/capbilities/include/VideoEncoder.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 NotifyEndOfStream(); + int32_t Stop(); + int32_t Release(); + +private: + int32_t SetCallback(CodecUserData *codecUserData); + int32_t Configure(const SampleInfo &sampleInfo); + int32_t GetSurface(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..a47b6ae623d15ff44d869323391b6788470b2cff --- /dev/null +++ b/entry/src/main/cpp/common/SampleCallback.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024 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" + +namespace { +constexpr int LIMIT_LOGD_FREQUENCY = 50; +} + +// Custom write data function +int32_t SampleCallback::OnRenderWriteData(OH_AudioRenderer *renderer, void *userData, void *buffer, int32_t length) { + (void)renderer; + (void)length; + CodecUserData *codecUserData = static_cast(userData); + + // Write the data to be played to the buffer by length + uint8_t *dest = (uint8_t *)buffer; + size_t index = 0; + std::unique_lock lock(codecUserData->outputMutex); + // Retrieve the length of the data to be played from the queue + while (!codecUserData->renderQueue.empty() && index < length) { + dest[index++] = codecUserData->renderQueue.front(); + codecUserData->renderQueue.pop(); + } + AVCODEC_SAMPLE_LOGD("render BufferLength:%{public}d Out buffer count: %{public}u, renderQueue.size: %{public}u " + "renderReadSize: %{public}u", + length, codecUserData->outputFrameCount, (unsigned int)codecUserData->renderQueue.size(), + (unsigned int)index); + if (codecUserData->renderQueue.size() < length) { + codecUserData->renderCond.notify_all(); + } + return 0; +} +// Customize the audio stream event function +int32_t SampleCallback::OnRenderStreamEvent(OH_AudioRenderer *renderer, void *userData, OH_AudioStream_Event event) { + (void)renderer; + (void)userData; + (void)event; + // Update the player status and interface based on the audio stream event information represented by the event + return 0; +} +// Customize the audio interrupt event function +int32_t SampleCallback::OnRenderInterruptEvent(OH_AudioRenderer *renderer, void *userData, + OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) { + (void)renderer; + (void)userData; + (void)type; + (void)hint; + // Update the player status and interface based on the audio interrupt information indicated by type and hint + return 0; +} +// Custom exception callback functions +int32_t SampleCallback::OnRenderError(OH_AudioRenderer *renderer, void *userData, OH_AudioStream_Result error) { + (void)renderer; + (void)userData; + (void)error; + AVCODEC_SAMPLE_LOGE("OnRenderError"); + // Handle the audio exception information based on the error message + return 0; +} + +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(); +} + +void SampleCallback::OnNewOutputBuffer(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..351c3a7084233825097b7c31784353e6d40fb73a --- /dev/null +++ b/entry/src/main/cpp/common/SampleCallback.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 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 +#include +#include "SampleInfo.h" + +class SampleCallback { +public: + static int32_t OnRenderWriteData(OH_AudioRenderer *renderer, void *userData, void *buffer, int32_t length); + static int32_t OnRenderStreamEvent(OH_AudioRenderer *renderer, void *userData, OH_AudioStream_Event event); + static int32_t OnRenderInterruptEvent(OH_AudioRenderer *renderer, void *userData, OH_AudioInterrupt_ForceType type, + OH_AudioInterrupt_Hint hint); + static int32_t OnRenderError(OH_AudioRenderer *renderer, void *userData, OH_AudioStream_Result error); + + 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); +}; + +#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..4c2639a86d87625047487cb52364fed6ad3f5313 --- /dev/null +++ b/entry/src/main/cpp/common/SampleInfo.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024 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 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 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; +}; + +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::mutex renderMutex; + std::condition_variable renderCond; + std::queue outputBufferInfoQueue; + + std::queue renderQueue; +}; + +#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..59f35de8137e629727beba5245a2b9f5187f0e8e --- /dev/null +++ b/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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..05be70f3ee61df5e5721be800a48c043c5316d5f --- /dev/null +++ b/entry/src/main/cpp/common/dfx/log/AVCodecSampleLog.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 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/render/EglCore.cpp b/entry/src/main/cpp/render/EglCore.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2f4cfa0b1eb02d9497f342bae3d6cb5f10b526ef --- /dev/null +++ b/entry/src/main/cpp/render/EglCore.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2024 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 "EglCore.h" +#include +#include +#include +#include +#include +#include +#include "PluginRender.h" + +#undef LOG_TAG +#define LOG_TAG "EGLCORE" + +namespace NativeXComponentSample { +namespace { +constexpr uint32_t LOG_PRINT_DOMAIN = 0xFF00; +constexpr int32_t NUM_4 = 4; +/** + * Vertex shader. + */ +const char VERTEX_SHADER[] = "#version 300 es\n" + "layout(location = 0) in vec4 a_position;\n" + "layout(location = 1) in vec4 a_color; \n" + "out vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_Position = a_position; \n" + " v_color = a_color; \n" + "} \n"; + +/** + * Fragment shader. + */ +const char FRAGMENT_SHADER[] = "#version 300 es\n" + "precision mediump float; \n" + "in vec4 v_color; \n" + "out vec4 fragColor; \n" + "void main() \n" + "{ \n" + " fragColor = v_color; \n" + "} \n"; + +/** + * Background color #f4f4f4. + */ +const GLfloat BACKGROUND_COLOR[] = {244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f}; + +/** + * Draw color #7E8FFB. + */ +const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f}; + +/** + * Change color #92D6CC. + */ +const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f}; + +/** + * Background area. + */ +const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f}; + +/** + * Get context parameter count. + */ +const size_t GET_CONTEXT_PARAM_CNT = 1; + +/** + * Fifty percent. + */ +const float FIFTY_PERCENT = 0.5; + +/** + * Pointer size. + */ +const GLint POINTER_SIZE = 2; + +/** + * Triangle fan size. + */ +const GLsizei TRIANGLE_FAN_SIZE = 4; + +/** + * Egl red size default. + */ +const int EGL_RED_SIZE_DEFAULT = 8; + +/** + * Egl green size default. + */ +const int EGL_GREEN_SIZE_DEFAULT = 8; + +/** + * Egl blue size default. + */ +const int EGL_BLUE_SIZE_DEFAULT = 8; + +/** + * Egl alpha size default. + */ +const int EGL_ALPHA_SIZE_DEFAULT = 8; + +/** + * Default x position. + */ +const int DEFAULT_X_POSITION = 0; + +/** + * Default y position. + */ +const int DEFAULT_Y_POSITION = 0; + +/** + * Gl red default. + */ +const GLfloat GL_RED_DEFAULT = 0.0; + +/** + * Gl green default. + */ +const GLfloat GL_GREEN_DEFAULT = 0.0; + +/** + * Gl blue default. + */ +const GLfloat GL_BLUE_DEFAULT = 0.0; + +/** + * Gl alpha default. + */ +const GLfloat GL_ALPHA_DEFAULT = 1.0; + +/** + * Program error. + */ +const GLuint PROGRAM_ERROR = 0; + +/** + * Shape vertices size. + */ +const int SHAPE_VERTICES_SIZE = 8; + +/** + * Position handle name. + */ +const char POSITION_NAME[] = "a_position"; + +/** + * Position error. + */ +const GLint POSITION_ERROR = -1; + +/** + * Config attribute list. + */ +const EGLint ATTRIB_LIST[] = { + // Key,value. + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, EGL_RED_SIZE_DEFAULT, EGL_GREEN_SIZE, EGL_GREEN_SIZE_DEFAULT, + EGL_BLUE_SIZE, EGL_BLUE_SIZE_DEFAULT, EGL_ALPHA_SIZE, EGL_ALPHA_SIZE_DEFAULT, EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + // End. + EGL_NONE}; + +/** + * Context attributes. + */ +const EGLint CONTEXT_ATTRIBS[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; +} // namespace +bool EGLCore::EglContextInit(void *window, int width, int height) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "EglContextInit execute"); + if ((window == nullptr) || (width <= 0) || (height <= 0)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "EglContextInit: param error"); + return false; + } + + UpdateSize(width, height); + eglWindow_ = reinterpret_cast(window); + + // Init display. + eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (eglDisplay_ == EGL_NO_DISPLAY) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglGetDisplay: unable to get EGL display"); + return false; + } + + EGLint majorVersion; + EGLint minorVersion; + if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", + "eglInitialize: unable to get initialize EGL display"); + return false; + } + + // Select configuration. + const EGLint maxConfigSize = 1; + EGLint numConfigs; + if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglChooseConfig: unable to choose configs"); + return false; + } + + return CreateEnvironment(); +} + +bool EGLCore::CreateEnvironment() { + // Create surface. + if (!eglWindow_) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglWindow_ is null"); + return false; + } + eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL); + if (eglSurface_ == nullptr) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", + "eglCreateWindowSurface: unable to create surface"); + return false; + } + // Create context. + eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS); + if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglMakeCurrent failed"); + return false; + } + // Create program. + program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (program_ == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "CreateProgram: unable to create program"); + return false; + } + return true; +} + +GLuint EGLCore::LoadShader(GLenum type, const char *shaderSrc) { + if ((type <= 0) || (shaderSrc == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCreateShader type or shaderSrc error"); + return PROGRAM_ERROR; + } + + GLuint shader = glCreateShader(type); + if (shader == 0) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCreateShader unable to load shader"); + return PROGRAM_ERROR; + } + + // The gl function has no return value. + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled != 0) { + return shader; + } + + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen <= 1) { + glDeleteShader(shader); + return PROGRAM_ERROR; + } + + char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); + if (infoLog != nullptr) { + memset(infoLog, 0, infoLen + 1); + glGetShaderInfoLog(shader, infoLen, nullptr, infoLog); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCompileShader error = %s", infoLog); + free(infoLog); + infoLog = nullptr; + } + glDeleteShader(shader); + return PROGRAM_ERROR; +} + +GLuint EGLCore::CreateProgram(const char *vertexShader, const char *fragShader) { + if ((vertexShader == nullptr) || (fragShader == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", + "createProgram: vertexShader or fragShader is null"); + return PROGRAM_ERROR; + } + + GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader); + if (vertex == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram vertex error"); + return PROGRAM_ERROR; + } + + GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader); + if (fragment == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram fragment error"); + return PROGRAM_ERROR; + } + + GLuint program = glCreateProgram(); + if (program == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram program error"); + glDeleteShader(vertex); + glDeleteShader(fragment); + return PROGRAM_ERROR; + } + + // The gl function has no return value. + glAttachShader(program, vertex); + glAttachShader(program, fragment); + glLinkProgram(program); + + GLint linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (linked != 0) { + glDeleteShader(vertex); + glDeleteShader(fragment); + return program; + } + + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram linked error"); + GLint infoLen = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen > 1) { + char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); + memset(infoLog, 0, infoLen + 1); + glGetProgramInfoLog(program, infoLen, nullptr, infoLog); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glLinkProgram error = %s", infoLog); + free(infoLog); + infoLog = nullptr; + } + glDeleteShader(vertex); + glDeleteShader(fragment); + glDeleteProgram(program); + return PROGRAM_ERROR; +} + +void EGLCore::UpdateSize(int width, int height) { + width_ = width; + height_ = height; + if (width_ > 0) { + widthPercent_ = FIFTY_PERCENT * height_ / width_; + } +} + +void EGLCore::Release() { + if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroySurface failed"); + } + + if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroyContext failed"); + } + + if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglTerminate failed"); + } +} +} // namespace NativeXComponentSample \ No newline at end of file diff --git a/entry/src/main/cpp/render/PluginManager.cpp b/entry/src/main/cpp/render/PluginManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3649775d4caed9d50170e0eefd95a4f68f2519c7 --- /dev/null +++ b/entry/src/main/cpp/render/PluginManager.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 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 "PluginManager.h" + +#include +#include +#include +#include +#include + +#undef LOG_TAG +#define LOG_TAG "PLUGINMANAGER" + +namespace NativeXComponentSample { +constexpr uint32_t LOG_PRINT_DOMAIN = 0xFF00; +PluginManager PluginManager::pluginManager_; + +PluginManager::~PluginManager() { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "~PluginManager"); + for (auto iter = nativeXComponentMap_.begin(); iter != nativeXComponentMap_.end(); ++iter) { + if (iter->second != nullptr) { + delete iter->second; + iter->second = nullptr; + } + } + nativeXComponentMap_.clear(); + + for (auto iter = pluginRenderMap_.begin(); iter != pluginRenderMap_.end(); ++iter) { + if (iter->second != nullptr) { + delete iter->second; + iter->second = nullptr; + } + } + pluginRenderMap_.clear(); +} + +void PluginManager::Export(napi_env env, napi_value exports) { + if ((env == nullptr) || (exports == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: env or exports is null"); + return; + } + + napi_value exportInstance = nullptr; + if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: napi_get_named_property fail"); + return; + } + + OH_NativeXComponent *nativeXComponent = nullptr; + if (napi_unwrap(env, exportInstance, reinterpret_cast(&nativeXComponent)) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: napi_unwrap fail"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", + "Export: OH_NativeXComponent_GetXComponentId fail"); + return; + } + + std::string id(idStr); + auto context = PluginManager::GetInstance(); + if ((context != nullptr) && (nativeXComponent != nullptr)) { + context->SetNativeXComponent(id, nativeXComponent); + auto render = context->GetRender(id); + if (render != nullptr) { + render->RegisterCallback(nativeXComponent); + render->Export(env, exports); + } + } +} + +void PluginManager::SetNativeXComponent(std::string &id, OH_NativeXComponent *nativeXComponent) { + if (nativeXComponent == nullptr) { + return; + } + + if (nativeXComponentMap_.find(id) == nativeXComponentMap_.end()) { + nativeXComponentMap_[id] = nativeXComponent; + return; + } + + if (nativeXComponentMap_[id] != nativeXComponent) { + OH_NativeXComponent *tmp = nativeXComponentMap_[id]; + delete tmp; + tmp = nullptr; + nativeXComponentMap_[id] = nativeXComponent; + } +} + +PluginRender *PluginManager::GetRender(std::string &id) { + if (pluginRenderMap_.find(id) == pluginRenderMap_.end()) { + PluginRender *instance = PluginRender::GetInstance(id); + pluginRenderMap_[id] = instance; + return instance; + } + + return pluginRenderMap_[id]; +} +} // namespace NativeXComponentSample \ No newline at end of file diff --git a/entry/src/main/cpp/render/PluginRender.cpp b/entry/src/main/cpp/render/PluginRender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6d8a4799e205c131cde944fad39978f66ba7b659 --- /dev/null +++ b/entry/src/main/cpp/render/PluginRender.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "PluginManager.h" +#include "PluginRender.h" + +#undef LOG_TAG +#define LOG_TAG "PLUGINRENDER" + +namespace NativeXComponentSample { +namespace { +constexpr uint32_t LOG_PRINT_DOMAIN = 0xFF00; + +void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceCreatedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceCreatedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceCreatedCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + auto render = PluginRender::GetInstance(id); + uint64_t width; + uint64_t height; + int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height); + if ((xSize == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) && (render != nullptr)) { + if (render->eglCore_->EglContextInit(window, width, height)) { + auto context = PluginManager::GetInstance(); + context->pluginWindow_ = (OHNativeWindow *)window; + } + } +} + +void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChangedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceChangedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceChangedCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + auto render = PluginRender::GetInstance(id); + if (render != nullptr) { + render->OnSurfaceChanged(component, window); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "surface changed"); + } +} + +void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceDestroyedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceDestroyedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "OnSurfaceDestroyedCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + PluginRender::Release(id); +} + +void DispatchTouchEventCB(OH_NativeXComponent *component, void *window) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "DispatchTouchEventCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "DispatchTouchEventCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + PluginRender *render = PluginRender::GetInstance(id); + if (render != nullptr) { + render->OnTouchEvent(component, window); + } +} +} // namespace + +std::unordered_map PluginRender::instance_; +int32_t PluginRender::hasDraw_ = 0; +int32_t PluginRender::hasChangeColor_ = 0; + +PluginRender::PluginRender(std::string &id) { + this->id_ = id; + this->eglCore_ = new EGLCore(); +} + +PluginRender *PluginRender::GetInstance(std::string &id) { + if (instance_.find(id) == instance_.end()) { + PluginRender *instance = new PluginRender(id); + instance_[id] = instance; + return instance; + } else { + return instance_[id]; + } +} + +void PluginRender::Export(napi_env env, napi_value exports) { + if ((env == nullptr) || (exports == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: env or exports is null"); + return; + } + + napi_property_descriptor desc[] = {}; + if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: napi_define_properties failed"); + } +} + +void PluginRender::Release(std::string &id) { + PluginRender *render = PluginRender::GetInstance(id); + if (render != nullptr) { + render->eglCore_->Release(); + delete render->eglCore_; + render->eglCore_ = nullptr; + delete render; + render = nullptr; + instance_.erase(instance_.find(id)); + } +} + +void PluginRender::OnSurfaceChanged(OH_NativeXComponent *component, void *window) { + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChanged: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + PluginRender *render = PluginRender::GetInstance(id); + double offsetX; + double offsetY; + OH_NativeXComponent_GetXComponentOffset(component, window, &offsetX, &offsetY); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OH_NativeXComponent_GetXComponentOffset", + "offsetX = %{public}lf, offsetY = %{public}lf", offsetX, offsetY); + uint64_t width; + uint64_t height; + OH_NativeXComponent_GetXComponentSize(component, window, &width, &height); + if (render != nullptr) { + render->eglCore_->UpdateSize(width, height); + } +} + +void PluginRender::OnTouchEvent(OH_NativeXComponent *component, void *window) { + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", + "DispatchTouchEventCB: Unable to get XComponent id"); + return; + } + OH_NativeXComponent_TouchEvent touchEvent; + OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent); + float tiltX = 0.0f; + float tiltY = 0.0f; + OH_NativeXComponent_TouchPointToolType toolType = + OH_NativeXComponent_TouchPointToolType::OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN; + OH_NativeXComponent_GetTouchPointToolType(component, 0, &toolType); + OH_NativeXComponent_GetTouchPointTiltX(component, 0, &tiltX); + OH_NativeXComponent_GetTouchPointTiltY(component, 0, &tiltY); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OnTouchEvent", + "touch info: toolType = %{public}d, tiltX = %{public}lf, tiltY = %{public}lf", toolType, tiltX, tiltY); +} + +void PluginRender::RegisterCallback(OH_NativeXComponent *nativeXComponent) { + renderCallback_.OnSurfaceCreated = OnSurfaceCreatedCB; + renderCallback_.OnSurfaceChanged = OnSurfaceChangedCB; + renderCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB; + renderCallback_.DispatchTouchEvent = DispatchTouchEventCB; + OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_); +} +} // namespace NativeXComponentSample \ No newline at end of file diff --git a/entry/src/main/cpp/render/include/EglCore.h b/entry/src/main/cpp/render/include/EglCore.h new file mode 100644 index 0000000000000000000000000000000000000000..fb7fcbb97d07a7d6b999adba3c7225988c6afb5d --- /dev/null +++ b/entry/src/main/cpp/render/include/EglCore.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 NATIVE_XCOMPONENT_EGL_CORE_H +#define NATIVE_XCOMPONENT_EGL_CORE_H + +#include +#include +#include + +namespace NativeXComponentSample { +class EGLCore { +public: + explicit EGLCore(){}; + ~EGLCore() {} + bool EglContextInit(void *window, int width, int height); + bool CreateEnvironment(); + void Release(); + void UpdateSize(int width, int height); + +private: + GLuint LoadShader(GLenum type, const char *shaderSrc); + GLuint CreateProgram(const char *vertexShader, const char *fragShader); + +private: + EGLNativeWindowType eglWindow_; + EGLDisplay eglDisplay_ = EGL_NO_DISPLAY; + EGLConfig eglConfig_ = EGL_NO_CONFIG_KHR; + EGLSurface eglSurface_ = EGL_NO_SURFACE; + EGLContext eglContext_ = EGL_NO_CONTEXT; + GLuint program_; + bool flag_ = false; + int width_; + int height_; + GLfloat widthPercent_; +}; +} // namespace NativeXComponentSample +#endif // NATIVE_XCOMPONENT_EGL_CORE_H \ No newline at end of file diff --git a/entry/src/main/cpp/render/include/PluginManager.h b/entry/src/main/cpp/render/include/PluginManager.h new file mode 100644 index 0000000000000000000000000000000000000000..d9dfc992485f1572de3a8c6a89dff0c15b7ef340 --- /dev/null +++ b/entry/src/main/cpp/render/include/PluginManager.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 NATIVE_XCOMPONENT_PLUGIN_MANAGER_H +#define NATIVE_XCOMPONENT_PLUGIN_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include "native_window/external_window.h" + +#include "PluginRender.h" + +namespace NativeXComponentSample { +class PluginManager { +public: + ~PluginManager(); + + static PluginManager *GetInstance() { return &PluginManager::pluginManager_; } + + void SetNativeXComponent(std::string &id, OH_NativeXComponent *nativeXComponent); + PluginRender *GetRender(std::string &id); + void Export(napi_env env, napi_value exports); + OHNativeWindow *pluginWindow_; + +private: + static PluginManager pluginManager_; + + std::unordered_map nativeXComponentMap_; + std::unordered_map pluginRenderMap_; +}; +} // namespace NativeXComponentSample +#endif // NATIVE_XCOMPONENT_PLUGIN_MANAGER_H \ No newline at end of file diff --git a/entry/src/main/cpp/render/include/PluginRender.h b/entry/src/main/cpp/render/include/PluginRender.h new file mode 100644 index 0000000000000000000000000000000000000000..5e03083fa224561fd29f173e1d4e38dd1b027cf0 --- /dev/null +++ b/entry/src/main/cpp/render/include/PluginRender.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 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 NATIVE_XCOMPONENT_PLUGIN_RENDER_H +#define NATIVE_XCOMPONENT_PLUGIN_RENDER_H + +#include +#include +#include +#include + +#include "EglCore.h" + +namespace NativeXComponentSample { +class PluginRender { +public: + explicit PluginRender(std::string &id); + ~PluginRender() { + if (eglCore_ != nullptr) { + eglCore_->Release(); + delete eglCore_; + eglCore_ = nullptr; + } + } + static PluginRender *GetInstance(std::string &id); + static void Release(std::string &id); + void Export(napi_env env, napi_value exports); + void OnSurfaceChanged(OH_NativeXComponent *component, void *window); + void OnTouchEvent(OH_NativeXComponent *component, void *window); + void RegisterCallback(OH_NativeXComponent *nativeXComponent); + +public: + static std::unordered_map instance_; + EGLCore *eglCore_; + std::string id_; + static int32_t hasDraw_; + static int32_t hasChangeColor_; + +private: + OH_NativeXComponent_Callback renderCallback_; + OH_NativeXComponent_MouseEvent_Callback mouseCallback_; +}; +} // namespace NativeXComponentSample +#endif // NATIVE_XCOMPONENT_PLUGIN_RENDER_H \ No newline at end of file diff --git a/entry/src/main/cpp/sample/player/Player.cpp b/entry/src/main/cpp/sample/player/Player.cpp new file mode 100644 index 0000000000000000000000000000000000000000..46d3c3dd69c191653c7f3308f0a4611da20d8a59 --- /dev/null +++ b/entry/src/main/cpp/sample/player/Player.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2024 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 "Player.h" +#include +#include "AVCodecSampleLog.h" +#include "dfx/error/AVCodecSampleError.h" + +#undef LOG_TAG +#define LOG_TAG "player" + +namespace { +constexpr int BALANCE_VALUE = 5; +using namespace std::chrono_literals; +} // namespace + +Player::~Player() { Player::StartRelease(); } + +int32_t Player::CreateAudioDecoder() { + AVCODEC_SAMPLE_LOGW("audio mime:%{public}s", sampleInfo_.audioCodecMime.c_str()); + int32_t ret = audioDecoder_->Create(sampleInfo_.audioCodecMime); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + AVCODEC_SAMPLE_LOGE("Create audio decoder failed, mime:%{public}s", sampleInfo_.audioCodecMime.c_str()); + } else { + audioDecContext_ = new CodecUserData; + ret = audioDecoder_->Config(sampleInfo_, audioDecContext_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Audio Decoder config failed"); + OH_AudioStreamBuilder_Create(&builder_, AUDIOSTREAM_TYPE_RENDERER); + OH_AudioStreamBuilder_SetLatencyMode(builder_, AUDIOSTREAM_LATENCY_MODE_NORMAL); + // Set the audio sample rate + OH_AudioStreamBuilder_SetSamplingRate(builder_, sampleInfo_.audioSampleRate); + // Set the audio channel + OH_AudioStreamBuilder_SetChannelCount(builder_, sampleInfo_.audioChannelCount); + // Set the audio sample format + OH_AudioStreamBuilder_SetSampleFormat(builder_, AUDIOSTREAM_SAMPLE_S16LE); + // Sets the encoding type of the audio stream + OH_AudioStreamBuilder_SetEncodingType(builder_, AUDIOSTREAM_ENCODING_TYPE_RAW); + // Set the working scenario for the output audio stream + OH_AudioStreamBuilder_SetRendererInfo(builder_, AUDIOSTREAM_USAGE_MUSIC); + AVCODEC_SAMPLE_LOGW("Init audioSampleRate: %{public}d, ChannelCount: %{public}d", sampleInfo_.audioSampleRate, + sampleInfo_.audioChannelCount); + OH_AudioRenderer_Callbacks callbacks; + // Configure the callback function +#ifndef DEBUG_DECODE + callbacks.OH_AudioRenderer_OnWriteData = SampleCallback::OnRenderWriteData; +#else + callbacks.OH_AudioRenderer_OnWriteData = nullptr; +#endif + callbacks.OH_AudioRenderer_OnStreamEvent = SampleCallback::OnRenderStreamEvent; + callbacks.OH_AudioRenderer_OnInterruptEvent = SampleCallback::OnRenderInterruptEvent; + callbacks.OH_AudioRenderer_OnError = SampleCallback::OnRenderError; + // Set the callback for the output audio stream + OH_AudioStreamBuilder_SetRendererCallback(builder_, callbacks, audioDecContext_); + OH_AudioStreamBuilder_GenerateRenderer(builder_, &audioRenderer_); + } + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Player::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; + sampleInfo_.window = NativeXComponentSample::PluginManager::GetInstance()->pluginWindow_; + 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 Player::Init(SampleInfo &sampleInfo) { + std::lock_guard lock(mutex_); + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(demuxer_ == nullptr && videoDecoder_ == nullptr && audioDecoder_ == nullptr, + AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + + sampleInfo_ = sampleInfo; + + videoDecoder_ = std::make_unique(); + audioDecoder_ = std::make_unique(); + demuxer_ = std::make_unique(); + + int32_t ret = demuxer_->Create(sampleInfo_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create demuxer failed"); + + ret = CreateAudioDecoder(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create audio decoder failed"); + + ret = CreateVideoDecoder(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create video decoder failed"); + + isReleased_ = false; + AVCODEC_SAMPLE_LOGI("Succeed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Player::Start() { + std::lock_guard 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(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Video Decoder start failed"); + isStarted_ = true; + videoDecInputThread_ = std::make_unique(&Player::VideoDecInputThread, this); + videoDecOutputThread_ = std::make_unique(&Player::VideoDecOutputThread, this); + + if (videoDecInputThread_ == nullptr || videoDecOutputThread_ == nullptr) { + AVCODEC_SAMPLE_LOGE("Create thread failed"); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + } + if (audioDecContext_) { + ret = audioDecoder_->Start(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Audio Decoder start failed"); + isStarted_ = true; + audioDecInputThread_ = std::make_unique(&Player::AudioDecInputThread, this); + audioDecOutputThread_ = std::make_unique(&Player::AudioDecOutputThread, this); +#ifdef DEBUG_DECODE + // for debug The decoded data is written to the sandbox address, and the physical address is /data/app/el2/100/base/com.example.avcodecsample/haps/entry/files/ + audioOutputFile_.open("/data/storage/el2/base/haps/entry/files/audio_decode_out.pcm", + std::ios::out | std::ios::binary); +#endif + if (audioDecInputThread_ == nullptr || audioDecOutputThread_ == nullptr) { + AVCODEC_SAMPLE_LOGE("Create thread failed"); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + } + // Clear the queue + while (audioDecContext_ && !audioDecContext_->renderQueue.empty()) { + audioDecContext_->renderQueue.pop(); + } + if (audioRenderer_) { + OH_AudioRenderer_Start(audioRenderer_); + } + AVCODEC_SAMPLE_LOGI("Succeed"); + doneCond_.notify_all(); + return AVCODEC_SAMPLE_ERR_OK; +} + +void Player::StartRelease() { + if (audioRenderer_) { + OH_AudioRenderer_Stop(audioRenderer_); + } + if (!isReleased_) { + isReleased_ = true; + Release(); + } +} + +void Player::ReleaseThread() { + if (videoDecInputThread_ && videoDecInputThread_->joinable()) { + videoDecInputThread_->detach(); + videoDecInputThread_.reset(); + } + if (videoDecOutputThread_ && videoDecOutputThread_->joinable()) { + videoDecOutputThread_->detach(); + videoDecOutputThread_.reset(); + } + if (audioDecInputThread_ && audioDecInputThread_->joinable()) { + audioDecInputThread_->detach(); + audioDecInputThread_.reset(); + } + if (audioDecOutputThread_ && audioDecOutputThread_->joinable()) { + audioDecOutputThread_->detach(); + audioDecOutputThread_.reset(); + } +} + +void Player::Release() { + std::lock_guard lock(mutex_); + isStarted_ = false; + + // Clear the queue + while (audioDecContext_ && !audioDecContext_->renderQueue.empty()) { + audioDecContext_->renderQueue.pop(); + } + if (audioRenderer_ != nullptr) { + OH_AudioRenderer_Release(audioRenderer_); + audioRenderer_ = nullptr; + } +#ifdef DEBUG_DECODE + if (audioOutputFile_.is_open()) { + audioOutputFile_.close(); + } +#endif + ReleaseThread(); + + if (demuxer_ != nullptr) { + demuxer_->Release(); + demuxer_.reset(); + } + if (videoDecoder_ != nullptr) { + videoDecoder_->Release(); + videoDecoder_.reset(); + } + if (videoDecContext_ != nullptr) { + delete videoDecContext_; + videoDecContext_ = nullptr; + } + if (audioDecoder_ != nullptr) { + audioDecoder_->Release(); + audioDecoder_.reset(); + } + if (audioDecContext_ != nullptr) { + delete audioDecContext_; + audioDecContext_ = nullptr; + } + OH_AudioStreamBuilder_Destroy(builder_); + doneCond_.notify_all(); + // Trigger the callback + sampleInfo_.playDoneCallback(sampleInfo_.playDoneCallbackData); + AVCODEC_SAMPLE_LOGI("Succeed"); +} + +void Player::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), "Catch EOS, thread out"); + } +} + +void Player::VideoDecOutputThread() { + sampleInfo_.frameInterval = MICROSECOND / sampleInfo_.frameRate; + while (true) { + thread_local auto lastPushTime = std::chrono::system_clock::now(); + 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(); }); + 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); + + CodecBufferInfo bufferInfo = videoDecContext_->outputBufferInfoQueue.front(); + videoDecContext_->outputBufferInfoQueue.pop(); + CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out"); + 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(); + + int32_t ret = videoDecoder_->FreeOutputBuffer(bufferInfo.bufferIndex, true); + CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Decoder output thread out"); + + std::this_thread::sleep_until(lastPushTime + std::chrono::microseconds(sampleInfo_.frameInterval)); + lastPushTime = std::chrono::system_clock::now(); + } + StartRelease(); +} + +void Player::AudioDecInputThread() { + while (true) { + CHECK_AND_BREAK_LOG(isStarted_, "Decoder input thread out"); + std::unique_lock lock(audioDecContext_->inputMutex); + bool condRet = audioDecContext_->inputCond.wait_for( + lock, 5s, [this]() { return !isStarted_ || !audioDecContext_->inputBufferInfoQueue.empty(); }); + CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out"); + CHECK_AND_CONTINUE_LOG(!audioDecContext_->inputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = audioDecContext_->inputBufferInfoQueue.front(); + audioDecContext_->inputBufferInfoQueue.pop(); + audioDecContext_->inputFrameCount++; + lock.unlock(); + + demuxer_->ReadSample(demuxer_->GetAudioTrackId(), reinterpret_cast(bufferInfo.buffer), + bufferInfo.attr); + + int32_t ret = audioDecoder_->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), "Catch EOS, thread out"); + } +} + +void Player::AudioDecOutputThread() { + while (true) { + CHECK_AND_BREAK_LOG(isStarted_, "Decoder output thread out"); + std::unique_lock lock(audioDecContext_->outputMutex); + bool condRet = audioDecContext_->outputCond.wait_for( + lock, 5s, [this]() { return !isStarted_ || !audioDecContext_->outputBufferInfoQueue.empty(); }); + CHECK_AND_BREAK_LOG(isStarted_, "Decoder output thread out"); + CHECK_AND_CONTINUE_LOG(!audioDecContext_->outputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = audioDecContext_->outputBufferInfoQueue.front(); + audioDecContext_->outputBufferInfoQueue.pop(); + CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out"); + audioDecContext_->outputFrameCount++; + AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64, + audioDecContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags, + bufferInfo.attr.pts); + uint8_t *source = OH_AVBuffer_GetAddr(reinterpret_cast(bufferInfo.buffer)); + // Put the decoded PMC data into the queue + for (int i = 0; i < bufferInfo.attr.size; i++) { + audioDecContext_->renderQueue.push(*(source + i)); + } +#ifdef DEBUG_DECODE + if (audioOutputFile_.is_open()) { + audioOutputFile_.write( + (const char *)OH_AVBuffer_GetAddr(reinterpret_cast(bufferInfo.buffer)), + bufferInfo.attr.size); + } +#endif + lock.unlock(); + + int32_t ret = audioDecoder_->FreeOutputBuffer(bufferInfo.bufferIndex, true); + CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Decoder output thread out"); + + std::unique_lock lockRender(audioDecContext_->renderMutex); + audioDecContext_->renderCond.wait_for(lockRender, 20ms, [this, bufferInfo]() { + return audioDecContext_->renderQueue.size() < BALANCE_VALUE * bufferInfo.attr.size; + }); + } + AVCODEC_SAMPLE_LOGI("Out buffer end"); + StartRelease(); +} \ No newline at end of file diff --git a/entry/src/main/cpp/sample/player/Player.h b/entry/src/main/cpp/sample/player/Player.h new file mode 100644 index 0000000000000000000000000000000000000000..a45ee8d128d7e7875c0124232d4a29c8b3aac9ea --- /dev/null +++ b/entry/src/main/cpp/sample/player/Player.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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 "AudioDecoder.h" +#include "multimedia/player_framework/native_avbuffer.h" +#include "Demuxer.h" +#include "SampleInfo.h" +#include "PluginManager.h" + +class Player { +public: + Player(){}; + ~Player(); + + static Player &GetInstance() { + static Player player; + return player; + } + + int32_t Init(SampleInfo &sampleInfo); + int32_t Start(); + +private: + void VideoDecInputThread(); + void VideoDecOutputThread(); + void AudioDecInputThread(); + void AudioDecOutputThread(); + void Release(); + void StartRelease(); + void ReleaseThread(); + int32_t CreateAudioDecoder(); + int32_t CreateVideoDecoder(); + + std::unique_ptr videoDecoder_ = nullptr; + std::shared_ptr audioDecoder_ = nullptr; + std::unique_ptr demuxer_ = nullptr; + + std::mutex mutex_; + std::atomic isStarted_{false}; + std::atomic isReleased_{false}; + std::unique_ptr videoDecInputThread_ = nullptr; + std::unique_ptr videoDecOutputThread_ = nullptr; + std::unique_ptr audioDecInputThread_ = nullptr; + std::unique_ptr audioDecOutputThread_ = nullptr; + std::condition_variable doneCond_; + SampleInfo sampleInfo_; + CodecUserData *videoDecContext_ = nullptr; + CodecUserData *audioDecContext_ = nullptr; + OH_AudioStreamBuilder *builder_ = nullptr; + OH_AudioRenderer *audioRenderer_ = nullptr; +#ifdef DEBUG_DECODE + std::ofstream audioOutputFile_; // for debug +#endif + static constexpr int64_t MICROSECOND = 1000000; +}; + +#endif // VIDEO_CODEC_PLAYER_H \ No newline at end of file diff --git a/entry/src/main/cpp/sample/player/PlayerNative.cpp b/entry/src/main/cpp/sample/player/PlayerNative.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5e598c7b2f96fe1c939a34e0f315ac0edb3960c --- /dev/null +++ b/entry/src/main/cpp/sample/player/PlayerNative.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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 "PlayerNative.h" + +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xFF00 +#define LOG_TAG "player" + +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; + // 管理 napi_value 的生命周期,防止内存泄露 + napi_open_handle_scope(context->env, &scope); + napi_value callback = nullptr; + napi_get_reference_value(context->env, context->callbackRef, &callback); + // 回调至UI侧 + napi_call_function(context->env, nullptr, callback, 0, nullptr, nullptr); + napi_close_handle_scope(context->env, scope); + delete context; + delete work; + }); +} + +napi_value PlayerNative::Play(napi_env env, napi_callback_info info) { + SampleInfo sampleInfo; + size_t argc = 4; + napi_value args[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + int32_t two = 2; + int32_t three = 3; + napi_get_value_int32(env, args[0], &sampleInfo.inputFd); + napi_get_value_int64(env, args[1], &sampleInfo.inputFileOffset); + napi_get_value_int64(env, args[two], &sampleInfo.inputFileSize); + + auto asyncContext = new CallbackContext(); + asyncContext->env = env; + napi_create_reference(env, args[three], 1, &asyncContext->callbackRef); + + sampleInfo.playDoneCallback = &Callback; + sampleInfo.playDoneCallbackData = asyncContext; + Player::GetInstance().Init(sampleInfo); + Player::GetInstance().Start(); + return nullptr; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor classProp[] = { + {"playNative", nullptr, PlayerNative::Play, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + + NativeXComponentSample::PluginManager::GetInstance()->Export(env, exports); + napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp); + return exports; +} +EXTERN_C_END + +static napi_module PlayerModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "player", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterPlayerModule(void) { napi_module_register(&PlayerModule); } \ No newline at end of file diff --git a/entry/src/main/cpp/sample/player/PlayerNative.h b/entry/src/main/cpp/sample/player/PlayerNative.h new file mode 100644 index 0000000000000000000000000000000000000000..75dc35af559748e379a280aa94c699a76edc7d90 --- /dev/null +++ b/entry/src/main/cpp/sample/player/PlayerNative.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 +#include +#include "napi/native_api.h" +#include "Player.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" +#include "PluginManager.h" + +class PlayerNative { +public: + static napi_value Play(napi_env env, napi_callback_info info); +}; +#endif // VIDEO_CODEC_SAMPLE_PLAYER_NATIVE_H \ No newline at end of file diff --git a/entry/src/main/cpp/sample/recorder/Recorder.cpp b/entry/src/main/cpp/sample/recorder/Recorder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..13337caf682ef5bc9142fd536b71af843728b12d --- /dev/null +++ b/entry/src/main/cpp/sample/recorder/Recorder.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 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 "Recorder.h" +#include +#include "AVCodecSampleLog.h" +#include "dfx/error/AVCodecSampleError.h" + +#undef LOG_TAG +#define LOG_TAG "recorder" + +namespace { +using namespace std::chrono_literals; +constexpr int64_t MICROSECOND = 1000000; +} // namespace + +Recorder::~Recorder() { StartRelease(); } + +int32_t Recorder::Init(SampleInfo &sampleInfo) { + std::lock_guard lock(mutex_); + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(videoEncoder_ == nullptr && muxer_ == nullptr, AVCODEC_SAMPLE_ERR_ERROR, + "Already started."); + + sampleInfo_ = sampleInfo; + + videoEncoder_ = std::make_unique(); + muxer_ = std::make_unique(); + + int32_t ret = videoEncoder_->Create(sampleInfo_.videoCodecMime); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create video encoder failed"); + 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); + + encContext_ = new CodecUserData; + ret = videoEncoder_->Config(sampleInfo_, encContext_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Encoder config failed"); + + ret = muxer_->Config(sampleInfo_); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Recorder muxer config failed"); + + sampleInfo.window = sampleInfo_.window; + + releaseThread_ = nullptr; + AVCODEC_SAMPLE_LOGI("Succeed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Recorder::Start() { + std::lock_guard lock(mutex_); + CHECK_AND_RETURN_RET_LOG(!isStarted_, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + CHECK_AND_RETURN_RET_LOG(encContext_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Already started."); + 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"); + + isStarted_ = true; + encOutputThread_ = std::make_unique(&Recorder::EncOutputThread, this); + if (encOutputThread_ == nullptr) { + AVCODEC_SAMPLE_LOGE("Create thread failed"); + StartRelease(); + return AVCODEC_SAMPLE_ERR_ERROR; + } + + AVCODEC_SAMPLE_LOGI("Succeed"); + return AVCODEC_SAMPLE_ERR_OK; +} + +void Recorder::EncOutputThread() { + while (true) { + CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out"); + std::unique_lock lock(encContext_->outputMutex); + bool condRet = encContext_->outputCond.wait_for( + lock, 5s, [this]() { return !isStarted_ || !encContext_->outputBufferInfoQueue.empty(); }); + CHECK_AND_BREAK_LOG(isStarted_, "Work done, thread out"); + CHECK_AND_CONTINUE_LOG(!encContext_->outputBufferInfoQueue.empty(), + "Buffer queue is empty, continue, cond ret: %{public}d", condRet); + + CodecBufferInfo bufferInfo = encContext_->outputBufferInfoQueue.front(); + encContext_->outputBufferInfoQueue.pop(); + CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out"); + lock.unlock(); + if ((bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) || + (bufferInfo.attr.flags == AVCODEC_BUFFER_FLAGS_NONE)) { + encContext_->outputFrameCount++; + bufferInfo.attr.pts = encContext_->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, + encContext_->outputFrameCount, bufferInfo.attr.size, bufferInfo.attr.flags, + bufferInfo.attr.pts); + + muxer_->WriteSample(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", encContext_->outputFrameCount); + StartRelease(); +} + +void Recorder::StartRelease() { + if (releaseThread_ == nullptr) { + AVCODEC_SAMPLE_LOGI("Start release CodecTest"); + releaseThread_ = std::make_unique(&Recorder::Release, this); + } +} + +void Recorder::Release() { + std::lock_guard lock(mutex_); + isStarted_ = false; + if (encOutputThread_ && encOutputThread_->joinable()) { + encOutputThread_->join(); + encOutputThread_.reset(); + } + if (muxer_ != nullptr) { + muxer_->Release(); + muxer_.reset(); + AVCODEC_SAMPLE_LOGI("Muxer release successful"); + } + if (videoEncoder_ != nullptr) { + videoEncoder_->Stop(); + videoEncoder_->Release(); + videoEncoder_.reset(); + AVCODEC_SAMPLE_LOGI("Video encoder release successful"); + } + if (encContext_ != nullptr) { + delete encContext_; + encContext_ = nullptr; + } + doneCond_.notify_all(); + AVCODEC_SAMPLE_LOGI("Succeed"); +} + +int32_t Recorder::WaitForDone() { + AVCODEC_SAMPLE_LOGI("Wait called"); + std::unique_lock lock(mutex_); + doneCond_.wait(lock); + if (releaseThread_ && releaseThread_->joinable()) { + releaseThread_->join(); + releaseThread_.reset(); + } + AVCODEC_SAMPLE_LOGI("Done"); + return AVCODEC_SAMPLE_ERR_OK; +} + +int32_t Recorder::Stop() { + int32_t ret = videoEncoder_->NotifyEndOfStream(); + CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Encoder notifyEndOfStream failed"); + return WaitForDone(); +} \ No newline at end of file diff --git a/entry/src/main/cpp/sample/recorder/Recorder.h b/entry/src/main/cpp/sample/recorder/Recorder.h new file mode 100644 index 0000000000000000000000000000000000000000..5fd7b2863bb8f4b635f57539e16dda571a351446 --- /dev/null +++ b/entry/src/main/cpp/sample/recorder/Recorder.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 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_RECODER_H +#define VIDEO_CODEC_SAMPLE_RECODER_H + +#include +#include +#include +#include +#include +#include +#include "VideoEncoder.h" +#include "Muxer.h" +#include "SampleInfo.h" + +class Recorder { +public: + Recorder(){}; + ~Recorder(); + + static Recorder &GetInstance() { + static Recorder recorder; + return recorder; + } + + int32_t Init(SampleInfo &sampleInfo); + int32_t Start(); + int32_t Stop(); + +private: + void EncOutputThread(); + void Release(); + void StartRelease(); + int32_t WaitForDone(); + + std::unique_ptr videoEncoder_ = nullptr; + std::unique_ptr muxer_ = nullptr; + + std::mutex mutex_; + std::atomic isStarted_{false}; + std::unique_ptr encOutputThread_ = nullptr; + std::unique_ptr releaseThread_ = nullptr; + std::condition_variable doneCond_; + SampleInfo sampleInfo_; + CodecUserData *encContext_ = nullptr; +}; + +#endif // VIDEO_CODEC_SAMPLE_RECODER_H \ No newline at end of file diff --git a/entry/src/main/cpp/sample/recorder/RecorderNative.cpp b/entry/src/main/cpp/sample/recorder/RecorderNative.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fe404a9f78b8bed5b28f2be2c9f192ee574c1934 --- /dev/null +++ b/entry/src/main/cpp/sample/recorder/RecorderNative.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2024 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 "RecorderNative.h" +#include + +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xFF00 +#define LOG_TAG "recorder" + +struct AsyncCallbackInfo { + napi_env env; + napi_async_work asyncWork; + napi_deferred deferred; + int32_t resultCode = 0; + std::string surfaceId = ""; + SampleInfo sampleInfo; +}; + +void DealCallBack(napi_env env, void *data) { + AsyncCallbackInfo *asyncCallbackInfo = static_cast(data); + napi_value code; + napi_create_int32(env, asyncCallbackInfo->resultCode, &code); + napi_value surfaceId; + napi_create_string_utf8(env, asyncCallbackInfo->surfaceId.data(), NAPI_AUTO_LENGTH, &surfaceId); + napi_value obj; + napi_create_object(env, &obj); + + napi_set_named_property(env, obj, "code", code); + napi_set_named_property(env, obj, "surfaceId", surfaceId); + napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj); + napi_delete_async_work(env, asyncCallbackInfo->asyncWork); + delete asyncCallbackInfo; +} + +void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code) { asyncCallbackInfo->resultCode = code; } + +void SurfaceIdCallBack(AsyncCallbackInfo *asyncCallbackInfo, std::string surfaceId) { + asyncCallbackInfo->surfaceId = surfaceId; +} + +void NativeInit(napi_env env, void *data) { + AsyncCallbackInfo *asyncCallbackInfo = static_cast(data); + int32_t ret = Recorder::GetInstance().Init(asyncCallbackInfo->sampleInfo); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + SetCallBackResult(asyncCallbackInfo, -1); + } + + uint64_t id = 0; + ret = OH_NativeWindow_GetSurfaceId(asyncCallbackInfo->sampleInfo.window, &id); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + SetCallBackResult(asyncCallbackInfo, -1); + } + asyncCallbackInfo->surfaceId = std::to_string(id); + SurfaceIdCallBack(asyncCallbackInfo, asyncCallbackInfo->surfaceId); +} + +napi_value RecorderNative::Init(napi_env env, napi_callback_info info) { + int32_t two = 2; + int32_t three = 3; + int32_t four = 4; + int32_t five = 5; + int32_t six = 6; + SampleInfo sampleInfo; + size_t argc = 7; + napi_value args[7] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + napi_get_value_int32(env, args[0], &sampleInfo.outputFd); + char videoCodecMime[20] = {0}; + size_t videoCodecMimeStrlen = 0; + size_t len = 20; + napi_get_value_string_utf8(env, args[1], videoCodecMime, len, &videoCodecMimeStrlen); + napi_get_value_int32(env, args[two], &sampleInfo.videoWidth); + napi_get_value_int32(env, args[three], &sampleInfo.videoHeight); + napi_get_value_double(env, args[four], &sampleInfo.frameRate); + napi_get_value_int32(env, args[five], &sampleInfo.isHDRVivid); + napi_get_value_int64(env, args[six], &sampleInfo.bitrate); + sampleInfo.videoCodecMime = videoCodecMime; + + if (sampleInfo.isHDRVivid) { + sampleInfo.hevcProfile = HEVC_PROFILE_MAIN_10; + } + + napi_value promise; + napi_deferred deferred; + napi_create_promise(env, &deferred, &promise); + + AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo(); + + asyncCallbackInfo->env = env; + asyncCallbackInfo->asyncWork = nullptr; + asyncCallbackInfo->deferred = deferred; + asyncCallbackInfo->resultCode = -1; + asyncCallbackInfo->sampleInfo = sampleInfo; + + napi_value resourceName; + napi_create_string_latin1(env, "recorder", NAPI_AUTO_LENGTH, &resourceName); + napi_create_async_work( + env, nullptr, resourceName, [](napi_env env, void *data) { NativeInit(env, data); }, + [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo, + &asyncCallbackInfo->asyncWork); + napi_queue_async_work(env, asyncCallbackInfo->asyncWork); + return promise; +} + +napi_value RecorderNative::Start(napi_env env, napi_callback_info info) { + Recorder::GetInstance().Start(); + return nullptr; +} + +void NativeStop(napi_env env, void *data) { + AsyncCallbackInfo *asyncCallbackInfo = static_cast(data); + int32_t ret = Recorder::GetInstance().Stop(); + if (ret != AVCODEC_SAMPLE_ERR_OK) { + SetCallBackResult(asyncCallbackInfo, -1); + } + SetCallBackResult(asyncCallbackInfo, 0); +} + +napi_value RecorderNative::Stop(napi_env env, napi_callback_info info) { + napi_value promise; + napi_deferred deferred; + napi_create_promise(env, &deferred, &promise); + + AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo(); + + asyncCallbackInfo->env = env; + asyncCallbackInfo->asyncWork = nullptr; + asyncCallbackInfo->deferred = deferred; + + napi_value resourceName; + napi_create_string_latin1(env, "recorder", NAPI_AUTO_LENGTH, &resourceName); + napi_create_async_work( + env, nullptr, resourceName, [](napi_env env, void *data) { NativeStop(env, data); }, + [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo, + &asyncCallbackInfo->asyncWork); + napi_queue_async_work(env, asyncCallbackInfo->asyncWork); + return promise; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor classProp[] = { + {"initNative", nullptr, RecorderNative::Init, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"startNative", nullptr, RecorderNative::Start, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"stopNative", nullptr, RecorderNative::Stop, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + + napi_value RecorderNative = nullptr; + const char *classBindName = "recorderNative"; + napi_define_class(env, classBindName, strlen(classBindName), nullptr, nullptr, 1, classProp, &RecorderNative); + napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp); + return exports; +} +EXTERN_C_END + +static napi_module RecorderModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "recorder", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterRecorderModule(void) { napi_module_register(&RecorderModule); } \ No newline at end of file diff --git a/entry/src/main/cpp/sample/recorder/RecorderNative.h b/entry/src/main/cpp/sample/recorder/RecorderNative.h new file mode 100644 index 0000000000000000000000000000000000000000..adad4cb98ae6f6239ef1c9a44661cba7fa503f2b --- /dev/null +++ b/entry/src/main/cpp/sample/recorder/RecorderNative.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 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_RECODER_NATIVE_H +#define VIDEO_CODEC_SAMPLE_RECODER_NATIVE_H + +#include +#include +#include +#include +#include "napi/native_api.h" +#include "Recorder.h" +#include "dfx/error/AVCodecSampleError.h" +#include "AVCodecSampleLog.h" + +class RecorderNative { +public: + static napi_value Init(napi_env env, napi_callback_info info); + static napi_value Start(napi_env env, napi_callback_info info); + static napi_value Stop(napi_env env, napi_callback_info info); +}; + +#endif // VIDEO_CODEC_SAMPLE_RECODER_NATIVE_H \ No newline at end of file diff --git a/entry/src/main/cpp/types/libplayer/index.d.ts b/entry/src/main/cpp/types/libplayer/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..35995e8d521657b9271f0e24c2d3336229a7b4b4 --- /dev/null +++ b/entry/src/main/cpp/types/libplayer/index.d.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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 playNative: ( + inputFileFd: number, + inputFileOffset: number, + inputFileSize: number, + cbFn: () => void +) => void; \ No newline at end of file diff --git a/entry/src/main/cpp/types/libplayer/oh-package.json5 b/entry/src/main/cpp/types/libplayer/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..9b895e04180f589cb0f7ff26ce3a4631dc439236 --- /dev/null +++ b/entry/src/main/cpp/types/libplayer/oh-package.json5 @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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": "libplayer.so", + "types": "./index.d.ts", + "version": "1.0.0", + "decription": "Player interface." +} \ No newline at end of file diff --git a/entry/src/main/cpp/types/librecorder/index.d.ts b/entry/src/main/cpp/types/librecorder/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bd6fcfe9195eea4d3f5f7ba8800200d261c91a0 --- /dev/null +++ b/entry/src/main/cpp/types/librecorder/index.d.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 initNative: (fd: number, videoCodecMime: string, width: number, height: number, + frameRate: number, isHDRVivid: number, bitRate: number) => Promise + +export const startNative: () => void + +export const stopNative: () => Promise + +export class Response { + code: number + surfaceId: string +} \ No newline at end of file diff --git a/entry/src/main/cpp/types/librecorder/oh-package.json5 b/entry/src/main/cpp/types/librecorder/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bb79f73394101892d4c1e736062bef7740bd06cb --- /dev/null +++ b/entry/src/main/cpp/types/librecorder/oh-package.json5 @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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": "librecorder.so", + "types": "./index.d.ts", + "version": "1.0.0", + "decription": "Recorder 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..3e2801ad66affff142a6ee578e319a46f718cace --- /dev/null +++ b/entry/src/main/ets/common/CommonConstants.ets @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024 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 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; + + /** + * 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 = 200; + + /** + * 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[] = ['HDRVivid', 'H264', 'H265']; + + /** + * Video resolution. + */ + static readonly VIDEO_RESOLUTION: string[] = ['4K', '1080P', '720P']; + + /** + * 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/DateTimeUtils.ets b/entry/src/main/ets/common/utils/DateTimeUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..c956b711445ea0fc2ec6087fece0ce06f6f25d64 --- /dev/null +++ b/entry/src/main/ets/common/utils/DateTimeUtils.ets @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 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 default class DateTimeUtil { + getTime(): string { + const DATETIME = new Date(); + return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds()); + } + + getDate(): string { + const DATETIME = new Date(); + return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate()); + } + + fill(value: number): string { + return (value > 9 ? '' : '0') + value; + } + + concatDate(year: number, month: number, date: number): string { + return `${year}${month}${date}`; + } + + concatTime(hour: number, minute: number, second: number): string { + return `${this.fill(hour)}${this.fill(minute)}${this.fill(second)}`; + } +} + +export function getShownTimer(ms: number): string { + let seconds: number = Math.round(ms / 1000); + let sec: number = seconds % 60; + let min: number = (seconds - sec) / 60; + let secStr = (sec >= 0 && sec < 10) ? ('0' + sec) : sec; + let minStr = (min >= 0 && min < 10) ? ('0' + min) : min; + return (minStr + ':' + secStr); +} + +export function dateTime(t: number): string { + let minute: number = Math.floor(t / 60) % 60; + let m = minute < 10 ? '0' + minute : minute; + let second: number = t % 60; + let s = second < 10 ? '0' + second : second; + return m + ':' + s; +} \ 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..7293357f7995f70603422ec46b42103123e47623 --- /dev/null +++ b/entry/src/main/ets/common/utils/Logger.ets @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 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/SaveCameraAsset.ets b/entry/src/main/ets/common/utils/SaveCameraAsset.ets new file mode 100644 index 0000000000000000000000000000000000000000..d3a91beb3026019aaa1307d553965e2d79df2f0d --- /dev/null +++ b/entry/src/main/ets/common/utils/SaveCameraAsset.ets @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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 { fileIo, picker } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import DateTimeUtil from './DateTimeUtils'; +import Logger from './Logger'; + +const TAG: string = 'SaveAsset'; + +export default class SaveAsset { + private tag: string; + private lastSaveTime: string = ''; + private saveIndex: number = 0; + + constructor(tag: string) { + this.tag = tag; + } + + public async createVideoFd(): Promise { + Logger.info(TAG, 'get Recorder File Fd'); + const mDateTimeUtil = new DateTimeUtil(); + const displayName = this.checkName(`AVCodec_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.mp4'; + Logger.info(TAG, 'get Recorder display name is: ' + displayName); + let photoSaveOptions = new picker.PhotoSaveOptions(); + photoSaveOptions.newFileNames = [displayName]; + let photoPicker = new picker.PhotoViewPicker(); + let result = await photoPicker.save(photoSaveOptions); + + let videoFd: number = 0; + await fileIo.open(result[0], fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE).then((file) => { + videoFd = file.fd; + Logger.info(TAG, 'getRawFileDescriptor success fileName: ' + result[0] + ', fd: ' + videoFd); + }).catch((err: BusinessError) => { + Logger.error(TAG, 'open file failed with error message: ' + err.message + ', error code: ' + err.code); + }) + + Logger.info(TAG, 'leave get Recorder File Fd'); + return videoFd; + } + + private checkName(name: string): string { + if (this.lastSaveTime == name) { + this.saveIndex += 1; + return `${name}_${this.saveIndex}`; + } + this.lastSaveTime = name; + this.saveIndex = 0; + Logger.info(this.tag, 'get Recorder File name is: ' + name); + return name; + } +} \ 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..b81c18f1349eefd006453ca5539784ccdb871c0a --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 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'); + + try { + let atManager = abilityAccessCtrl.createAtManager(); + atManager.requestPermissionsFromUser(this.context, + ['ohos.permission.MEDIA_LOCATION', 'ohos.permission.READ_MEDIA', + 'ohos.permission.WRITE_MEDIA', 'ohos.permission.CAMERA']) + .then((data) => { + Logger.info('Entry', 'requestPre() data: ' + JSON.stringify(data)); + }).catch((err: BusinessError) => { + Logger.info('Entry', 'requestPre() data: ' + JSON.stringify(err)); + }) + } catch (err) { + Logger.error('Entry', 'requestPre() data: ' + JSON.stringify(err)); + } + + 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; + } + 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..62cb15a36f79397d9ac3c07fefb163a0087e1ca5 --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..24ee45842e1ea3059b012ceff7e725c76979989c --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2024 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 { picker } from '@kit.CoreFileKit'; +import { fileIo } from '@kit.CoreFileKit'; +import { display, promptAction, router } from '@kit.ArkUI'; +import player from 'libplayer.so'; +import recorder from 'librecorder.so'; +import Logger from '../common/utils/Logger'; +import SaveAsset from '../common/utils/SaveCameraAsset'; +import { CommonConstants as Const } from '../common/CommonConstants'; + +const TAG: string = Const.INDEX_TAG; + +class DataModel { + surfaceId: string = ''; + cameraWidth: number = Const.DEFAULT_VALUE; + cameraHeight: number = Const.DEFAULT_VALUE; + isHDRVivid: number = Const.DEFAULT_VALUE; + outputfd: number | null = null; + frameRate: number = Const.DEFAULT_VALUE; +} + +@Entry +@Component +struct Player { + @State buttonEnabled: boolean = true; + private selectFilePath: string | null = null; + private mSaveAsset: SaveAsset = new SaveAsset(TAG); + private videoCodecMime: string | null = Const.MIME_VIDEO_AVC; + private isHDRVivid: number = Const.DEFAULT_VALUE; + private cameraWidth: number = Const.DEFAULT_WIDTH; + private cameraHeight: number = Const.DEFAULT_HEIGHT; + private bitRate: number = Const.BITRATE_VIDEO_20M; + private display = display.getDefaultDisplaySync(); + private heightPx = (this.display.width * this.cameraHeight / this.cameraWidth) + Const.PX; + private frameRate: number = Const.FRAMERATE_VIDEO_30FPS; + private outputfd: number | null = null; + + selectFile() { + let documentSelectOptions = new picker.DocumentSelectOptions; + let documentViewPicker = new picker.DocumentViewPicker; + documentViewPicker.select(documentSelectOptions) + .then((documentSelectResult) => { + this.selectFilePath = documentSelectResult[0]; + if (this.selectFilePath == null) { + promptAction.showToast({ + message: $r('app.string.alert'), + duration: Const.DURATION, + bottom: Const.BOTTOM + }); + } else { + this.play(); + Logger.info(TAG, 'documentViewPicker.select to file succeed and URI is:' + this.selectFilePath); + } + }); + } + + async selectOutputFilePath(): Promise { + this.outputfd = await this.mSaveAsset.createVideoFd(); + if (this.outputfd !== null) { + recorder.initNative(this.outputfd, this.videoCodecMime, this.cameraWidth, + this.cameraHeight, this.frameRate, this.isHDRVivid, this.bitRate) + .then((data) => { + if (data.surfaceId !== null) { + let paramsInfo: DataModel = { + surfaceId: data.surfaceId, + cameraWidth: this.cameraWidth, + cameraHeight: this.cameraHeight, + isHDRVivid: this.isHDRVivid, + outputfd: this.outputfd, + frameRate: this.frameRate + }; + router.pushUrl({ + url: 'pages/Recorder', + params: paramsInfo + }); + } + }) + } else { + Logger.error(TAG, 'get outputfd failed'); + } + } + + play() { + let inputFile = fileIo.openSync(this.selectFilePath, fileIo.OpenMode.READ_ONLY); + if (!inputFile) { + Logger.error(TAG, 'player inputFile is null'); + } + let inputFileState = fileIo.statSync(inputFile.fd); + if (inputFileState.size <= 0) { + Logger.error(TAG, 'player inputFile size is 0'); + } + this.buttonEnabled = false; + player.playNative(inputFile.fd, Const.DEFAULT_VALUE, inputFileState.size, () => { + Logger.info(TAG, 'player JSCallback'); + this.buttonEnabled = true; + fileIo.close(inputFile); + }) + } + + @Builder + SettingButton() { + Button() { + Image($r('app.media.gearshape')) + .width($r('app.float.set_image_width')) + .height($r('app.float.set_image_height')) + } + .width($r('app.float.set_button_width')) + .height($r('app.float.set_button_height')) + .type(ButtonType.Circle) + .backgroundColor($r('app.color.set_button_background')) + .margin({ right: $r('app.float.set_button_margin_right') }) + .onClick(() => { + TextPickerDialog.show({ + defaultPickerItemHeight: Const.DEFAULT_PICKER_ITEM_HEIGHT, + selectedTextStyle: ({ + font: ({ + size: Const.SELECTED_TEXT_STYLE_FONT_SIZE + }) + }), + range: Const.RECORDER_INFO, + canLoop: false, + alignment: DialogAlignment.Center, + backgroundColor: Color.White, + backgroundBlurStyle: BlurStyle.BACKGROUND_ULTRA_THICK, + onAccept: (value: TextPickerResult) => { + switch (value.value[0]) { + case Const.VIDEO_MIMETYPE[0]: { + this.videoCodecMime = Const.MIME_VIDEO_HEVC; + this.isHDRVivid = Const.TRUE; + break; + } + case Const.VIDEO_MIMETYPE[1]: { + this.videoCodecMime = Const.MIME_VIDEO_AVC; + this.isHDRVivid = Const.FALSE; + break; + } + case Const.VIDEO_MIMETYPE[2]: { + this.videoCodecMime = Const.MIME_VIDEO_HEVC; + this.isHDRVivid = Const.FALSE; + break; + } + default: + break; + } + + switch (value.value[1]) { + case Const.VIDEO_RESOLUTION[0]: { + this.cameraWidth = Const.VIDEO_WIDTH_4K; + this.cameraHeight = Const.VIDEO_HEIGHT_4K; + this.bitRate = Const.BITRATE_VIDEO_30M; + break; + } + case Const.VIDEO_RESOLUTION[1]: { + this.cameraWidth = Const.VIDEO_WIDTH_1080P; + this.cameraHeight = Const.VIDEO_HEIGHT_1080P; + this.bitRate = Const.BITRATE_VIDEO_20M; + break; + } + case Const.VIDEO_RESOLUTION[2]: { + this.cameraWidth = Const.VIDEO_WIDTH_720P; + this.cameraHeight = Const.VIDEO_HEIGHT_720P; + this.bitRate = Const.BITRATE_VIDEO_10M; + break; + } + default: + break; + } + + switch (value.value[2]) { + case Const.VIDEO_FRAMERATE[0]: { + this.frameRate = Const.FRAMERATE_VIDEO_30FPS; + break; + } + case Const.VIDEO_FRAMERATE[1]: { + this.frameRate = Const.FRAMERATE_VIDEO_60FPS; + break; + } + default: + break; + } + } + }); + }) + } + + @Builder + Window() { + Row() { + XComponent({ + id: 'player', + type: XComponentType.SURFACE, + libraryname: 'player' + }) + .height(this.heightPx) + .width(Const.FULL_SIZE) + } + .alignRules({ + 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top }, + 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start } + }) + .margin({ top: $r('app.float.window_margin_top') }) + } + + build() { + RelativeContainer() { + Row() { + this.SettingButton() + } + .width($r('app.string.full_width')) + .height($r('app.float.set_row_height')) + .margin({ top: $r('app.float.set_row_margin_top') }) + .justifyContent(FlexAlign.End) + .alignRules({ + 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top }, + 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start } + }) + + this.Window() + + Column() { + Button(this.buttonEnabled ? $r('app.string.play') : $r('app.string.playing')) + .onClick(() => { + this.selectFile(); + }) + .size({ + width: $r('app.float.index_button_width'), + height: $r('app.float.index_button_height') + }) + .enabled(this.buttonEnabled) + .margin({ bottom: $r('app.float.button_margin_bottom') }) + + Button($r('app.string.record')) + .onClick(() => { + this.selectOutputFilePath(); + }) + .size({ + width: $r('app.float.index_button_width'), + height: $r('app.float.index_button_height') + }) + .enabled(this.buttonEnabled) + .margin({ bottom: $r('app.float.last_button_margin_bottom') }) + } + .alignRules({ + 'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom }, + 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start } + }) + .width($r('app.string.full_width')) + .height($r('app.float.index_column_height')) + .justifyContent(FlexAlign.End) + } + .width($r('app.string.full_width')) + .height($r('app.string.full_height')) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Recorder.ets b/entry/src/main/ets/pages/Recorder.ets new file mode 100644 index 0000000000000000000000000000000000000000..df42603e2ed1eecf86337770443cfba2c20462ca --- /dev/null +++ b/entry/src/main/ets/pages/Recorder.ets @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2024 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 { fileIo } from '@kit.CoreFileKit'; +import { display, router } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import recorder from 'librecorder.so'; +import Logger from '../common/utils/Logger'; +import { dateTime } from '../common/utils/DateTimeUtils'; +import { CommonConstants as Const } from '../common/CommonConstants'; + +const TAG: string = Const.RECORDER_TAG; + +const params: RouTmp = router.getParams() as RouTmp; +const encoderSurfaceId: string = params.surfaceId; +const cameraWidth: number = params.cameraWidth; +const cameraHeight: number = params.cameraHeight; +const isHDRVivid: number = params.isHDRVivid; +const frameRate: number = params.frameRate; +const outputfd: number = params.outputfd; + +let cameraInput: camera.CameraInput; +let XComponentPreviewOutput: camera.PreviewOutput; +let encoderVideoOutput: camera.VideoOutput; +let videoSession: camera.VideoSession; + +class RouTmp { + surfaceId: string = Const.DEFAULT_ID; + cameraWidth: number = Const.DEFAULT_VALUE; + cameraHeight: number = Const.DEFAULT_VALUE; + isHDRVivid: number = Const.DEFAULT_VALUE; + frameRate: number = Const.DEFAULT_VALUE; + outputfd: number = Const.DEFAULT_VALUE; +} + +async function releaseCamera() { + // Stop the video output stream + if (encoderVideoOutput) { + encoderVideoOutput.stop((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to stop the encoder video output. error: ${JSON.stringify(err)}`); + return; + } + Logger.info(TAG, 'Callback invoked to indicate the encoder video output stop success.'); + }) + } + // Stop the Session. + videoSession.stop(); + // Close file fd. + fileIo.close(outputfd); + // Close camera input stream. + cameraInput.close(); + // Release preview output stream. + XComponentPreviewOutput.release(); + // Release the video output stream. + encoderVideoOutput.release(); + Logger.info(TAG, 'encoderVideoOutput release'); + // Release session. + videoSession.release(); +} + +function getCameraDevices(cameraManager: camera.CameraManager): Array { + let cameraArray: Array = cameraManager.getSupportedCameras(); + if (cameraArray !== undefined && cameraArray.length <= 0) { + Logger.error(TAG, 'cameraManager.getSupportedCameras error'); + return []; + } + for (let index = 0; index < cameraArray.length; index++) { + Logger.info(TAG, 'getCameraDevices -- cameraId :' + cameraArray[index].cameraId); + Logger.info(TAG, 'getCameraDevices -- cameraPosition :' + cameraArray[index].cameraPosition); + } + return cameraArray; +} + +@Entry +@Component +struct Recorder { + @State isRecorderTimeTextHide: boolean = true; + @State playFlag: boolean = false; + @State buttonEnabled: boolean = true; + @State videoRecorderTimeText: string = Const.DEFAULT_TIME; + @State fov: number = 1; + private XComponentSurfaceId: string = Const.DEFAULT_ID; + private cameraWidth: number = Const.DEFAULT_WIDTH; + private cameraHeight: number = Const.DEFAULT_HEIGHT; + private XComponentController: XComponentController = new XComponentController(); + private display = display.getDefaultDisplaySync(); + private heightPx = (this.display.width * this.cameraWidth / this.cameraHeight) + Const.PX; + private timer: number = Const.DEFAULT_VALUE; + private seconds: number = Const.DEFAULT_VALUE; + private isReleased: boolean = false; + private isBack: boolean = false; + private range: number[] = []; + + onBackPress() { + this.isBack = true; + } + + onPageHide() { + this.release(); + if (!this.isBack) { + router.back(); + } + } + + async release() { + if (!this.isReleased) { + this.isReleased = true; + clearInterval(this.timer); + this.seconds = 0; + this.videoRecorderTimeText = Const.DEFAULT_TIME; + + recorder.stopNative().then(async (data) => { + if (data.code == 0) { + await releaseCamera(); + this.playFlag = false; + } + }) + } + } + + getRecordTime(): void { + this.timer = setInterval(() => { + this.seconds += 1; + this.videoRecorderTimeText = dateTime(this.seconds); + }, 1000); + } + + async createRecorder(): Promise { + releaseCamera(); + // Create the CameraManager object. + let cameraManager = camera.getCameraManager(getContext()); + if (!cameraManager) { + Logger.error(TAG, 'camera.getCameraManager error'); + return; + } + + // Get supported camera devices. + let camerasDevices: Array = getCameraDevices(cameraManager); + + // Get profile object. + let profiles: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(camerasDevices[0], + camera.SceneMode.NORMAL_VIDEO); + if (!profiles) { + Logger.error(TAG, 'cameraManager.getSupportedOutputCapability error'); + return; + } + + // Get the preview stream profile + let previewProfiles: Array = profiles.previewProfiles; + if (!previewProfiles) { + Logger.error(TAG, 'createOutput previewProfiles == null || undefined'); + return; + } + + // Get the video stream profile. + let videoProfiles: Array = profiles.videoProfiles; + if (!videoProfiles) { + Logger.error(TAG, 'createOutput videoProfiles == null || undefined'); + return; + } + + //The preview stream of XComponent. + let XComponentPreviewProfile: camera.Profile = previewProfiles[0]; + if (XComponentPreviewProfile === undefined) { + Logger.error(TAG, 'XComponentPreviewProfile is not found'); + return; + } + + // The videoProfile's width and height need to be the same as the encoder's width and height. + let videoSize: camera.Size = { + width: cameraWidth, + height: cameraHeight + }; + + // Matches the videoProfile selected by the user. + let videoProfile: undefined | camera.VideoProfile = videoProfiles.find((profile: camera.VideoProfile) => { + if (isHDRVivid) { + if (frameRate === Const.FRAMERATE_VIDEO_30FPS) { + return profile.size.width === videoSize.width && + profile.size.height === videoSize.height && + profile.format === camera.CameraFormat.CAMERA_FORMAT_YCBCR_P010 && + profile.frameRateRange.min === Const.MIN_RANGE && + profile.frameRateRange.max === Const.MAX_RANGE; + } else { + return profile.size.width === videoSize.width && + profile.size.height === videoSize.height && + profile.format === camera.CameraFormat.CAMERA_FORMAT_YCBCR_P010 && + profile.frameRateRange.min === frameRate && + profile.frameRateRange.max === frameRate; + } + } else { + if (frameRate == Const.FRAMERATE_VIDEO_30FPS) { + return profile.size.width === videoSize.width && + profile.size.height === videoSize.height && + profile.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP && + profile.frameRateRange.min === Const.MIN_RANGE && + profile.frameRateRange.max === Const.MAX_RANGE; + } else { + return profile.size.width === videoSize.width && + profile.size.height === videoSize.height && + profile.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP && + profile.frameRateRange.min === frameRate && + profile.frameRateRange.max === frameRate; + } + } + }); + if (!videoProfile) { + Logger.error(TAG, 'videoProfile is not found'); + return; + } + + //Create the encoder output object + encoderVideoOutput = cameraManager.createVideoOutput(videoProfile, encoderSurfaceId); + if (encoderVideoOutput === undefined) { + Logger.error(TAG, 'encoderVideoOutput is undefined'); + return; + } + Logger.info(TAG, 'encoderVideoOutput success'); + + // Create a preview stream output object + XComponentPreviewOutput = cameraManager.createPreviewOutput(XComponentPreviewProfile, this.XComponentSurfaceId); + if (XComponentPreviewOutput === undefined) { + Logger.error(TAG, 'XComponentPreviewOutput is undefined'); + return; + } + + // Create the cameraInput object. + try { + cameraInput = cameraManager.createCameraInput(camerasDevices[0]); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to createCameraInput. error: ${JSON.stringify(err)}`); + } + if (cameraInput === undefined) { + Logger.error(TAG, 'cameraInput is undefined'); + return; + } + + // Turn on the camera. + try { + await cameraInput.open(); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to open cameraInput. error: ${JSON.stringify(err)}`); + } + + // Session flow. + try { + videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession; + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to create the session instance. error: ${JSON.stringify(err)}`); + } + if (videoSession === undefined) { + Logger.error(TAG, 'videoSession is undefined'); + return; + } + + // Start Configuring the session. + try { + videoSession.beginConfig(); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to beginConfig. error: ${JSON.stringify(err)}`); + } + + // Add CameraInput to the session. + try { + videoSession.addInput(cameraInput); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to add cameraInput. error: ${JSON.stringify(err)}`); + } + + // Add the XComponent preview stream to the session. + try { + videoSession.addOutput(XComponentPreviewOutput); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to add XcomponentPreviewOutput. error: ${JSON.stringify(err)}`); + } + + // Add the encoder video stream to the session. + try { + videoSession.addOutput(encoderVideoOutput); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `Failed to add encoderVideoOutput. error: ${JSON.stringify(err)}`); + } + + // Submit configuration information. + try { + await videoSession.commitConfig(); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `videoSession commitConfig error: ${JSON.stringify(err)}`); + } + + // Session start. + try { + await videoSession.start(); + } catch (error) { + let err = error as BusinessError; + Logger.error(TAG, `videoSession start error: ${JSON.stringify(err)}`); + } + + // Start the video output stream + encoderVideoOutput.start((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to start the encoder video output. error: ${JSON.stringify(err)}`); + return; + } + Logger.info(TAG, 'Callback invoked to indicate the encoder video output start success.'); + }) + } + + build() { + Stack() { + XComponent({ + id: 'recorderXComponent', + type: XComponentType.SURFACE, + libraryname: '', + controller: this.XComponentController + }) + .onLoad(() => { + this.XComponentSurfaceId = this.XComponentController.getXComponentSurfaceId(); + this.createRecorder(); + this.playFlag = true; + recorder.startNative(); + this.isRecorderTimeTextHide = false; + this.getRecordTime(); + }) + .width(Const.FULL_SIZE) + .height(this.heightPx) + .gesture( + PinchGesture() + .onActionUpdate((event: GestureEvent) => { + if (videoSession) { + let currentFov = this.fov * event.scale; + if (currentFov > this.range[1]) { + currentFov = this.range[1]; + } + if (currentFov < this.range[0]) { + currentFov = this.range[0]; + } + videoSession.setZoomRatio(currentFov); + } + }) + .onActionEnd((event: GestureEvent) => { + if (videoSession) { + this.fov = videoSession.getZoomRatio(); + } + }) + ) + + if (!this.isRecorderTimeTextHide) { + Text(this.videoRecorderTimeText) + .fontFamily('HarmonyHeilTi-Light') + .fontSize(27) + .fontColor(Color.White) + .align(Alignment.Bottom) + .margin({ bottom: $r('app.float.recorder_time_margin_bottom') }) + } + + Image($r('app.media.camera_pause_video_4x')) + .width($r('app.float.recorder_image_width')) + .height($r('app.float.recorder_image_height')) + .margin({ top: $r('app.float.recorder_image_margin_top') }) + .onClick(() => { + this.buttonEnabled = false; + this.release(); + this.isBack = true; + router.back(); + }) + .enabled(this.buttonEnabled) + } + .width(Const.FULL_SIZE) + .height(Const.FULL_SIZE) + .backgroundColor(Color.Black) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bfbbf0e11aa14b99bba0c52528b129167fdbfdef --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,84 @@ +{ + "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": [ + { + "name": "ohos.permission.READ_MEDIA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": ["EntryAbility"], + "when": "always" + } + }, + { + "name": "ohos.permission.WRITE_MEDIA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": ["EntryAbility"], + "when": "always" + } + }, + { + "name": "ohos.permission.MEDIA_LOCATION", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": ["EntryAbility"], + "when": "always" + } + }, + { + "name": "ohos.permission.CAMERA", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": ["EntryAbility"], + "when": "always" + } + } + ] + } +} \ 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..7044cd1ad581ab07126ab27ff6e17ed377f40683 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,44 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecVideo" + }, + { + "name": "alert", + "value": "None video selected" + }, + { + "name": "full_width", + "value": "100%" + }, + { + "name": "full_height", + "value": "100%" + }, + { + "name": "reason", + "value": "label" + }, + { + "name": "playing", + "value": "playing" + }, + { + "name": "play", + "value": "play" + }, + { + "name": "record", + "value": "record" + } + ] +} \ 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/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..e98e8b9c205409bcc57e87b1da2ca9d7da0eb97c --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,6 @@ +{ + "src": [ + "pages/Index", + "pages/Recorder" + ] +} 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..7044cd1ad581ab07126ab27ff6e17ed377f40683 --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,44 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecVideo" + }, + { + "name": "alert", + "value": "None video selected" + }, + { + "name": "full_width", + "value": "100%" + }, + { + "name": "full_height", + "value": "100%" + }, + { + "name": "reason", + "value": "label" + }, + { + "name": "playing", + "value": "playing" + }, + { + "name": "play", + "value": "play" + }, + { + "name": "record", + "value": "record" + } + ] +} \ No newline at end of file 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..35c12a541d5f9622a0d6369f8b777a527ab37534 --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,44 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "AVCodecVideo" + }, + { + "name": "full_width", + "value": "100%" + }, + { + "name": "full_height", + "value": "100%" + }, + { + "name": "alert", + "value": "未选择视频!" + }, + { + "name": "reason", + "value": "label" + }, + { + "name": "playing", + "value": "播放中" + }, + { + "name": "play", + "value": "播放" + }, + { + "name": "record", + "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..06b2783670a348f95533b352c1ceda909a842bbc --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.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..490e08c08b4db775f0c5887aabe564c0e36e2b44 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": {} +} diff --git a/screenshots/device/AVCodecSample.gif b/screenshots/device/AVCodecSample.gif new file mode 100644 index 0000000000000000000000000000000000000000..4ec67594f26280460cab759398b981917ca40e0b Binary files /dev/null and b/screenshots/device/AVCodecSample.gif differ diff --git a/screenshots/device/AVCodec_Index.png b/screenshots/device/AVCodec_Index.png new file mode 100644 index 0000000000000000000000000000000000000000..2be1928bded5808dbda7f5d4e710bac60b6ce662 Binary files /dev/null and b/screenshots/device/AVCodec_Index.png differ