knowledge-work-plugins 之 Zoom Video SDK for Windows 官方示例应用完全指南:20 个 Sample 选型、代码模式与构建实战
knowledge-work-plugins 之 Zoom Video SDK for Windows 官方示例应用完全指南:20 个 Sample 选型、代码模式与构建实战
本篇技术指南以 knowledge-work-plugins 仓库中 partner-built/zoom-plugin/skills/video-sdk/windows 技能库的官方示例参考文档为核心,系统讲解 Zoom Video SDK Windows 端(C++)官方示例工程的完整清单、推荐学习路径、关键代码模式与构建配置。读完本文,你将能够:根据业务需求(原始音视频采集、自定义音视频注入、云录制、信令、转写等)快速选定对应示例工程作为起点,理解贯穿所有示例的"单例获取 → 委托实现 → 订阅使用"通用架构,并掌握在 Visual Studio 中编译运行这些示例所需的全部前提与配置细节。
一、示例总览:20 个官方 Sample 一览
Zoom Video SDK Windows 官方示例仓库(videosdk-windows-rawdata-sample)提供了覆盖 SDK 主要能力的示例工程。下表汇总了全部示例的名称、功能定位与核心特性,是选型时的首要参考:
| 示例 | 描述 | 关键特性 |
|---|---|---|
| VSDK_SkeletonDemo | 最小化会话加入 | 最简单的入门起点 |
| VSDK_getRawVideo | 采集原始视频 | YUV420 帧提取 |
| VSDK_getRawAudio | 采集原始音频 | PCM 音频提取 |
| VSDK_getRawShare | 采集屏幕共享 | 共享内容捕获 |
| VSDK_sendRawVideo | 发送自定义视频 | 虚拟摄像头注入 |
| VSDK_sendRawAudio | 发送自定义音频 | 虚拟麦克风注入 |
| VSDK_sendRawShare | 发送自定义共享 | 自定义屏幕共享源 |
| VSDK_CloudRecording | 云录制 | 启停云端录制 |
| VSDK_CommandChannel | 自定义消息 | 收发自定义命令 |
| VSDK_CallIn | PSTN 呼入 | 电话拨入支持 |
| VSDK_Callout | PSTN 呼出 | 电话拨出支持 |
| VSDK_ServiceQuality | 网络统计 | 质量监控 |
| VSDK_TranscriptionAndTranslation | 实时转写 | 实时字幕 |
| VSDK_MultiStreamVideo | 多路视频流 | 多摄像头支持 |
| VSDK_PreviewCameraAndMicrophone | 设备预览 | 入会前设备测试 |
| VSDK_Share2ndCameraAsMultiCam | 第二摄像头 | 多摄像头共享 |
| VSDK_Share2ndCameraAsShareScreenDemo | 摄像头作为共享 | 摄像头内容共享 |
| VSDK_ShareScreenPreprocessorDemo | 共享预处理 | 自定义共享处理 |
| VSDK_RTMSDemo | 实时消息 | RTMS 集成 |
| VSDK_DuilibDemo2 | 完整 UI 演示 | 完整 GUI 应用 |
官方示例仓库与文档的链接信息可在技能库的 SKILL.md 与 windows.md 中查阅,本文聚焦于这些示例的本地化解读与代码模式。
从示例清单可以看出,官方示例覆盖了 SDK 的四大能力域:原始数据采集(Raw Data Capture)、原始数据注入(Raw Data Injection)、通信(Communication)与录制/流媒体/高级特性,下文第三节将按类别展开。
二、推荐学习路径:五个阶段吃透核心示例
官方建议的学习路径遵循"先跑通最小闭环,再逐项深入"的原则,与技能库中 concepts/sdk-architecture-pattern.md 所总结的"单例 → 委托 → 订阅"三段式架构一脉相承。
阶段 1:从 VSDK_SkeletonDemo 起步
这是代码量最小、最纯粹的会话加入示例,演示了以下 SDK 基础能力:
- SDK 初始化(
CreateZoomVideoSDKObj+initialize) - JWT 鉴权(
token字段) - 会话加入/离开(
joinSession/leaveSession) - Windows 消息循环(回调驱动的关键前提)
- 基础委托实现(
IZoomVideoSDKDelegate)
需要掌握的核心模式:
// 1. 创建 SDK
IZoomVideoSDK* sdk = CreateZoomVideoSDKObj();
// 2. 初始化
ZoomVideoSDKInitParams params;
params.domain = L"https://zoom.us";
sdk->initialize(params);
// 3. 添加委托
sdk->addListener(myDelegate);
// 4. 加入会话
ZoomVideoSDKSessionContext ctx;
ctx.sessionName = L"session";
ctx.token = L"jwt";
ctx.audioOption.connect = false;
sdk->joinSession(ctx);
// 5. 消息循环(关键!)
while (running) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(10);
}
为什么消息循环"关键"? 技能库中的 troubleshooting/windows-message-loop.md 明确指出:Zoom Video SDK 依赖 Windows 消息机制派发回调。事件发生时,SDK 将消息投递到调用 joinSession() 所在线程的消息队列;若没有 PeekMessage/GetMessage 驱动的消息循环,onSessionJoin()、onError() 等回调永远不会触发——表现为"joinSession() 返回成功但无任何回调"。因此:
- 消息循环必须与调用 SDK 方法的线程同一线程;
- 回调内禁止阻塞(不要
sleep或忙等); - GUI 应用(WinMain 标准消息循环)天然满足,控制台/自定义主循环必须显式补充。
阶段 2:视频采集 VSDK_getRawVideo
该示例演示如何捕获远端用户的原始 YUV420 视频帧,包含:
IZoomVideoSDKRawDataPipeDelegate接口实现onRawDataFrameReceived()回调- YUV 缓冲区提取(Y、U、V 三平面)
- 分辨率与旋转角处理
核心模式:
class VideoCapture : public IZoomVideoSDKRawDataPipeDelegate {
void onRawDataFrameReceived(YUVRawDataI420* data) override {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
char* yBuffer = data->GetYBuffer();
char* uBuffer = data->GetUBuffer();
char* vBuffer = data->GetVBuffer();
// Process frames...
}
};
// 订阅
IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe();
pipe->subscribe(ZoomVideoSDKResolution_720P, videoCapture);
该示例对应技能库 examples/raw-video-capture.md 中的完整实现:RawVideoCapture 类在 onRawDataFrameReceived 中计算 ySize = width * height、uvSize = ySize / 4,随后将三平面依次写入 .yuv 文件,并支持分辨率变化检测与进度日志。原始 YUV 文件无文件头,回放/转码需显式指定格式(详见下文"原始视频处理的进阶要点")。
阶段 3:音频采集 VSDK_getRawAudio
该示例演示 PCM 原始音频采集,覆盖三种回调模式:混合音频(全体参会人)、逐用户音频分离与共享音频。核心格式为 32 kHz 采样率、16-bit 有符号、单声道/双声道小端 PCM。
核心模式:
void onMixedAudioRawDataReceived(AudioRawData* data) override {
char* buffer = data->GetBuffer();
int length = data->GetBufferLen();
int sampleRate = data->GetSampleRate(); // 32000 Hz
int channels = data->GetChannelNum(); // 1 or 2
// Process PCM audio...
}
阶段 4:视频注入 VSDK_sendRawVideo
该示例将自定义视频作为虚拟摄像头发送,包含:
IZoomVideoSDKVideoSource接口实现- 通过
sendVideoFrame()发送帧 - YUV 帧创建
- 帧率控制
核心模式(流程): 自定义视频源(文件、摄像头或程序生成)→ 转换为 YUV420(I420)→ SDK 回调 onInitialize() 获取 IZoomVideoSDKVideoSender → 循环调用 sendVideoFrame() → 远端参会人看到自定义视频。完整实现见 examples/send-raw-video.md,其中 VideoSource 在 onStartSend() 中启动发送线程,按 1000 / fps 的帧间隔维护帧率,并通过 videoHelper->setExternalVideoSource(source) + videoHelper->startVideo() 注册启用——务必在设置外部源后调用 startVideo(),否则视频不会生效。
阶段 5:音频注入 VSDK_sendRawAudio
该示例将自定义音频作为虚拟麦克风发送,包含:
IZoomVideoSDKVirtualAudioMic接口实现- PCM 音频发送
- 采样率匹配
关键约束:SDK 要求精确的 32000 Hz 采样率(单声道、16-bit)。发送时应按 20ms 分块(32000 Hz 单声道下每块 640 采样 = 1280 字节),并使用 m_sender->send(buffer, BYTES_PER_CHUNK, SAMPLE_RATE) 发送,同时以 std::chrono::milliseconds(20) 精确控制节奏以保证音频不卡顿。完整实现见 examples/send-raw-audio.md,其 VirtualMic 同时支持从 .pcm 文件循环读取与程序生成 440 Hz 正弦波两种数据来源。
三、示例分类:按功能域选型
除按学习路径推进外,还可以按功能域快速定位示例:
原始数据采集(Raw Data Capture)
| 示例 | 输入 | 输出 |
|---|---|---|
| VSDK_getRawVideo | 远端视频 | YUV420 帧 |
| VSDK_getRawAudio | 会话音频 | PCM 采样 |
| VSDK_getRawShare | 屏幕共享 | YUV420 帧 |
原始数据注入(Raw Data Injection)
| 示例 | 输入 | 输出 |
|---|---|---|
| VSDK_sendRawVideo | YUV420 帧 | 虚拟摄像头 |
| VSDK_sendRawAudio | PCM 采样 | 虚拟麦克风 |
| VSDK_sendRawShare | YUV420 帧 | 屏幕共享 |
通信(Communication)
| 示例 | 特性 |
|---|---|
| VSDK_CommandChannel | 自定义命令消息(限 60 条/秒) |
| VSDK_CallIn | PSTN 电话拨入 |
| VSDK_Callout | PSTN 电话拨出 |
命令通道的 60 条/秒速率限制在 references/windows-reference.md 中亦有对应记载:
IZoomVideoSDKCmdChannel提供sendCommand(user, command)单发与sendCommandToAll(command)广播两种方法,收到命令时触发委托的onCommandReceived(sender, cmd)回调。
录制与流媒体(Recording & Streaming)
| 示例 | 特性 |
|---|---|
| VSDK_CloudRecording | Zoom 云端录制 |
| VSDK_RTMSDemo | 实时消息服务(RTMS) |
高级特性(Advanced Features)
| 示例 | 特性 |
|---|---|
| VSDK_ServiceQuality | 网络质量统计 |
| VSDK_TranscriptionAndTranslation | 实时字幕 |
| VSDK_MultiStreamVideo | 多路视频流 |
| VSDK_PreviewCameraAndMicrophone | 入会前设备预览 |
| VSDK_ShareScreenPreprocessorDemo | 自定义共享处理 |
四、跨示例通用模式:所有 Sample 共同遵循的代码骨架
无论实现哪个功能,所有官方示例都遵循同一套初始化与事件驱动骨架。这与 concepts/sdk-architecture-pattern.md 中的"万能三段式"完全一致:① 获取单例 → ② 实现委托 → ③ 订阅并使用。
1. 初始化模式
// 初始化 SDK
IZoomVideoSDK* sdk = CreateZoomVideoSDKObj();
ZoomVideoSDKInitParams params;
params.domain = L"https://zoom.us";
params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
sdk->initialize(params);
内存模式注意事项:技能库 SKILL.md 特别强调,原始数据的内存模式应始终使用堆模式(ZoomVideoSDKRawDataMemoryModeHeap),并同时为视频、共享、音频三路分别设置:
params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
堆模式(而非栈模式)可避免大尺寸视频帧导致的内存问题;此外还可通过 params.enableLog = true 与 params.logFilePrefix 开启日志以辅助排查。
2. 委托注册
必须始终在加入会话之前注册委托:
sdk->addListener(new MyDelegate());
sdk->joinSession(context);
若在 joinSession() 之后才调用 addListener(),将错过早期事件(examples/session-join-pattern.md 中明确标注此为错误用法)。同时注意:IZoomVideoSDKDelegate 拥有 70~80+ 个纯虚方法,全部必须实现(哪怕为空实现),否则会产生抽象类编译错误。
3. 音频连接策略
官方推荐"先入会、后连音频"的两段式:入会时设置 audioOption.connect = false,在 onSessionJoin() 回调中再调用 startAudio():
context.audioOption.connect = false; // 入会配置
void onSessionJoin() override {
sdk->getAudioHelper()->startAudio(); // 在此连接
}
该策略分离了会话加入与音频初始化,可提高可靠性并便于错误处理。同理,自己的摄像头应在 onSessionJoin 中通过 videoHelper->startVideo() 开启。
4. 消息循环
所有示例都包含消息循环,这是回调触发的必要前提(前文已详述):
while (!g_exit) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(10);
}
五、构建与配置
前置条件
- Visual Studio 2019 或 2022(安装"使用 C++ 的桌面开发"工作负载,含 MSVC v142/v143 编译器与 Windows 10/11 SDK)
- Windows SDK 10.0.19041.0 及以上
- Zoom Video SDK(从 Zoom Marketplace 下载)
构建步骤
- 打开解决方案文件(
.sln) - 将平台设置为 x64
- 将配置设置为 Release
- 生成解决方案(Ctrl+Shift+B)
- 将 SDK DLL 拷贝到输出目录
技能库 SKILL.md 补充了运行时环境的基线:Windows 10(1903 或更高)或 Windows 11,支持 x64(推荐)、x86 与 ARM64 架构;若使用 C#/.NET 应用,还需 .NET Framework 4.8 及以上与 C++/CLI 支持。
配置文件
每个示例通过 config.json 提供运行参数:
{
"jwt": "your-jwt-token",
"session_name": "test-session",
"password": "",
"user_name": "Bot"
}
在 examples/session-join-pattern.md 的完整实现中,LoadConfig() 会读取该 JSON(使用 jsoncpp),将 jwt、session_name、password、user_name(默认 "Bot")映射为全局宽字符串,再填入 ZoomVideoSDKSessionContext 完成入会。若文件无法打开或 SDK 初始化失败,程序会立即退出并返回错误码。
六、进阶深化:示例背后的 SDK 实现细节
理解示例代码后,可进一步结合技能库参考文档深入底层实现。
6.1 五层 API 层级与"导航式"访问
references/windows-reference.md 将 SDK 建模为五层深的单例对象树,所有功能都通过"导航"而非"构造"获取:
- Level 1 入口:
CreateZoomVideoSDKObj() → IZoomVideoSDK*,提供initialize、joinSession、addListener、getVideoHelper、getAudioHelper、getShareHelper、getChatHelper、getCmdChannel、getRecordingHelper等入口; - Level 2 核心 Helper 与会话:
IZoomVideoSDKSession(getMyself、getRemoteUsers)、IZoomVideoSDKVideoHelper(仅控制自己的摄像头)、IZoomVideoSDKAudioHelper、IZoomVideoSDKShareHelper; - Level 3 用户与渲染对象:
IZoomVideoSDKUser(GetVideoCanvas、GetVideoPipe、getVideoStatus)、IZoomVideoSDKCanvas(SDK 渲染)、IZoomVideoSDKRawDataPipe(原始 YUV)、IZoomVideoSDKShareAction(共享控制); - Level 4 设备/聊天/回调:
IZoomVideoSDKCameraDevice、IZoomVideoSDKChatHelper、IZoomVideoSDKRawDataPipeDelegate; - Level 5 原始数据与工具:
YUVRawDataI420、AudioRawData、IZoomVideoSDKUserHelper、IZoomVideoSDKCmdChannel等。
关键区分:VideoHelper/AudioHelper 只控制自己的音视频流(startVideo() 开启自己的摄像头);观看远端参会人的视频,必须通过其 user->GetVideoCanvas()->subscribeWithView(hwnd, aspect, resolution) 或 user->GetVideoPipe()->subscribe(resolution, delegate) 订阅。屏幕共享则更特殊:远端共享必须使用 onUserShareStatusChanged 回调中的 IZoomVideoSDKShareAction(而非 user->GetShareCanvas()),因为一个用户可能同时存在多个共享流。
6.2 两种渲染/处理路线:Canvas API 与 Raw Data Pipe
| 路线 | 接口 | 适用场景 |
|---|---|---|
| Canvas API | IZoomVideoSDKCanvas::subscribeWithView(HWND) |
标准应用、画质最佳、实现最简单 |
| Raw Data Pipe | IZoomVideoSDKRawDataPipe::subscribe(delegate) |
自定义处理、特效、录制、计算机视觉 |
Canvas API 由 SDK 直接渲染到窗口句柄,无需 YUV 转换,支持 ZoomVideoSDKVideoAspect_Original / FullFilled / PanAndScan / LetterBox 等纵横比与 ZoomVideoSDKResolution_90P / 180P / 360P / 720P / 1080P / Auto 等分辨率选项;Raw Data Pipe 则向委托回调交付 YUVRawDataI420 帧,交由应用自行转换渲染(GDI/DirectX/OpenGL 均可)。
6.3 原始视频处理的进阶要点(对应 VSDK_getRawVideo)
examples/raw-video-capture.md 给出了 YUV420(I420 平面)的内存布局:Y 平面为全分辨率(width × height 字节),U/V 平面各为四分之一分辨率((width/2) × (height/2) 字节),单帧总大小为 width × height × 1.5 字节:
| 分辨率 | Y 缓冲区 | U 缓冲区 | V 缓冲区 | 总计 |
|---|---|---|---|---|
| 720p(1280×720) | 921,600 | 230,400 | 230,400 | 1,382,400 |
| 1080p(1920×1080) | 2,073,600 | 518,400 | 518,400 | 3,110,400 |
| 360p(640×360) | 230,400 | 57,600 | 57,600 | 345,600 |
YUVRawDataI420 的主要方法包括 GetYBuffer/GetUBuffer/GetVBuffer(三平面指针)、GetStreamWidth/GetStreamHeight(帧尺寸)、GetRotation(0/90/180/270)、GetTimeStamp(时间戳)以及 CanAddRef/AddRef/Release(引用计数,用于跨回调异步处理)。采集到的 .yuv 文件可用 FFplay 回放或 FFmpeg 转码:
ffplay -video_size 1280x720 -pixel_format yuv420p -f rawvideo video.yuv
ffmpeg -video_size 1280x720 -pixel_format yuv420p -framerate 30 -f rawvideo -i video.yuv -c:v libx264 output.mp4
性能优化建议:预分配 RGB 缓冲区(仅尺寸变化时重分配);回调内使用 AddRef() 将帧投递到独立处理线程,处理完 Release();AI 处理订阅低分辨率(ZoomVideoSDKResolution_360P),显示则用高分辨率 Canvas。
6.4 原始音频采集格式(对应 VSDK_getRawAudio)
examples/raw-audio-capture.md 明确音频格式为:PCM 未压缩、32000 Hz、16-bit 有符号、单声道或双声道、小端序,每次回调约 640~1280 字节(20~40ms 音频)。三种回调分别对应:
onMixedAudioRawDataReceived:全体参会人混合音频,适合整场录制;onOneWayAudioRawDataReceived:逐用户音频(需特殊配置),适合按发言人转写;onSharedAudioRawDataReceived:屏幕共享内容音频,与参会人音频分离。
AudioRawData 提供 GetBuffer()(PCM 缓冲区)、GetBufferLen()(字节数)、GetSampleRate()(通常 32000)与 GetChannelNum()(1=单声道,2=双声道)。采集到的 .pcm 可用 FFplay 回放或转为 WAV:
ffplay -f s16le -ar 32000 -ac 1 audio.pcm
ffmpeg -f s16le -ar 32000 -ac 1 -i audio.pcm output.wav
常见坑位:音频回调不触发(未在 onSessionJoin 连接音频)、FFmpeg 中错误使用 44100 Hz、静音(无人发言或全员静音,需检查 onUserAudioStatusChanged)。
6.5 订阅时机与错误码(调试示例的关键依据)
references/windows-reference.md 强调两条时序铁律:
- 不要在
onUserJoin中订阅视频——此时视频可能尚未就绪,会触发 Error 2(Internal_Error)。正确做法是在onUserVideoStatusChanged中检查pipe->getVideoStatus().isOn为真后再订阅,并跳过自己(if (user == myself) continue;); - 订阅过快会触发
ZoomVideoSDKSubscribeFailReason_TooFrequentCall(=6),处理方式是在相邻订阅调用之间插入Sleep(200)。
常用错误码速查:0 Success、1 Wrong_Usage(错误状态调用)、2 Internal_Error(视频未就绪/过早订阅)、7 Invalid_Parameter(NULL 指针、非法 HWND)、8 Call_Too_Frequently(调用过于频繁)。
6.6 线程安全与生命周期
SDK 回调运行在 SDK 线程而非主线程:不要在回调中执行重操作、不要在回调中调用 cleanup()、跨线程传递数据使用线程安全队列(如 YUVRawDataI420 的引用计数配合生产者-消费者队列)并加锁保护共享状态。退会时统一在 onUserLeave / onSessionLeave 中解除订阅并清理,最后按 leaveSession(false) → cleanup() → DestroyZoomVideoSDKObj() 的顺序释放。
七、相关文档导航
本文对应的示例参考文档位于 references/samples.md,技能库中与之配套的深度资料还包括:
- SDK 架构模式:贯穿所有功能的"单例 → 委托 → 订阅"万能三段式
- 会话加入模式:完整的 JWT 鉴权 + 入会代码(含
main.cpp与config.json) - 原始视频采集:YUV 捕获完整实现与 FFmpeg 处理
- 原始音频采集:PCM 捕获完整实现与 FFplay 处理
- 发送原始视频 / 发送原始音频:虚拟摄像头/虚拟麦克风注入实现
- API 参考:五层 API 层级、方法签名、错误码与时序规则
- 委托方法:全部回调方法
- Windows 消息循环:回调不触发的第一排查项
- 常见问题:快速诊断清单
行动建议:以 VSDK_SkeletonDemo 打通"初始化 → 委托 → 入会 → 消息循环"的最小闭环,再根据业务诉求选择 VSDK_getRawVideo 或 VSDK_getRawAudio 切入原始数据采集;若需要向会话注入内容,则对应研读 VSDK_sendRawVideo(虚拟摄像头)与 VSDK_sendRawAudio(虚拟麦克风)。任何示例运行异常,优先检查消息循环是否存在、委托是否在入会前注册、订阅是否发生在视频就绪之后。