# HMOSLiveAudioCall
**Repository Path**: zhouxinyu0709/HMOS_LiveAudioCall
## Basic Information
- **Project Name**: HMOSLiveAudioCall
- **Description**: 本示例在媒体直播案例基础上实现了媒体直播连麦功能
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2025-12-13
- **Last Updated**: 2025-12-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 基于媒体子系统实现媒体直播连麦功能
## 项目简介
本示例基于媒体子系统,实现媒体直播的开直播端、看播端和直播连麦模块。实现了直播场景常用的音视频采集、音视频播放、音频焦点管理、ROI、
背景音乐添加、前后摄像头翻转等功能。基于本示例可帮助应用开发开启直播和观看直播的场景。
- 看播端是通过播放视频文件来模拟的,主要流程是将直播端录制的视频文件通过AvPlayer播放。
- 直播端视频录制的主要流程是相机采集->Opengl旋转->编码->封装成mp4文件。
- 直播过程通过分布式文件进行模拟。两台手机分别模拟直播端和看播端,须登录同一个华为账号并开启WIFI和蓝牙,完成分布式组网。具体流程为:直播端
将录制的视频文件保存到沙箱并拷贝到分布式目录中,看播端将分布式文件下的视频文件拷贝到沙箱后,通过AvPlayer进行播放。
- 录制场景在相机和编码之间加了OpenGL渲染管线,开发者可参考该流程加上对应的Shader,如直播场景加上美颜、滤镜等算子。
- 直播连麦是在录制视频的同时,对上一次直播录制的mp4文件进行编解码。上半屏使用相机录制视频,实现逻辑参考本节第二条直播端视频录制。下半屏点击加入连麦后,会对上次直播产生的视频文件进行编解码播放。
### 播放支持的原子能力规格
| 媒体格式 | 封装格式 | 码流格式 |
|------|:--------|:------------------------------------|
| 视频 | mp4 | 视频码流:H.264/H.265, 音频码流:AAC |
### 录制支持的原子能力规格
| 封装格式 | 视频编解码类型 | 音频编解码类型 |
|------|-------------|---------|
| mp4 | H.264/H.265 | AAC |
## 效果预览
| 应用主界面 | 直播页 | 看播页 | 加入连麦页 | 直播连麦页 |
|------------------------------------------------------------|-----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
|
|
|
|
|
|
## 使用说明
1. 在弹出是否允许“HMOSLiveConnect”访问你的相机后,点击“允许”。
2. 在弹出是否允许“HMOSLiveConnect”访问你的麦克风后,点击“允许”。
3. 在弹出是否允许“HMOSLiveConnect”发现和连接附近的设备后,点击“允许”。
### 开直播
1. 点击“开直播”。
2. 确认允许录制文件保存到分布式文件。
3. 录制完成后点击右上角的按钮关闭。
### 看直播
1. 在开直播结束后,可以进行看直播。
2. 点击“看直播”,即可进入直播播放页面。
### 看直播连麦
1. 在开直播结束后,可以进行直播连麦。
2. 点击“直播连麦”,即可进入直播连麦页面。
3. 进入页面后,点击下方加入连麦的按钮,会读取上次直播的录屏文件并渲染上屏,此时下半屏会循环播放上次的直播录屏。
## 工程目录
```
├──entry/src/main/cpp // Native层
│ ├──audioPlayer // 音频播放能力接口和实现
│ │ ├──include // 音频播放能力接口
│ │ │ ├──AudioPlayer.h // 音频播放接口
│ │ │ └──AudioPlayerNative.h // 音频播放入口
│ │ ├──AudioPlayer.cpp // 音频播放接口实现
│ │ └──AudioPlayerNative.cpp // 音频播放入口实现
│ ├──capbilities // 能力接口和实现
│ │ ├──codec // 音视频采集编解码
│ │ │ ├──include // 音视频采集编解码接口
│ │ │ │ ├──AudioBgmQueue.h // 音频bgm播放接口
│ │ │ │ ├──AudioCapturer.h // 音频采集接口
│ │ │ │ ├──AudioDecoder.h // 音频解码接口
│ │ │ │ ├──AudioEncoder.h // 音频编码接口
│ │ │ │ ├──AudioRender.h // 音频播放接口
│ │ │ │ ├──CodecCallback.h // 编解码回调接口
│ │ │ │ ├──CodecInfo.h // 编解码信息接口
│ │ │ │ ├──Demuxer.h // 解封装接口
│ │ │ │ ├──Muxer.h // 封装接口
│ │ │ │ ├──VideoDecoder.h // 视频解码接口
│ │ │ │ └──VideoEncoder.h // 视频编码接口
│ │ │ ├──AudioCapturer.cpp // 音频采集接口实现
│ │ │ ├──AudioDecoder.cpp // 音频解码接口实现
│ │ │ ├──AudioEncoder.cpp // 音频编码接口实现
│ │ │ ├──AudioRender.cpp // 音频播放接口实现
│ │ │ ├──CodecCallback.cpp // 编解码回调接口实现
│ │ │ ├──Demuxer.cpp // 解封装接口实现
│ │ │ ├──Muxer.cpp // 封装接口实现
│ │ │ ├──VideoDecoder.cpp // 视频解码接口实现
│ │ │ └──VideoEncoder.cpp // 视频编码入口实现
│ │ ├──render // 送显模块接口和实现
│ │ │ ├──include // 送显模块接口
│ │ │ │ ├──PluginManager.h // surface渲染管理接口
│ │ │ │ ├──PluginRender.h // surface渲染接口
│ │ │ │ ├──RecorderRender.h // 渲染线程接口
│ │ │ │ ├──RecorderRenderThread.h // EGL渲染上下文接口
│ │ │ │ ├──ShaderProgram.h // 封装 OpenGL ES 着色器程序的接口
│ │ │ │ ├──VideoRender.h // 视频渲染接口
│ │ │ │ └──VideoRenderThread.h // 视频渲染线程接口
│ │ │ ├──PluginManager.cpp // surface渲染管理接口实现
│ │ │ ├──PluginRender.cpp // surface渲染接口实现
│ │ │ ├──RecorderRender.cpp // 渲染线程接口实现
│ │ │ ├──RecorderRenderThread.cpp // EGL渲染上下文接口实现
│ │ │ ├──ShaderProgram.cpp // 封装 OpenGL ES 着色器程序的接口实现
│ │ │ ├──VideoRender.cpp // 视频渲染接口实现
│ │ │ └──VideoRenderThread.cpp // 视频渲染线程接口实现
│ ├──common // 公共模块
│ │ ├──dfx // 日志
│ │ │ ├──error // 错误日志
│ │ │ │ ├──AVCodecSampleError.h // 编解码错误日志
│ │ │ │ └──SampleError.h // 功能实现错误日志
│ │ │ ├──log // 打印日志
│ │ │ │ ├──AVCodecSampleLog.h // 编解码日志
│ │ │ │ └──SampleLog.h // 功能实现日志
│ │ ├──ApiCompatibility.h // API兼容性
│ │ ├──SampleCallback.cpp // 功能实现回调接口实现
│ │ ├──SampleCallback.h // 功能实现回调接口
│ │ └──SampleInfo.h // 功能实现公共类
│ ├──libboundcheck // 安全函数三方库
│ ├──recorder // Native层录制接口和实现
│ │ ├──include // Native层录制功能调用逻辑的实现
│ │ │ ├──Recorder.h // Native层录制功能调用逻辑的接口
│ │ │ └──RecorderNative.h // Native层 录制入口的接口
│ │ ├──Recorder.cpp // Native层录制功能调用逻辑的实现
│ │ └──RecorderNative.cpp // Native层 录制的入口
│ ├──types // Native层提供上来的接口
│ │ ├──libaudioplayer // 音频播放模块提供给UI层的接口
│ │ ├──librecorder // 录制模块提供给UI层的接口
│ │ └──libvideoplayer // 视频播放模块提供给UI层的接口
│ ├──videoPlayer // 音频播放能力接口和实现
│ │ ├──include // 音频播放能力接口
│ │ │ ├──videoPlayer.h // 视频播放接口
│ │ │ └──videoPlayerNative.h // 视频播放入口
│ │ ├──videoPlayer.cpp // 视频播放接口实现
│ │ └──VideoPlayerNative.cpp // 视频播放入口实现
│ └──CMakeLists.txt // 编译入口
├──ets // UI层
│ ├──common // 公共模块
│ │ ├──utils // 共用的工具类
│ │ │ ├──BackgroundTaskManager.ets // 后台任务工具类
│ │ │ ├──CameraCheck.ets // 检查相机参数是否支持
│ │ │ ├──DateTimeUtils.ets // 时间转换工具类
│ │ │ ├──ImageUtil.ets // 图片处理工具类
│ │ │ ├──Logger.ets // 日志工具
│ │ │ ├──PermissionUtil.ets // 权限校验工具
│ │ │ └──WindowUtil.ets // 窗口工具
│ │ ├──GlobalConstants.ets // 全局变量名称
│ │ └──CommonConstants.ets // 参数常量
│ ├──components // 组件目录
│ │ ├──SaveFileDialog.ets // 安全弹框组件
│ │ └──SettingPopupDialog.ets // 设置相关数据类
│ ├──controller // 控制器
│ │ ├──BgmController.ets // 背景音乐控制器
│ │ ├──CameraController.ets // 相机控制器
│ │ ├──DistributeFileManager.ets // 分布式文件管理器
│ │ ├──VideoPlayerController.ets // 本地音视频播放控制器
│ │ └──VideoSessionController.ets // 音频会话控制器
│ ├──entryability // 应用的入口
│ │ └──EntryAbility.ets
│ ├──entrybackupability
│ │ └──EntryBackupAbility.ets
│ ├──model
│ │ ├──CameraDataModel.ets // 相机参数数据类
│ │ └──SettingPopupOptionItem.ets // 设置的数据类
│ ├──pages // EntryAbility 包含的页面
│ │ ├──Index.ets // 首页
│ │ ├──StartConnectLiveStream.ets // 直播连麦页面
│ │ ├──StartLiveStream.ets // 直播端页面
│ │ └──WatchLiveStream.ets // 看播端页面
│ └──view // EntryAbility 包含的页面
│ ├──AvplayerView.ets // 看播端AvPlayer音视频播放
│ ├──StartLiveDecorationView.ets // 直播端数据页面
│ ├──StartLiveRenderView.ets // 直播端渲染器
│ └──WatchLiveDecorationView.ets // 看播端数据页面
├──resources // 存放应用的资源文件
└──module.json5 // 模块配置信息
```
## 具体实现
### 开直播
#### UI层
1. 在UI层Index页面,用户点击“开直播”后,确认保存录制文件到分布式文件夹下,将会创建一个新的文件。
2. 创建好文件后,会用刚刚创建文件的fd,和用户预设的录制参数,调用Native层的initNative()进行录制初始化,待初始化结束后,Native层会调用
OH_NativeWindow_GetSurfaceId接口,得到NativeWindow的surfaceId,并把surfaceId回调回UI层。
3. UI层拿到编码器给的surfaceId后,构造cameraController和bgmController,并调起页面路由,跳转到StartLiveStream页面。
4. StartLiveStream页面中的StartLiveRenderView组件的XComponent在构建时,会调起.onLoad()方法,此方法首先会拿到XComponent的surfaceId,
然后调起cameraController的createRecorder()和startNative(),此函数会建立一个相机生产,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轮转。
#### 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()创建两个专门用于输入和输出的线程。
### 看直播
#### UI层
1. 在UI层Index页面,用户点击看直播按钮后,触发点击事件,应用会将分布式目录下的视频文件拷贝到沙箱中,并选择最新的视频文件进行播放。
2. 将视频文件信息传递给AvplayerView,使用Avplayer完成视频的播放。
### 直播连麦
#### UI层
1. 在UI层Index页面,用户点击“直播连麦”后,确认保存录制文件到分布式文件夹下,将会创建一个新的文件。
2. 创建好文件后,会用刚刚创建文件的fd,和用户预设的录制参数,调用Native层的initNative()进行录制初始化,待初始化结束后,Native层会调用
OH_NativeWindow_GetSurfaceId接口,得到NativeWindow的surfaceId,并把surfaceId回调回UI层。
3. UI层拿到编码器给的surfaceId后,构造cameraController和bgmController,并调起页面路由,跳转到StartConnectLiveStream页面。
4. StartLiveStream页面中的StartLiveRenderView组件的XComponent在构建时,会调起.onLoad()方法,此方法首先会拿到XComponent的surfaceId,
然后调起cameraController的createRecorder()和startNative(),此函数会建立一个相机生产,XComponent和编码器的surface消费的生产消费模型。
5. 点击下方“直播连麦”后,会从分布式文件夹中获取最近一次的视频文件,进行解码播放。
6. 从而达到录制视频和编解码视频同时显示的场景。
#### 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轮转。
## 相关权限
- 允许应用使用相机:ohos.permission.CAMERA。
- 允许应用使用麦克风:ohos.permission.MICROPHONE。
- 运行应用使用分布式文件进行同步:ohos.permission.DISTRIBUTED_DATASYNC。
- 运行应用后台运行:ohos.permission.KEEP_BACKGROUND_RUNNING。
## 依赖
1. 引入libboundcheck安全函数三方库,拉取项目后需要执行git submodule update --init 加载子模块。示例代码如下:
```
git clone https://gitee.com/harmonyos_samples/HMOS_LiveAudioCall.git
git submodule update --init
```
## 约束与限制
1. 本示例仅支持标准系统上运行,支持设备:华为手机。
2. HarmonyOS系统:HarmonyOS 6.0.0 Release Release及以上。
3. DevEco Studio版本:DevEco Studio 6.0.0 Release及以上。
4. HarmonyOS SDK版本:HarmonyOS 6.0.0 Release SDK及以上。