WebRTC-IOT:嵌入式设备接入实时音视频的C++实践
先说个题外话。这两年总有人问我:WebRTC 这么火,能不能把它用到嵌入式设备上,让摄像头、门铃、小车直接浏览器播放?每次听到我都先反问一句:你打算用 libwebrtc 官方的 SDK 编译进你那个 64MB 内存的板子吗?对方通常愣一下,然后我们就聊到了这套 WebRTC-IOT。简单说,它就是用现代 C++(C++17)重新组织 WebRTC 能力,面向物联网和嵌入式设备做减法,把 WebRTC 的实时音视频和数据通道能力塞进资源受限的硬件里。这篇文章我把整个项目从设计到落地掰开揉碎讲清楚,适合正在做物联网音视频方案、嵌入式接流开发、或者想给设备加实时通信能力的同学,也适合想入门 WebRTC 但不想被整套源码劝退的 C++ 开发者。
1. 项目背景与要解决的核心痛点
1.1 嵌入式设备接入 WebRTC 为什么这么难
WebRTC 在浏览器里的表现大家都熟悉,无需安装插件就能点对点视频通话,延迟能压到几百毫秒。但这个“浏览器级”的体验背后,是 Chrome 团队维护的一整套庞然大物 libwebrtc。这套库有多夸张?编译产物动辄几十甚至上百 MB,依赖一大堆平台相关的优化指令集,运行时内存峰值能轻松突破几百 MB,对 CPU 的消耗也不小。这种体量放到桌面和移动端没问题,但放到物联网设备上就是不现实,很多设备 Flash 总共才几 MB,内存更是按 KB 级来算的。
另一个尴尬的点是:嵌入式设备传统的视频方案,比如 RTSP、RTMP、私有协议推流,在浏览器里基本不能直接播放。套 HLS 又绕回延迟问题,视频到用户手里已经过去好几秒。用户就是想在看门铃画面的时候能立刻看到屋里人,而不是转菊花转半天。WebRTC 的低延迟和浏览器原生支持正好是答案,问题只在于怎么把技术栈做轻。
1.2 WebRTC-IOT 的设计目标与整体解决思路
WebRTC-IOT 就是冲着这个矛盾去的。立项目标很朴素:保留 WebRTC 的核心通信能力,把体积、内存、CPU 占用全部砍到嵌入式设备能接受的范围。具体来说是做了三件事。
第一,模块化裁剪。libwebrtc 本身是单仓库多模块结构,并不是所有代码都需要。WebRTC-IOT 在构建层面把不必要的依赖剥掉,按需编译视频编解码、音频处理、P2P 打洞这些模块,让静态库体积压到几 MB 级别。第二,用现代 C++ 做接口层。原本 WebRTC 的 C++ API 非常繁琐,涉及大量 observe、callback、线程模型,对嵌入式开发者很不友好。这个项目把常用场景封装成干净的 C++ 接口,用智能指针管理生命周期,用回调函数传递事件,开发体验直线上升。第三,提供硬件编解码适配层。嵌入式设备普遍带有硬件编解码单元,比如 V4L2 M2M、OpenMAX IL、VAAPI 这些,直接用硬编硬解能把 CPU 占用降一个数量级,这是桌面版 WebRTC 根本不用考虑的维度。
解决思路对应到选型上就是:协议栈还是要跟 WebRTC 标准兼容,信令流程复用,ICE 打洞、DTLS 加密、SRTP 传输这些能力必须保留,因为它们是 WebRTC 低延迟和安全性的根基。但底层实现可以大量裁剪和替换,硬编硬解接进来,网络传输可以适配不同板子的网卡能力,弱网策略也可以按场景调。
2. 整体架构与方案选型解析
2.1 模块划分与裁剪策略
拆开看这套库的架构,其实和标准 WebRTC 的分层很相似。我这里画一个大致的模块分布,方便说明:
| 层次 | 模块 | 裁剪策略 | 说明 |
|---|---|---|---|
| 应用层 | 信令封装、业务回调 | 保留核心封装,去掉不必要的扩展 | 开发者接触最多的部分 |
| 传输层 | ICE、DTLS、SRTP、SCTP | 完整保留,适配嵌入式网络 | 保证 P2P 连接和加密能力 |
| 媒体层 | 采集、编码、解码、渲染 | 编解码可切换硬软方案 | 硬件加速是关键差异点 |
| 系统层 | 线程、内存、日志 | 精简线程模型,日志可关闭 | 控制运行时开销 |
应用层是 WebRTC-IOT 的价值点,它没有让开发者直接面对 SDP 和 ICE candidate 这些底层概念,而是提供类似 addVideoTrack、onRemoteStream 这种语义清晰的接口。传输层则基本复用了 WebRTC 的成熟实现,因为这一块涉及时序、状态机、重传策略,很难自己造轮子,也不太应该造。音频处理模块做了取舍,标准的降噪、回声消除逻辑对于很多嵌入式设备来说太复杂了,特别是在一些只有单向音频收发的场景里,直接绕过这些模块能省不少 CPU。视频编码这边默认优先走 H.264,因为硬件支持最普遍,且浏览器解码兼容性最好,VP8 作为软编码备选。
2.2 为什么坚持用现代 C++ 而不是 C 或 Rust
做嵌入式库的人,很容易陷入“必须用 C 才能轻量”的惯性思维。但 WebRTC-IOT 选择 C++17 是有实际考量的。首先是生态优势,WebRTC 本身就是 C++ 写的,直接复用底层实现就不用做跨语言绑定,维护成本低。C++ 的 RAII 机制特别适合处理音视频这种大量资源密集型的场景,一个 PeerConnection 对象析构时,所有 session、buffer、socket 都能自动释放,不会像 C 风格代码那样漏掉某个 free,在长时间运行的设备上这是致命的。
其次是可读性和安全性。你可以想象一下用 C 去写 ICE 连接状态机,每个回调都要传 userdata 指针,一旦回调里有人把类型转换写错,崩溃排查会非常痛苦。C++ 的强类型、shared_ptr、std::function 可以极大减少这类问题。我实测过在裸机上用 C 写类似功能,代码量至少比 C++ 多三倍,而且 bug 密度更高。至于 Rust,虽然内存安全更好,但 WebRTC 底层的 C++ 实现已经经过多年打磨,重写风险太大,短期内并不现实。所以最终选择了 C++17 作为实现语言,既控制了复杂度,又保留了足够性能。
2.3 信令与数据通道的嵌入式适配
信令在 WebRTC 里不属于规范强制内容,开发者可以自由设计。WebRTC-IOT 提供了一套默认的 JSON over WebSocket 信令实现,方便设备端直接接入现有的物联网云平台。如果不想用 WebSocket,它也暴露了 SignalHandler 接口,用户可以用 MQTT、CoAP 甚至是串口来传 SDP offer/answer 和 ICE candidate,这对嵌入式场景特别重要,因为很多设备压根没有完整的 TCP/IP 网络栈。数据通道这块也是重点,不只是传视频,设备控制指令、传感器数据都能通过 DataChannel 在点对点连接里实时流转,省去了走服务器中转的延迟和带宽。
3. 核心 API 实操与代码走读
3.1 初始化与会话配置
先看最常用的一个流程:设备端初始化 WebRTC-IOT 库,创建一个 peer 会话,然后等待远端浏览器来连接。这套 API 的用法和标准 WebRTC 对齐,但简化了不少细节。
#include <webrtc-iot/webrtc_iot.h>
using namespace webrtc_iot;
int main() {
// 初始化全局资源,内部会准备线程池和加密库
WebRtcIoT::Init();
// 创建配置,设置信令服务器地址、ICE服务器
PeerConfig config;
config.signal_server = "wss://iot.example.com/signal";
config.ice_servers = {{"stun:stun.example.com:3478"}};
config.is_audio_enabled = false; // 纯视频,省资源
config.is_video_enabled = true;
config.video_codec = VideoCodec::H264; // 优先硬编
// 创建 PeerConnection,传入回调
auto peer = CreatePeerConnection(config);
peer->OnIceStateChanged([](IceState state) {
// 处理连接状态变化
});
// 启动连接,进入信令协商流程
peer->Start();
// 保持主线程存活
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
这里面最需要注意的是
config.is_audio_enabled
和
config.video_codec
这两个配置,很多设备 CPU 能力很弱,如果音频视频全都开软编解码,跑起来非常吃力。我一般建议纯视频场景直接关音频,或者用 G.711 这种低复杂度编码,而视频默认走 H.264 硬编。
3.2 音视频推流与远端观看
设备端通常扮演发送者角色,把摄像头画面推给浏览器端观看。代码上需要把采集到的画面喂给编码器,再交给 peer 对象发送。WebRTC-IOT 封装了采集和输入的接口,可以直接从 V4L2 获取原始帧数据。
// 创建视频轨道,设置分辨率和帧率
auto video_track = peer->AddVideoTrack("camera1", 640, 480, 30);
// 从摄像头读取一帧 YUV 数据
uint8_t* yuv_frame = CaptureV4L2Frame();
// 送入编码器;内部自动完成硬编和 RTP 打包
video_track->PushFrame(yuv_frame, 640 * 480 * 3 / 2);
这里有个很容易忽略的点:
PushFrame
的入参格式要和编码器期望的保持一致。很多摄像头直接输出 MJPG 或 NV12,如果编码器期望的是 I420,就必须在送入之前做格式转换,否则出来的画面会有色偏或者花屏。WebRTC-IOT 内部有像素格式转换的辅助函数,但转换本身有 CPU 开销,能直接输出 NV12 的摄像头就尽量用 NV12,省一次转换。480p @ 30fps 的软编在常见 ARM 板子上已经能压掉不少 CPU,如果硬编可用,这个损耗会降到很低。
对于浏览器观看者,设备端不需要额外做什么,SDP 协商结束后,浏览器会自动收到流并播放。我这里顺手写个前端用 WebRTC 接收流的伪代码,给做联调的同学参考:
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.example.com:3478' }] });
pc.ontrack = (event) => {
document.getElementById('video').srcObject = event.streams[0];
};
// 发送 offer 给设备端,由设备端调 Answer API
跑通这个流程你就拥有了一个“设备推流、浏览器观看”的最小闭环,后续再补信令和房间管理就非常顺手了。
3.3 订阅远端流与双工数据通信
设备端不一定总是发送方,有时候也需要接收浏览器端的流,比如双向对讲场景。WebRTC-IOT 提供了 onRemoteStream 回调来接收远端媒体流。更常用的反而是 DataChannel,因为它可以承载任意二进制数据,非常适合设备控制信号和传感器状态上报。
// 监听远端视频流
peer->OnRemoteStream([](std::shared_ptr<MediaStream> stream) {
auto video = stream->GetVideoTracks().front();
video->OnFrame([](const Frame& frame) {
// 这里拿到远端传来的视频帧,可以做人脸检测或存储
});
});
// 绑定数据通道接收事件
peer->OnDataMessage([](const std::string& label, const uint8_t* data, size_t len) {
// 解析收到的控制指令,比如云台转动、报警触发
HandleControlCommand(data, len);
});
// 发送数据给对端
std::string payload = "alert";
peer->SendData("control", reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
这套 API 对嵌入式开发非常友好,回调模型统一,不用维护多线程状态。数据通道在低带宽场景下也很高效,信令和业务数据可以共用一条 P2P 链路,比每次命令都走 MQTT 中转实时性强太多了。
4. 交叉编译与嵌入式平台移植实战
4.1 用 CMake 工具链完成交叉编译
WebRTC-IOT 的构建系统基于 CMake,交叉编译思路和其它库一样:准备一个工具链文件,指定编译器、系统根目录和目标平台架构,然后执行 CMake 配置。
# arm-linux.toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_SYSROOT /opt/arm-sysroot)
set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
配置和编译命令:
mkdir build && cd build
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../arm-linux.toolchain.cmake \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DWEBRTC_ENABLE_AUDIO=OFF \
-DWEBRTC_ENABLE_VIDEO=ON \
-DWEBRTC_ENABLE_HW_CODEC=ON
make -j4
编译产物包括 libwebrtc_iot.a 静态库和几个示例程序。MinSizeRel 的优化选项对体积很关键,实测某些场景下体积能比默认 Release 再小 20% 左右。WEBRTC_ENABLE_AUDIO 这个开关在纯视频设备上建议关掉,能省掉音频采集、回声消除、降噪一整条链路,库体积和运行时内存都会明显下降。
4.2 硬件编解码的接入方式与适配逻辑
硬件编解码是嵌入式 WebRTC 性能的核心。目前大多数 ARM SoC 提供两种主流接口:一种是 Linux 标准的 V4L2 Memory-to-Memory 接口,另一种是各芯片厂商私有的 API,比如 Rockchip MPP、Allwinner V56。WebRTC-IOT 的设计是抽象出一个 VideoCodec 接口,底层可以注册不同的硬件实现。
// 通过工厂注册硬件编解码器
HardwareCodecRegistry::Register("v4l2_h264_enc", []() {
return std::make_shared<V4L2H264Encoder>();
});
接入新板子时,只需要按接口约定写一个 Encoder/Decoder 实现,并在编译时通过宏切换。这个设计虽然简单,但非常实用。我建议在接入硬编时特别关注四点:输入缓冲区的对齐要求、帧率不匹配时的丢帧策略、关键帧请求的处理时机、编码器输出 SPS/PPS 的注入位置。SPS/PPS 没处理好是很多人在 WebRTC 里画面发灰、解码失败的常见原因,尤其在硬编里,SPS/PPS 可能不是每个 IDR 帧都带,需要单独触发一次关键帧请求。
软编这边也别完全忽视。VP8 软编在低端设备上跑 CIF(352×288)分辨率还是可行的,所以 WebRTC-IOT 保留了软编后端作为硬编的 fallback。设备有硬编能力就优先硬编,没有就降低分辨率用软编,这是比较稳妥的降级策略。
4.3 内存与性能优化的几个关键笔记
做嵌入式优化,最怕拍脑袋。老实讲我早期做这类项目时也踩过不少坑,这里写几个比较通用的经验和方向,供参考。
零拷贝很关键。摄像头采集到的数据通常经由 DMA 放入内存,如果每一帧都做 memcpy,带宽开销很可观。WebRTC-IOT 的 Frame 对象设计成引用计数方式,编码器输入、RTP 打包只持有帧引用,不再复制整块数据。这个点处理好了,内存占用能降一半以上。
缓冲池也非常管用。视频编码器和网络发送模块会频繁申请释放 buffer,建议在初始化阶段一次性申请复用池,按最大分辨率和参考帧数量估算。为什么这么做?因为嵌入式系统内存碎片化严重,长时间跑容易出现申请不到连续内存的情况,缓冲池能规避这类问题。我在实际批量部署时碰到的设备掉线率降低,跟缓冲池关系很大。
带宽自适应这块,WebRTC 标准里叫拥塞控制,但嵌入式设备的网络环境和桌面差异很大。默认配置检测到 RTT 升高会快速降码率,对室内 WiFi 稳定场景也许偏保守,可以调整相关参数,比如放宽 RTT 阈值、拉长降码率步进。需要注意的是,如果想要固定码率传输,建议把分辨率设成目标值,然后让编码器在波动范围内自适应。同分辨率下,将帧率从 30 降到 15,通常也能省下不少编码耗时。
5. 常见问题排查与避坑实录
5.1 编译环境相关的高频问题速查
我整理了一份常见问题表,覆盖了我自己和不少使用者踩过的坑,可以先对照自查。
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 报错 Microsoft Visual C++ 14.0 is required | Windows 开发机上编译依赖工具链版本过低 | 更新至 Visual Studio 2019/2022 Build Tools,并安装“使用 C++ 的桌面开发”组件 |
| CMake 找不到 OpenSSL 或其他依赖 | 交叉编译工具链的 sysroot 中没有对应库 | 在 sysroot 中补装 libssl-dev,或将依赖编译为静态库后指定路径 |
| 链接时大量 undefined reference | 未按顺序链接依赖库,或硬件编码器模块未开启 | 调整库链接顺序,检查 WEBRTC_ENABLE_HW_CODEC 开关 |
| V4L2 摄像头打开失败 | 设备节点权限不够或格式不受支持 | 检查 /dev/video0 权限,使用 v4l2-ctl 查询支持格式并匹配 |
| 远端画面只有首帧 | SPS/PPS 未正确处理 | 强制编码器在关键帧输出完整参数集,或在收到关键帧前不解码 |
这里额外说一句 Visual C++ 14.0 的问题。很多同学在 Windows 上开发设备端辅助工具时,会先把 WebRTC-IOT 拉下来直接编译,结果 Python 或 CMake 的某个步骤报错提示缺少 MSVC 14.0。这不是库本身的问题,而是系统缺一套完整 C++ 构建工具链,安装 Build Tools 后通常能解决。嵌入式 Linux 为主的话,建议在 Linux 容器里做交叉编译,省心得多。
5.2 运行期连不上、花屏、卡顿的排查逻辑
运行期问题千奇百怪,但绝大多数可以按照连接建立和媒体传输两条线去查。
连接建立阶段最典型的问题是 SDP 协商失败。两边协商的编解码格式、ICE candidate 类型不匹配,就可能导致一直连不上。排查的时候,先把 ICE 状态日志打开,看 candidate 是否已经收集到,有没有收到对端 candidate,然后抓包看 STUN binding 请求和响应是否正常。如果信令走了不通的服务器,也要检查 WebSocket 是否被防火墙拦截。
媒体传输阶段的问题最好从端到端链路看。画面花屏,大概率是丢包或 SPS/PPS 处理不当;画面卡顿,先看编码耗时和网络 RTT。码率自适应生效时会主动降质量,这时候卡顿和模糊是正常表现,但如果帧率也掉得厉害,就要怀疑 CPU 编码能力瓶颈了。我一般会同时开三个指标观察:编码帧率、发送码率、丢包率。三者放在一起基本能定位瓶颈在采集、编码还是网络。
调试工具上,Wireshark 的 RTP/RTCP 过滤器和 Chrome 的 webrtc-internals 页面都非常好用。设备端日志可以先看 WebRTC-IOT 自带的 verbose 日志,重点看 ICE 状态机切换和 SRTP 错误计数、NACK 重传请求是否异常多。
还有一个隐蔽的坑:有些低端设备系统时钟不准,或者 RTC 电池失效导致时间偏了几小时,这会直接影响 DTLS 证书有效性校验和 STUN 请求。连接一直失败、但日志看起来都正常的时候,先对一下设备时间,这是我从多次现场故障里总结出来的经验。
5.3 性能调优的实战心得
调优这块我的建议是“先砍需求,再上手段”。很多项目一开始就要求 1080p 双路视频加双向音频,这种配置在地面硬件上没问题,但在嵌入式设备上根本跑不动。根据我的测试经验,常见的 RK3288、全志 V3s 这类平台,比较稳妥的组合是 720p @ 20fps H.264 硬编,纯视频,不加音频处理。单路视频场景下,CPU 占用能控制在 20% 左右,内存占用约 80MB 左右,包含整个 WebRTC 协议栈。如果你要做双路视频,建议第二路用 CIF 分辨率,码率压到 300kbps,适合做预览或检测。
其次,线程数要克制。libwebrtc 默认会创建一堆线程,在嵌入式平台上会造成上下文切换开销。WebRTC-IOT 默认精简为三个线程:主线程跑信令和业务回调,媒体线程跑采集和编码,网络线程跑 ICE/SRTP。这样在四核板子上基本不会互相干扰。如果设备是单核,建议把媒体和网络线程也合并,牺牲一点并发性,换来稳定的调度延迟。
日志系统在生产环境要关闭或降到 error 级别,这点很多人忽略。吞吐量大的时候,printf 家族会卡住 CPU,导致编码超时和丢帧,性能表现突然变差有时真不是算法问题,就是日志刷屏了。
6. 从设备到业务:典型应用场景落地分析
6.1 边缘网关:从 RTSP 摄像头到 WebRTC 播放
现实中有大量存量摄像头是 RTSP 协议的,直接改造成本高、周期长。用 WebRTC-IOT 做边缘网关是很务实的方案:网关一边拉取 RTSP 流,解码成原始帧,另一边用 WebRTC-IOT 编码推给浏览器。这样用户仍然可以在网页里实时观看旧摄像头画面,不用装插件,也不用 ffmpeg 转 HLS 忍受高延迟。
网关设备性能通常比纯传感器设备好一些,跑 1080p 解码和 720p 编码的转码链路问题不大。实现时需要注意音视频同步,RTSP 拉流和 WebRTC 推流的时间基准不一样,要把 PTS 对齐到 WebRTC 的时间戳基准上,否则音画不同步会很影响体验。
6.2 智能家居、智慧出行、智慧零售与智慧物流中的设备形态
WebRTC-IOT 这类库最典型的落地场景,就是“给硬件加上实时眼睛”。智能家居里的可视门铃、宠物摄像头,室内场景 WiFi 环境相对稳定,720p 硬编加 DataChannel 下发云台控制指令,体验比传统 RTSP 方案好得多。智慧出行场景里,车载终端可以用它把行车画面实时传给远程调度中心,后装 OBD 盒子也能通过热点把视频推给手机。智慧零售场景中,门店摄像头数量多、网络情况复杂,低码率自适应和 P2P 打洞能力很有价值,总部远程巡店不再依赖专线。智慧物流里的 AGV 小车和巡检机器人,更是可以把视频流和控制通道统一到一条 WebRTC 连接里,减少端侧通信模块数量。
这些场景看上去差异大,但核心诉求是一样的:设备端尽量省资源,连接尽量直接,用户端打开浏览器就能看到实时画面。WebRTC-IOT 在这类项目里扮演的角色,其实就是把 WebRTC 协议栈变成一个“设备外设”,让硬件厂商不用重复造轮子。
6.3 与前端 Vue/小程序项目的配合
设备端就绪后,客户端开发通常落在 Web 前端。常规 Vue 项目里接入 WebRTC 播放,和浏览器原生的规范保持一致,前端维护一个 RTCPeerConnection,处理 offer/answer 交换后绑定 video 标签即可。关键是信令协议要和设备端约定一致,比如谁先发起 offer、candidate 怎么转发、断线重连逻辑。小程序端稍微特殊一点,因为小程序不是完整浏览器环境,WebRTC 支持有限,但 WebView 场景或部分小程序插件已经能兼容,这点需要单独验证,不能拿浏览器方案直接套。
7. 扩展思路:让这个项目走得更远
如果只是把推拉流跑通,WebRTC-IOT 还只发挥了一半潜力。我认为后续值得探索的两个方向,一是把 DataChannel 利用起来做远程诊断和运维通道,设备在线、实时状态、固件版本查询全走这条链路,运维体验会好很多;二是和 AI 推理结合,在设备端做简单的运动检测或者人脸框,事件触发后再把精确视频流推给用户,对功耗和带宽都很友好。再往后,把多设备的 Mesh 组网做起来,也能覆盖一些集中监控的小场景。
这套库的前景建立在“WebRTC 兼容 + 嵌入式资源可控”的交叉点上,只要设备端和 Web 端各司其职,整个链路的开发效率会比传统的 RTSP 转码方案高不少。
最后分享一个经验:建议你拿到这个库之后别急着看源码,先跑一遍官方示例,用树莓派或者一台 Linux 虚拟机当作设备端,Chrome 当作观看端,把 P2P 链路拉通。这个闭环走通后,你对 ICE 打洞、SDP 协商、硬编硬解全链路会有一个完整的体感。之后再根据自己板子的情况交叉编译、调整参数,就能少走很多我在前期踩过的弯路了。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)