1. “面对面”视频通话App,为什么最难的选库在拉流
今年做HarmonyOS应用开发的人应该都有同感:UI布局、状态管理、生命周期这些老本行技能在ArkTS里基本能平滑迁移,真正让人卡壳的往往是音视频这条链路。标题里说“视频通话App,需要用到这个库”,这个“库”字其实点破了很多人的痛点——不是不会写界面,而是不知道在鸿蒙生态里,视频通话的采集、编码、传输、解码、渲染到底该依赖什么。我最初也以为走系统AVPlayer加CameraKit就能拼出来,结果通话质量惨不忍睹,后来彻底换了思路才算跑通。
先说结论:在HarmonyOS NEXT的纯血版本上,目前没有一套官方开箱即用的“视频通话全家桶”SDK。你需要组合使用系统级能力(CameraKit、AudioKit、AVCodec)配合一个可靠的传输通道方案(比如WebRTC的鸿蒙适配版本),或者直接接入商业化的音视频SDK(声网、腾讯云TRTC、即构等)。这个“库”不是一个,而是一组。
这篇文章我会按我实际调通的路径,把“面对面”视频通话App从选库到集成的完整链路拆开讲清楚。重点回答三个问题:到底该用开源WebRTC方案还是商业SDK?鸿蒙上的音视频权限和硬件适配有哪些坑?从零到能通话的Demo需要多少行代码、卡在哪里?适合正在做鸿蒙音视频应用、被库选型折磨的开发者和产品经理参考。
需要说明的是,如果原文或者你手上拿到的资料是关于某个具体SDK的使用教程,我这里的通用链路同样适用——因为任何SDK的底层逻辑都是“采集-编码-传输-解码-渲染”,只要把这条链路上的鸿蒙特有点数清楚,换哪个库都不会慌。
2. 前置判断:开源WebRTC和商业SDK,到底怎么选
2.1 先看你是哪种团队
视频通话的“库选择”第一关不是技术选型,而是团队资源判断。很多团队一上来就搜“HarmonyOS 视频通话 SDK”,结果被一堆商业方案的价格吓退,转头去搞开源WebRTC,最后卡在信令服务和STUN/TURN服务器搭建上,一卡就是两周。
我帮你把选择条件拆成三组,依次对号入座:
- 团队有C++/Native层经验和足够时间,希望完全掌控媒体链路、不想付License费用:选WebRTC的鸿蒙适配版(如华为官方维护的ohos_webrtc,或移植libwebrtc M98左右版本),自己搭信令服务和TURN转发。
- 团队以ArkTS/应用层开发为主,希望在几天内跑通高质量"面对面"通话:选商业SDK,声网(Agora)、腾讯云TRTC、即构(Zego)都有明确的鸿蒙适配,集成后可以直接用其音视频房间能力。
- 团队是个人开发者或做原型验证:优先选带免费额度的商业SDK(声网每月有免费时长,即构也有体验套餐),或者直接先用系统能力做局域网内POC(下面会细说)。
这个判断几乎决定了你后续所有的技术路径,也决定了这篇博客后面的阅读重点。我实际遇到太多人一上来就写WebRTC的SDP交换,写完发现自己的服务器带宽撑不住多人通话,又折回来换商业方案,白白浪费一周。
2.2 商业SDK在鸿蒙上的成熟度差异
很多人误以为HarmonyOS NEXT刚出来时,三方SDK一片空白。实际在2024年底到2025年,主流的几个音视频服务商已经完成了对鸿蒙NEXT的适配,但适配深度并不一样。我整理了一张对比表,方便你判断:
| SDK | 鸿蒙适配方式 | 核心优势 | 主要顾虑 |
|---|---|---|---|
| 声网 Agora RTC | 鸿蒙NEXT专属SDK包 | 全球节点多、文档齐全、音频3A处理成熟 | 有免费额度,但商用后按时长计费 |
| 腾讯云 TRTC | 鸿蒙NEXT SDK 适配中 | 和腾讯云生态打通好,直播场景方案丰富 | 个别接口还没完全兼容元服务 |
| 即构 ZEGO | 有独立鸿蒙SDK | 弱网优化口碑好,小团队对接响应快 | 需要主动找销售开权限 |
| 开源 ohos_webrtc | 华为贡献/社区维护 | 免费、可控、深度可定制 | 信令自建、没有商业SLA保证 |
我个人的倾向是:如果你的应用未来要出海,或者用户网络环境复杂,优先看看声网和即构这类在弱网对抗上有积累的方案;如果应用只服务国内且你本来就在用腾讯云,TRTC更顺滑。开源方案我更推荐作为学习手段,而不是直接上生产。
2.3 一个容易被忽略的选型维度:元服务还是原生App
在鸿蒙生态里,还有一个比较特殊的考量:你的视频通话功能是跑在原生App里,还是做成元服务(Atomic Service)里的某个模块。如果是元服务,包体积限制、权限弹窗方式、前后台切换策略都和原生App不同,部分SDK的体积优化和剪裁能力就对集成结果影响很大。
从我踩过的坑来说,元服务场景下尽量选提供“插件化/按需加载”能力的SDK,不要把整个音视频引擎一次性编进元服务包,否则启动速度和审核体验都会被拖累。原生App则可以更从容地集成完整版SDK,把音频前后处理、视频美颜等模块全部打开。
3. 不得不懂的底层逻辑:一通视频电话的数据流和鸿蒙适配点
不管选什么库,底层这条数据链路是绕不开的。我用最简单的方式拆一下“面对面”视频通话背后的数据流,你在配置库和排查问题时都会更有底。
3.1 从摄像头到屏幕的六步链路
视频通话的最小闭环可以拆成六个环节:
- 采集:CameraKit从摄像头拿到原始帧(YUV/NV12或RGBA),麦克风通过AudioKit拿到PCM音频数据
- 前处理:采集到的图像可能要做旋转、镜像、美颜、降噪;音频要做回声消除(AEC)、噪声抑制(NS)、自动增益(AGC)
- 编码:视频用H.264/H.265编码压缩,音频用OPUS或AAC编码,压缩的目的是降低网络带宽占用
- 传输:编码后的数据打包成RTP包,通过UDP/TCP传输,信令通道负责建立连接、交换SDP、控制通话状态
- 接收端解码:对方设备收到数据包后按序重组,用AVCodec做硬解
- 渲染:解码后的视频帧通过XComponent(Surface模式)渲染到屏幕上,音频通过AudioRenderer播放,同时要考虑音画同步
在这个链路里,HarmonyOS和其他平台最大的区别在于渲染通道和权限模型。鸿蒙上视频画面渲染通常要绑定XComponent,通过getSurfaceId()拿到Surface,再把Surface ID传给底层拉流/解码模块。这一点如果没有提前设计好架构,后面很容易被卡住。
3.2 鸿蒙权限模型是第一个大坑
在Android上,摄像头和麦克风权限就是两个<uses-permission>加运行时弹窗。在HarmonyOS里则要区分“权限声明”和“权限使用理由”,而且在HarmonyOS NEXT上,敏感权限默认是“仅使用时允许”。如果App退到后台后还想继续通话,你会遇到前台服务(ContinuousTask)和权限联动的组合限制。
这是我在第一篇Demo时就踩上的坑:App切后台后,音频采集被系统挂起,视频画面也直接黑屏。如果不能接受这种体验,你需要申请ohos.permission.KEEP_BACKGROUND_RUNNING,还要在module.json5里配置对应的后台模式类型为audioPlayback或audioRecording,并接入系统ContinuousTask机制。这些内容普通SDK的快速集成文档里很少主动提,不踩一次真的不会注意。
3.3 硬编硬解与Surface的配合
HarmonyOS上视频编解码首选系统AVCodec的硬件编解码器。很多SDK底层会自己管理Codec,或者暴露一个“是否启用硬件编码”的开关。如果你用的开源方案,需要手动确认:
- 编码器支持的分辨率、帧率、码率范围
- Surface输入模式还是Buffer输入模式(ByteBuffer模式适合自定义前处理,Surface模式性能更好)
- 某些老机型对H.265硬编支持不全,必要时回退H.264
我实测的场景是:鸿蒙中端机(比如搭载麒麟8000系列芯片的)在720P、30fps、1.5Mbps码率下H.264硬编完全没问题,但同样参数开启H.265硬编会出现偶发花屏——后面确认是编码器初始化时没设置正确的profile和level导致。视频编码里“看着能跑”和“稳定可用”之间的距离,往往就是这些配置项。
4. 实操起步:基于系统能力的最低成本验证方案
如果你用的是商业SDK,官方Demo通常已经帮你解决了90%的链路。但如果你想彻底理解原理,或者现阶段还没决定选型,可以先不引入第三方库,用系统能力搭一个最简的本地预览验证。这个验证的意义在于:先用最小成本确认硬件和权限链路没问题,再去接网络传输。
4.1 本地视频预览:验证CameraKit和XComponent
第一步最简单:启动相机预览,把画面渲染到XComponent上。代码骨架大致是这样:
// 页面中创建XComponent,并获取surfaceId private xComponentController: XComponentController = new XComponentController(); build() { Column() { XComponent({ id: 'cameraPreview', type: XComponentType.SURFACE, controller: this.xComponentController }) .onLoad(() => { let surfaceId = this.xComponentController.getXComponentSurfaceId(); this.startCameraPreview(surfaceId); }) } } // 使用CameraKit创建CameraInput和VideoOutput async startCameraPreview(surfaceId: string) { let cameraManager = camera.getCameraManager(getContext(this)); let cameras = cameraManager.getSupportedCameras(); let cameraDevice = cameras[0]; // 优先后置 let session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.CameraSession; let cameraInput = cameraManager.createCameraInput(cameraDevice); await cameraInput.open(); let videoOutput = session.createVideoOutput({ size: { width: 1280, height: 720 }, surfaceId: surfaceId }); session.beginConfig(); session.addInput(cameraInput); session.addOutput(videoOutput); await session.commitConfig(); await session.start(); }这段代码的關鍵点是:createVideoOutput时传入的surfaceId必须和XComponent拿到的Surface ID一致,而且XComponent的onLoad回调里执行初始化最安全。整个过程如果出错,90%是因为缺少相机权限或XComponent类型写错(注意要用XComponentType.SURFACE)。
4.2 音频采集验证:AudioKit的回声消除测试
光看画面还不够,音视频通话里音频比视频更容易翻车。你用AudioKit创建AudioCapturer时,需要主动打开回声消除和噪声抑制开关。在鸿蒙上,AudioCapturerOptions里可以配置audioScene为AUDIO_SCENE_CALL或AUDIO_SCENE_DEFAULT,但注意不是所有模拟器都支持这些场景,最好在真机上验证。
如果使用商业SDK或WebRTC适配库,通常在初始化引擎时会有enableAudioProcessing或AEC开关,开启后系统音频模块会被SDK接管。我从实际体验看,仅在应用层做AEC是徒劳的——回声消除必须在“采集→播放”的闭环里做全局处理,最简单可靠的办法是交给SDK内置的音频模块,而不是自己在业务层写算法。
4.3 模拟器、真机和一些“想当然”的预期管理
鸿蒙开发中一个很实际的提醒:模拟器(尤其是API 12以下的模拟器)对CameraKit和XComponent的支持并不完整。我曾在模拟器里跑本地预览,画面一直黑,折腾了半天发现模拟器根本不支持createVideoOutput走Surface模式。这类问题不是你的代码错,而是环境边界,早一点切真机调试能省很多时间。
另外一个预期管理是:不要指望本地预览的延迟低于20ms。从摄像头采集到Surface渲染,本身就有系统管线开销,只要延迟稳定、不卡顿,就是正常的。真正需要关注的是网络传输后的端到端延迟,那个才是通话体验的决定因素。
5. 信令与媒体链路:商业SDK帮你省掉的,恰好是很多人最恐惧的
很多人看到“信令”两个字就发怵。这里我先帮你拆个明白:如果你选商业SDK,信令大多不需要自己写。SDK会帮你处理房间管理、用户加入/离开、媒体流的订阅关系;你只需要调用joinChannel/leaveChannel,并把Token校验逻辑接入自己的业务服务端。
5.1 一个典型流程:Token模式下的加入房间
以声网为例(几乎主流SDK都是这个模式,只是函数命名和参数稍有不同),加入视频通话房间的流程是:
- 在服务端生成临时的Token(需要App ID、Channel Name、User ID、过期时间)
- 客户端向自己的业务服务端请求Token
- 客户端用
RTCEngine.joinChannel(token, channelName, uid)加入房间 - 远端用户同样加入该房间后,通过
onUserJoined回调拿到远端UID - 调用
setupRemoteVideo(uid, surfaceId)把远端画面渲染到指定Surface
这段流程里最值得下功夫的不是SDK调用本身,而是Token的生成与缓存策略。很多开发者图省事让客户端直接配置固定Token,这在测试阶段没什么,但一旦正式上线,Token泄漏意味着任何人都能冒名进入频道。正确做法是Token有效期设短一点(比如2-4小时),并在客户端做自动续期,或者在切入后台时预判可能发生的过期。
5.2 你仍然需要关心一个自建服务:业务鉴权
就算用了商业SDK,你通常还是需要一个自己的后端服务。因为Token需要签名,签名密钥不能放在客户端。虽然这不算“信令服务”,但它决定了谁能进频道、能进哪个频道,对商业化产品来说必不可少。
如果你选的是纯开源WebRTC方案,那信令服务就得全部自己搭。简单的可以用WebSocket中转JSON消息(Offer/Answer/ICE Candidate),复杂一点要设计房间管理、掉线重连、媒体协商状态机。很多团队在这里被消耗掉大量时间——所以我的观点始终是,快速产品验证优先走商业SDK,开源方案当作进阶挑战或深度定制手段。
5.3 面对“面对面”场景:前后摄切换与多人通话是否必须
“面对面”视频通话这个词,在App场景里有两个可能的需求走向:一是1v1亲密通话,二是小型线上见面会。两者的技术复杂度差异很大。
1v1的情况下,你只需要管理两路流(本地+远端),加上前/后摄像头切换即可。多人情况下,还得考虑:
- 每个远端用户一路下行流,码率和渲染性能如何平衡(通常限制同时渲染4-6路,其余按需拉流)
- 音频混音策略(麦克风被多人打断的体验怎么优化)
- 布局和翻页逻辑(Speaker切换、画面大小变化)
如果你做的是超越1v1的“面对面”场景,在选型时就要确认SDK对“大小流”的支持能力。大流给当前说话人或主画面,小流给缩略图瓦片。商业SDK普遍支持,开源自建则需要自己实现选择性转发(SFU)逻辑,这对团队挑战非常大。
6. 采集编码与渲染的质量调优:从“能通”到“能看”的分水岭
跑通Demo只是开始。我在实际做“面对面”视频通话App时的体感是:Demo能通大概花两天,画面质量调到“愿意长期用”大概花了两周。质量调优通常是整篇文章最吃经验的地方,下面按关键项拆开讲。
6.1 分辨率、帧率、码率的三角平衡
视频通话中,码率(bitrate)、分辨率(resolution)、帧率(fps)构成铁三角。很多人有一个误解:码率越高画质一定越好,分辨率越高体验一定越好。实际上,在带宽有限的情况下,分辨率过高会让画面变成“PPT视频”,帧率过高会让码率全部消耗在时间冗余上,画质反而模糊。
我给自己项目的推荐配置:
| 网络场景 | 分辨率 | 帧率 | 码率区间 | 适用场景 |
|---|---|---|---|---|
| 室内Wi-Fi / 5G | 720P | 30fps | 1.2-1.8Mbps | 高质量通话主力档 |
| 4G中等信号 | 540P | 24fps | 600-900Kbps | 平衡画质和流畅 |
| 弱网 | 360P | 15fps | 250-400Kbps | 保连接不中断 |
商业SDK通常会自动做带宽估计(拥塞控制),在弱网下自动降码率、降分辨率。开源方案则需要你自己实现或至少留出setVideoEncoderConfiguration的动态修改入口,最简单的做法是根据onNetworkQuality回调里的等级,动态切换两到三档编码配置。
6.2 音频的优先级:回声消除比降噪更重要
在做视频通话App时,很多人会先关注视频画质,但我建议音频优先。理由很简单:视频卡顿还能忍受,声音断续和回声啸叫会直接让人挂电话。
HarmonyOS上音频采集的配置项里,sampleRate(通常用48000Hz)、channelCount(双声道)、sampleFormat(S16LE)要正确匹配;编码端OPUS在48kHz双声道下工作效果最好。AEC的质量取决于采集和播放是否走同一个音频模块——这也是为什么商业SDK“接管”音频会更容易调好,因为它在引擎层把采集和播放路径统一管理了。
6.3 Surface渲染的刷新率与首帧策略
视频远端画面的渲染,和本地预览一样要绑定XComponent。但远端画面有个“首帧时间”的体验问题:用户点“接听”后,多久能看到对方的脸?优化方向有三点:
- 让接收端在收到第一个关键帧(IDR帧)后立刻渲染,而不是等缓冲足够多再起播
- 编码端在用户加入房间时主动发一次关键帧请求(甚至每秒强制一次IDR),帮助新加入者快速出图
- 在等待远端画面时展示本地预览或等待动画,降低黑屏焦虑
我在调优时踩过一个具体问题:某些鸿蒙版本上,远端视频输出到Surface后第一次渲染会花屏或闪一下。排查到最后是XComponent的onLoad时机和SDK内部初始化顺序冲突,解决方法是延迟200-300毫秒再传入Surface ID,给底层组件一个稳定周期。
6.4 弱网对抗:能不能让通话“听得清、看得见”
弱网下最痛苦的往往不是丢包,而是延迟和抖动的叠加。商业SDK普遍有NetEQ(音频抗抖动缓冲)和视频JitterBuffer,你几乎不用自己操心。开源方案需要自己处理:
- 音频:缓存一定时长的音频包,遇到乱序和抖动时平滑播放,但代价是增大延迟——你要找一个最小可用缓冲值
- 视频:对关键帧和参考帧做不同丢包策略,关键帧优先重传(NACK),参考帧丢了直接丢弃
如果你的目标是做出稳定可用的产品,且团队没有专门从事音视频的工程师,我不建议完全自研这部分。选一个有长期积累的SDK,大于自己从零维护两套平台逻辑的成本。
7. 实战排坑、经验与后续扩展
7.1 三个高频“坑”的排查路径
坑一:加入房间后画面只有黑屏。
排查链路:先看本地预览是否正常——如果本地画面OK,说明CameraKit链路没问题;再看远端有没有收到OnUserJoined回调——如果回调没触发,问题是Token或房间名不一致;如果回调触发了但画面黑,检查setupRemoteVideo里传的surfaceId和XComponent是否在正确的页面层级里。
坑二:声音有回声或刺耳。
排查链路:确认是否开了AEC和AGC;确认扬声器模式和听筒模式切换是否被SDK正确感知;检查有没有把采集到的音频直接播放(俗称“监听”),误开成“耳返”模式。我曾经在调试时把enableLocalAudio(false)写成了true,导致本地麦克风的数据被回放,整整排查了一下午。
坑三:App切后台后音频断掉。
排查链路:确认已申请ohos.permission.KEEP_BACKGROUND_RUNNING;确认module.json5里配置了对应的后台模式;确认接入ContinuousTask,在通话时启动前台服务;如果你用商业SDK,还要看SDK内部是否默认允许后台音频模式,有些SDK需要额外调用enableBackgroundAudio()。
7.2 从“能通”到“出品”的经验建议
如果你急着完成一个可Demo、可演示的版本,我建议按这个顺序推进:
- 第一天:申请商业SDK的AppID,跑通官方鸿蒙Demo(一家跑不通就换另一家)
- 第二天:把Demo代码迁移到自己的项目结构里,用官方UI或自定义UI接好XComponent
- 第三天:接入自己的业务后端,实现Token下发和登录逻辑
- 第四到第六天:调通前后摄像头切换、小窗模式、通话时长统计
- 第七天以后:专门做真机兼容性测试,优先拿中低端机和早期鸿蒙版本做回归
在集成SDK时,有一个容易被忽略的工程细节:HarmonyOS NEXT的模块编译产物只支持真机调测,不支持PC模拟器里做音视频全链路验证。因此团队最好常备一两台不同芯片的真机(麒麟和骁龙平台),否则很多硬件相关的问题根本测不出来。
7.3 后续扩展:录屏、美颜和AI降噪
当基础通话稳定后,你大概率会想做这些增强能力:
- 录制:商业SDK都提供
startRecording或addVideoWatermark,但也需要在生命周期里处理录像中断 - 美颜:可以接SDK内置的美颜接口,也可以自己玩一下Image Kit的滤镜能力
- AI降噪:HarmonyOS的智能音频接口已经可以接第三方算法,但性能开销要重点衡量
这些扩展功能千万不要一拥而上。先把音频AEC和弱网表现调稳,再考虑画面增强——因为对用户来说,“听得清、不断线”永远是第一需求。
7.4 我的一点实际体会
说实话,HarmonyOS音视频开发现在处在一个“能做,但要细心”的阶段。系统的CameraKit、AudioKit、AVCodec能力已经相当完整,三方SDK的鸿蒙适配也在快速补齐,但也正因为生态还没像安卓那么“卷”,很多坑都只能自己趟。
如果你正在规划这个项目,我最大的建议是:选型阶段别只看技术博客上的功能对比,直接注册几家SDK试一遍真机Demo,哪个跑得通、哪个问题少就用哪个,这比看一百篇对比文都管用。等Demo通了、链路稳了,再回头研究底层也不迟。
最后再分享一个小技巧:不管选哪个方案,都尽早把通话过程中的关键日志(音视频码率、延迟、丢包率、CPU占用)记录下来。后续做质量优化和问题排查时,这些日志是最有用的“现场证人”。我自己后来整理了一套标准排查SOP,很多线上问题靠日志能直接定位到具体环节,省下了大量和用户反复沟通的时间。