# HarmonyOS Vulkan Vsr Sample **Repository Path**: fenghaijun2/vulkan-vsr-sample ## Basic Information - **Project Name**: HarmonyOS Vulkan Vsr Sample - **Description**: 鸿蒙系统基于Vulkan的视频超分demo - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2024-05-10 - **Last Updated**: 2026-03-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # VulkanVsrSample ### 介绍 hiai foundation kit 部件示例 Sample,基于 API11 构建,提供视频超分功能。 - 视频超分的主要流程是将视频文件通过解封装->解码->超分->送显至屏幕。 - npu读取vulkan纹理的主要流程是从VkDeviceMemory获取NativeBuffer->转换为NativeWindowBuffer->从NativeWindowBuffer中获取到handle->通过handle的fd创建inputTensor。 ### 效果预览 | 超分前 |超分后 | |-----------------------------------------|--------------------------------------------| |NA | ![超分后.jpeg](screenshots/vsr.jpg) | ### 使用说明 1. 弹出是否允许“VulkanVsr”访问图片与视频?点击“允许” 2. 弹出是否允许“VulkanVsr”访问文件?点击“允许” #### 播放 1. 推送entry/src/main/resources/resfile/下的896_384.mp4文件至storage/media/100/local/files/Docs下: ```shell hdc file send entry/src/main/resources/resfile/896_384.mp4 /storage/media/100/local/files/Docs ``` 2. 点击播放按钮,选择文件 ### 目录 仓目录结构如下: ``` VulkanVsrSample/entry/src/main/ ├─cpp # Native层 │ │ CMakeLists.txt # 编译入口 │ │ │ ├─include # 头文件 │ │ │ hiaivsr_base_types.h # hiai超分基本类型 │ │ │ stb_image.h # 图像加载开源库 │ │ │ │ │ ├─glm # glm数学计算开源库 │ │ └─hiai_ddk # hiai ddk api头文件 │ │ │ ├─lib64 │ │ libhiai.so # hiai ddk 主so │ │ libhiai_ir.so # hiai ddk 模型构图so │ │ libhiai_ir_build.so # hiai ddk 模型编译so │ │ libhiai_ir_build_aipp.so # hiai ddk 模型输入预处理so │ │ │ ├─shader │ │ shader.frag # 顶点着色器 │ │ shader.vert # 片元着色器 │ │ │ ├─src # Native层源文件 │ │ │ hiaivsr.cpp # 模型加载推理实现 │ │ │ hiaivsr.h # 模型加载推理定义 │ │ │ ResourceMng.cpp # rawfile目录下的资源加载实现 │ │ │ ResourceMng.h # rawfile目录下的资源加载定义 │ │ │ │ │ ├─capbilities # 功能接口和实现 │ │ │ │ demuxer.cpp # 解封装实现 │ │ │ │ video_decoder.cpp # 解码实现 │ │ │ │ │ │ │ └─include #功能接口 │ │ │ │ │ ├─common # 公共模块 │ │ │ │ logger_common.h # 公共日志模块 │ │ │ │ sample_callback.cpp # 编解码回调实现 │ │ │ │ sample_callback.h # 编解码回调定义 │ │ │ │ sample_info.h # 功能实现公共类 │ │ │ │ │ │ │ └─dfx # 解码器日志模块 │ │ │ │ │ ├─render # 送显模块接口和实现 │ │ │ │ plugin_manager.cpp # 送显模块管理实现 │ │ │ │ plugin_render.cpp # 送显逻辑实现 │ │ │ │ │ │ │ ├─include # 送显模块接口 │ │ │ └─vulkan # Vulkan渲染接口和实现 │ │ │ Renderer.cpp # Vulkan渲染接口实现主流程 │ │ │ Renderer.h # Vulkan渲染接口定义 │ │ │ VulkanBuffer.cpp # Vulkan内存接口实现 │ │ │ VulkanBuffer.h # Vulkan内存接口定义 │ │ │ VulkanDevice.cpp # Vulkan设备接口实现 │ │ │ VulkanDevice.h # Vulkan设备接口实现 │ │ │ VulkanInitializers.hpp # Vulkan接口初始化实现 │ │ │ VulkanSwapChain.cpp # Vulkan交换链实现 │ │ │ VulkanSwapChain.h # Vulkan交换链定义 │ │ │ VulkanTools.cpp # Vulkan通用接口实现 │ │ │ VulkanTools.h # Vulkan通用接口定义 │ │ │ VulkanUtils.cpp # Vulkan接口封装实现 │ │ │ VulkanUtils.h # Vulkan接口封装定义 │ │ │ │ │ └─sample # Sample底层主要调用和传递模块 │ │ └─player # Native层播放接口和实现 │ │ Player.cpp # Native层播放功能调用逻辑的实现 │ │ Player.h # Native层播放功能调用逻辑的定义 │ │ PlayerNative.cpp # JS层播放的实现 │ │ PlayerNative.h # JS层播放的接口 │ │ │ └─types # Native层暴露上来的接口 │ └─libplayer # 播放模块暴露给UI层的接口 │ ├─ets # UI层 │ ├─common # 公共模块 │ │ └─utils # 共用的工具类 │ │ DateTimeUtils.ets # 获取当前时间 │ │ Logger.ts # 日志工具 │ │ SaveCameraAsset.ets # 选取文件保持位置 │ │ │ ├─entryability # 应用的入口 │ │ EntryAbility.ts # 申请权限弹窗实现,raw资源管理器初始化 │ │ │ ├─pages # EntryAbility 包含的页面 │ │ Index.ets # 首页 │ │ │ └─player # 播放 │ Player.ets # 播放页面 │ └─resources # 用于存放应用所用到的资源文件 ├─base # 该目录下的资源文件会被赋予唯一的ID │ ├─element # 用于存放字体和颜色 │ ├─media # 用于存放图片 │ └─profile # 应用入口首页 │ ├─en_US # 设备语言是美式英文时,优先匹配此目录下资源 ├─rawfile # raw资源文件,应用安装后不会解压 │ fsr.om # 超分模型文件 │ ├─resfile # res资源文件,安装后资源会解压到指定目录 │ 896_384.mp4 # 待超分的视频 │ first_frame.jpg # 初始化Vulkan用 │ shader.frag.spv # 编译后的顶点着色器 │ shader.vert.spv # 编译后的片元着色器 │ └─zh_CN # 设备语言是简体中文时,优先匹配此目录下资源 ``` ### 播放器具体实现 ##### 播放 1. 在UI层由[Player.ets](entry%2Fsrc%2Fmain%2Fets%2Fplayer%2FPlayer.ets)用户点击播放按钮后,触发点击事件,调起selectFile()函数,该函数会调起文件管理的选择文件模块,拿到用户选取文件的路径。 2. 用户选择文件成功后,调起play()函数,该函数会根据上一步获取到的路径,打开一个文件,并获取到该文件的大小,改变按钮状态为不可用,之后调起js层暴露给应用层的[playNative()](entry%2Fsrc%2Fmain%2Fcpp%2Ftypes%2Flibplayer%2Findex.d.ts)接口 3. 根据playNative字段,调起[PlayerNative::Play](entry%2Fsrc%2Fmain%2Fcpp%2Fsample%2Fplayer%2FPlayerNative.cpp)函数。把应用层传入的前三个参数保存起来,并注册播放结束回调Callback。 4. 播放结束时,Callback()中napi_call_function()接口调起,通知应用层,改变按钮状态。 ##### JS层 1. 在[Init()](entry%2Fsrc%2Fmain%2Fcpp%2Fsrc%2Fplayer%2FPlayerNative.cpp)中调用PluginManager()中的Export()方法,注册OnSurfaceCreatedCB()回调,当屏幕上出现新的Xcomponent时,将其转换并赋给单例类PluginManager中的pluginWindow_; ##### Native层 参考[开发者文档](https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/media/avcodec),以下是补充: 1. 解码器config阶段,OH_VideoDecoder_SetSurface接口的入参OHNativeWindow*,即为PluginManager中的pluginWindow_。 2. 解码器config阶段,SetCallback接口,[输入输出回调](entry%2Fsrc%2Fmain%2Fcpp%2Fsrc%2Fcommon%2Fsample_callback.cpp)需将回调上来的帧buffer和index存入一个[用户自定义容器](entry%2Fsrc%2Fmain%2Fcpp%2Fsrc%2Fcommon%2Fsample_info.h)中,方便后续操作。 3. [Start时](entry%2Fsrc%2Fmain%2Fcpp%2Fsrc%2Fplayer%2FPlayer.cpp),起两个专门用于输入和输出的线程。 4. 具体实现原理: - 解码器Start后,回调OnNeedInputBuffer被调起,avcodec框架会给用户一个OH_AVBuffer - 在输入回调中,把帧bufer、index存入输入队列中 - 在输入线程中,把上一步的帧信息储存为bufferInfo后,pop出队 - 在输入线程中,使用上一步的bufferInfo,调用ReadSample接口解封装帧数据 - 在输入线程中,使用解封装后的bufferInfo,调用解码的PushInputData接口,此时这片buffer用完,返回框架,实现buffer轮转 - PushInputData后,输出回调会被调起,把帧buffer、index存入输出队列中 - 在输出线程中,把上一步的帧信息储存为bufferInfo后,pop出队 - 在输出线程中,调用FreeOutputData接口后,就会送显并释放buffer。释放的buffer会返回框架,实现buffer轮转。 ### 视频超分具体实现 实现超分的零拷贝,关键是获取到输入buffer的fd,通过fd来创建tensor。以下是具体实现: 1. 创建两个VkDeviceMemory,一个作为视频超分的输入:textureInputImageMemory,一个作为输出:textureOutputImageMemory; 2. 在解码器的输出线程中,获取到解码后单帧图像buffer的首地址和大小; 3. 在[updateTexture()](entry%2Fsrc%2Fmain%2Fcpp%2Fsrc%2Frender%2Fvulkan%2FRenderer.cpp)中通过单帧图像buffer的首地址和大小将图像复制到Vulkan的textureInputImageMemory; 4. 获取输入输出的VkDeviceMemory的NativeBuffer->转换为NativeWindowBuffer->从NativeWindowBuffer中获取到handle->通过handle的fd创建inputTensor与outputTensor; 5. 在步骤4中,由于解码出的视频格式为YUV420SP,模型输入应该为RGB图像,所以创建inputTensor时通过[动态Aipp](https://developer.huawei.com/consumer/cn/doc/hiai-Guides/overview-0000001326963925)设置色域转换,将YUV420SP转换为RGB; 6. 通过inputTensor和outputTensor进行推理,推理完成后新的图像数据将会刷新在textureOutputImageMemory中; 7. 将textureOutputImageMemory复制给输出的Vkimage,更新描述符集,执行渲染命令。 ### 相关权限 #### [ohos.permission.READ_MEDIA](https://docs.openharmony.cn/pages/v3.2/zh-cn/application-dev/security/permission-list.md/#ohospermissionreadmedia) #### [ohos.permission.WRITE_MEDIA](https://docs.openharmony.cn/pages/v3.2/zh-cn/application-dev/security/permission-list.md/#ohospermissionwritemedia) #### [ohos.permission.MEDIA_LOCATION](https://docs.openharmony.cn/pages/v3.2/zh-cn/application-dev/security/permission-list.md/#ohospermissionmedialocation) ### 依赖 不涉及。 ### 约束与限制 1.本示例仅支持标准系统上运行,设备需要有麒麟芯片; 2.本示例仅支持 API11 及以上版本SDK,SDK版本号(API Version 11 Release),镜像版本号(4.1Release); 3.本示例需要使用DevEco Studio 4.1 才可编译运行。 ### 相关仓 - [multimedia_av_codec](https://gitee.com/openharmony/multimedia_av_codec) - [videoCodecSample](https://gitee.com/kairen-13/video-codec-sample) - [VulkanDemo](https://gitee.com/andrew0229/vulkan-demo) - [Vulkan-tutorial C++ code](https://vulkan-tutorial.com/code/26_texture_mapping.cpp) - [Vulkan-tutorial Vertex shader](https://vulkan-tutorial.com/code/26_shader_textures.vert) - [Vulkan-tutorial Fragment shader](https://vulkan-tutorial.com/code/26_shader_textures.frag) ### 运行时问题 - 现象: 视频播放时黑屏 - 原因: selinux策略适配未带上,待更新ROM - 临时解决方案: 暂时关闭selinux ```shell hdc shell setenforce 0 ```