Zoom Meeting SDK Windows
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Software development
- MIT
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Zoom Meeting SDK for Windows provides a native C++ interface for embedding Zoom functionality directly into desktop applications. It supports a wide range of features including custom UI rendering, headless bot operations, and raw audio/video stream access. The SDK relies on a controller-listener architecture, requiring developers to implement specific event interfaces to handle meeting lifecycle events and data streams. Proper integration requires careful management of the Windows message loop to ensure callbacks are processed correctly.
What it does
- Embeds full Zoom meeting functionality into native Windows C++ applications.
- Supports headless bot development for automated meeting participation.
- Provides access to raw audio and video data streams for custom processing.
- Enables custom UI development using child HWNDs or direct rendering.
- Facilitates local recording and advanced meeting control features.
- Requires implementation of event listeners for all feature controllers.
When to use it
- When building a native Windows desktop application that requires integrated video conferencing.
- When developing automated bots that need to join and interact with Zoom meetings.
- When you need low-level access to raw audio/video frames for custom analysis or rendering.
- When you require full control over the meeting UI rather than using the default Zoom interface.
Advantages
- Native C++ performance for resource-intensive media processing.
- Deep integration with Windows message loops and desktop UI frameworks.
- Consistent architecture pattern across all 35+ available feature controllers.
- Flexible options for both standard and fully custom UI implementations.
Usage examples
- “How do I initialize the Zoom SDK in my Windows C++ application?”
- The user receives the InitParam structure configuration and the call to InitSDK, including necessary header includes and error handling.
- “Why are my Zoom SDK callbacks not firing?”
- The user is guided to implement the Windows message loop using PeekMessage and DispatchMessage to ensure the SDK can process asynchronous events.
- “How can I capture raw video from a meeting?”
- The user is shown how to use IMeetingRecordingController to start raw recording and IZoomSDKRenderer to subscribe to video frames.
- “What is the recommended project structure for a Zoom SDK app?”
- The user receives a directory layout separating SDK binaries, headers, and libraries, along with instructions for configuring Visual Studio project properties.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/zoom-meeting-sdk-windows/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip zoom-meeting-sdk-windows.zip -d .agents/skills
Documentation by Zoom
Zoom Meeting SDK (Windows)
Embed Zoom meeting capabilities into Windows desktop applications for native C++ integrations and headless bots.
New to Zoom SDK? Start Here!
The fastest way to master the SDK:
SDK Architecture Pattern - Learn the universal pattern that works for ALL 35+ features
Authentication Pattern - Get a working bot joining meetings
Windows Message Loop - Fix the #1 reason callbacks don't fire
Building a Custom UI?
Custom UI Architecture - How SDK rendering actually works (child HWNDs, D3D, etc.)
Custom UI Video Rendering Example - Complete working code
SDK-Rendered vs Self-Rendered - Choose the right approach
Custom UI Interface Methods - All 13 required virtual methods
Having issues?
Build errors → Build Errors Guide
Callbacks not firing → Windows Message Loop
Quick diagnostics → Common Issues
Performance / service quality → service-quality.md
Deployment notes → deployment.md
MSBuild from git bash → Build Errors Guide
Complete navigation → SKILL.md
Prerequisites
Zoom app with Meeting SDK credentials (Client ID & Secret)
Visual Studio 2019/2022 or later
Windows 10 or later
C++ development environment
vcpkg for dependency management
Need help with authentication? See the zoom-oauth skill for JWT token generation.
Project Preferences & Learnings
IMPORTANT: These are hard-won preferences from real project experience. Follow these when creating new projects.
Do NOT use CMake — Use native Visual Studio .vcxproj
Always create a native Visual Studio .sln + .vcxproj project, not a CMake project. Reasons:
More standard and familiar for Windows C++ developers
Developers can double-click the
.slnto open in Visual Studio immediatelyProject settings (include dirs, lib dirs, preprocessor defines) are easier to see and edit in the VS Property Pages UI
No extra CMake tooling or configuration step required
Friendlier and easier for developers to understand and maintain
config.json must be visible in Solution Explorer
The config.json file (containing sdk_jwt, meeting_number, passcode) must be:
Included in the
.vcxprojas a<None>item with<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>Placed in a "Config" filter in the
.vcxproj.filtersfile so it appears under a "Config" folder in Solution ExplorerEasily editable by developers directly from Solution Explorer — they should never have to hunt for it in File Explorer
Example .vcxproj entry:
<ItemGroup>
<None Include="config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>Example .vcxproj.filters entry:
<ItemGroup>
<Filter Include="Config">
<UniqueIdentifier>{GUID-HERE}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="config.json">
<Filter>Config</Filter>
</None>
</ItemGroup>Overview
The Windows SDK is a C++ native SDK designed for:
Desktop applications - Native Windows apps with full UI control
Headless bots - Join meetings without UI
Raw media access - Capture/send audio/video streams
Local recording - Record meetings locally or to cloud
Key Architectural Insight
The SDK follows a universal 3-step pattern for every feature:
Get controller (singleton):
meetingService->Get[Feature]Controller()Implement event listener:
class MyListener : public I[Feature]Event { ... }Register and use:
controller->SetEvent(listener)then call methods
This works for ALL features: audio, video, chat, recording, participants, screen sharing, breakout rooms, webinars, Q&A, polling, whiteboard, and 20+ more!
Learn more: SDK Architecture Pattern Guide
Quick Start
1. Download Windows SDK
Download from Zoom Marketplace:
Extract
zoom-meeting-sdk-windows_x86_64-{version}.zip
2. Setup Project Structure
your-project/
YourApp/
SDK/
x64/
bin/ # DLL files and dependencies
h/ # Header files
lib/ # sdk.lib
x86/
bin/
h/
lib/
YourApp.cpp
YourApp.vcxproj
config.jsonCopy SDK files:
xcopy /E /I sdk-package\x64 your-project\YourApp\SDK\x64\
xcopy /E /I sdk-package\x86 your-project\YourApp\SDK\x86\3. Install Dependencies (vcpkg)
# Install vcpkg
git clone https://github.com/Microsoft/vcpkg.git C:\vcpkg
cd C:\vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
# Install dependencies
.\vcpkg install jsoncpp:x64-windows
.\vcpkg install curl:x64-windows4. Configure Visual Studio Project
Project Properties → C/C++ → General → Additional Include Directories:
$(SolutionDir)SDK\$(PlatformTarget)\h
C:\vcpkg\packages\jsoncpp_x64-windows\include
C:\vcpkg\packages\curl_x64-windows\includeProject Properties → Linker → General → Additional Library Directories:
$(SolutionDir)SDK\$(PlatformTarget)\libProject Properties → Linker → Input → Additional Dependencies:
sdk.libPost-Build Event (Copy DLLs to output):
xcopy /Y /D "$(SolutionDir)SDK\$(PlatformTarget)\bin\*.*" "$(OutDir)"5. Configure Credentials
Create config.json:
{
"sdk_jwt": "YOUR_JWT_TOKEN",
"meeting_number": "1234567890",
"passcode": "password123",
"zak": ""
}6. Build & Run
Open solution in Visual Studio
Select x64 or x86 configuration
Press F5 to build and run
Core Workflow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ InitSDK │───►│ AuthSDK │───►│ JoinMeeting │───►│ Raw Data │
│ │ │ (JWT) │ │ │ │ Subscribe │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
OnAuthComplete onInMeeting
callback callback⚠️ CRITICAL: Add Windows message loop or callbacks won't fire!
while (!done) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}See: Windows Message Loop Guide
Code Examples
💡 Pro Tip: These are minimal examples. For complete, tested code see:
Authentication Pattern - Full auth workflow
Raw Video Capture - Complete video capture
SDK Architecture Pattern - Implement any feature
Important: Include Order
CRITICAL: Include headers in this exact order or you'll get build errors:
#include <windows.h> // MUST be first
#include <cstdint> // MUST be second (SDK headers use uint32_t)
// ... other standard headers ...
#include <zoom_sdk.h>
#include <meeting_service_components/meeting_audio_interface.h> // BEFORE participants!
#include <meeting_service_components/meeting_participants_ctrl_interface.h>See: Build Errors Guide for all dependency fixes.
1. Initialize SDK
#include <windows.h>
#include <cstdint>
#include <zoom_sdk.h>
using namespace ZOOM_SDK_NAMESPACE;
bool InitMeetingSDK() {
InitParam initParam;
initParam.strWebDomain = L"https://zoom.us";
initParam.strSupportUrl = L"https://zoom.us";
initParam.emLanguageID = LANGUAGE_English;
initParam.enableLogByDefault = true;
initParam.enableGenerateDump = true;
SDKError err = InitSDK(initParam);
if (err != SDKERR_SUCCESS) {
std::wcout << L"InitSDK failed: " << err << std::endl;
return false;
}
return true;
}2. Authenticate with JWT
#include <windows.h>
#include <cstdint>
#include <auth_service_interface.h>
#include "AuthServiceEventListener.h"
IAuthService* authService = nullptr;
void OnAuthenticationComplete() {
std::cout << "Authentication successful!" << std::endl;
JoinMeeting(); // Proceed to join meeting
}
bool AuthenticateSDK(const std::wstring& jwtToken) {
CreateAuthService(&authService);
if (!authService) return false;
// Set event listener BEFORE calling SDKAuth
authService->SetEvent(new AuthServiceEventListener(&OnAuthenticationComplete));
// Authenticate with JWT
AuthContext authContext;
authContext.jwt_token = jwtToken.c_str();
SDKError err = authService->SDKAuth(authContext);
if (err != SDKERR_SUCCESS) {
std::wcout << L"SDKAuth failed: " << err << std::endl;
return false;
}
// CRITICAL: Add message loop or callback won't fire!
// See complete example: examples/authentication-pattern.md
return true;
}AuthServiceEventListener.h:
#include <windows.h>
#include <cstdint>
#include <auth_service_interface.h>
#include <iostream>
using namespace ZOOM_SDK_NAMESPACE;
class AuthServiceEventListener : public IAuthServiceEvent {
public:
AuthServiceEventListener(void (*onComplete)())
: onAuthComplete(onComplete) {}
void onAuthenticationReturn(AuthResult ret) override {
if (ret == AUTHRET_SUCCESS && onAuthComplete) {
onAuthComplete();
} else {
std::cout << "Auth failed: " << ret << std::endl;
}
}
// Must implement ALL pure virtual methods (6 total)
void onLoginReturnWithReason(LOGINSTATUS ret, IAccountInfo* info, LoginFailReason reason) override {}
void onLogout() override {}
void onZoomIdentityExpired() override {}
void onZoomAuthIdentityExpired() override {}
#if defined(WIN32)
void onNotificationServiceStatus(SDKNotificationServiceStatus status, SDKNotificationServiceError error) override {}
#endif
private:
void (*onAuthComplete)();
};See complete working code: Authentication Pattern Guide
3. Join Meeting
#include <meeting_service_interface.h>
#include "MeetingServiceEventListener.h"
IMeetingService* meetingService = nullptr;
void OnMeetingJoined() {
std::cout << "Joining meeting..." << std::endl;
}
void OnInMeeting() {
std::cout << "In meeting now!" << std::endl;
// Start raw data capture here
}
void OnMeetingEnds() {
std::cout << "Meeting ended" << std::endl;
}
bool JoinMeeting(UINT64 meetingNumber, const std::wstring& password) {
CreateMeetingService(&meetingService);
if (!meetingService) return false;
// Set event listener
meetingService->SetEvent(
new MeetingServiceEventListener(&OnMeetingJoined, &OnMeetingEnds, &OnInMeeting)
);
// Prepare join parameters
JoinParam joinParam;
joinParam.userType = SDK_UT_WITHOUT_LOGIN;
JoinParam4WithoutLogin& params = joinParam.param.withoutloginuserJoin;
params.meetingNumber = meetingNumber;
params.userName = L"Bot User";
params.psw = password.c_str();
params.isVideoOff = false;
params.isAudioOff = false;
SDKError err = meetingService->Join(joinParam);
if (err != SDKERR_SUCCESS) {
std::wcout << L"Join failed: " << err << std::endl;
return false;
}
return true;
}MeetingServiceEventListener.h:
#include <windows.h>
#include <cstdint>
#include <meeting_service_interface.h>
#include <iostream>
using namespace ZOOM_SDK_NAMESPACE;
class MeetingServiceEventListener : public IMeetingServiceEvent {
public:
MeetingServiceEventListener(
void (*onJoined)(),
void (*onEnded)(),
void (*onInMeeting)()
) : onMeetingJoined(onJoined),
onMeetingEnded(onEnded),
onInMeetingCallback(onInMeeting) {}
void onMeetingStatusChanged(MeetingStatus status, int iResult) override {
if (status == MEETING_STATUS_CONNECTING) {
if (onMeetingJoined) onMeetingJoined();
}
else if (status == MEETING_STATUS_INMEETING) {
if (onInMeetingCallback) onInMeetingCallback();
}
else if (status == MEETING_STATUS_ENDED) {
if (onMeetingEnded) onMeetingEnded();
}
}
// Must implement ALL pure virtual methods (9 total)
void onMeetingStatisticsWarningNotification(StatisticsWarningType type) override {}
void onMeetingParameterNotification(const MeetingParameter* param) override {}
void onSuspendParticipantsActivities() override {}
void onAICompanionActiveChangeNotice(bool isActive) override {}
void onMeetingTopicChanged(const zchar_t* sTopic) override {}
void onMeetingFullToWatchLiveStream(const zchar_t* sLiveStreamUrl) override {}
void onUserNetworkStatusChanged(MeetingComponentType type, ConnectionQuality level, unsigned int userId, bool uplink) override {}
#if defined(WIN32)
void onAppSignalPanelUpdated(IMeetingAppSignalHandler* pHandler) override {}
#endif
private:
void (*onMeetingJoined)();
void (*onMeetingEnded)();
void (*onInMeetingCallback)();
};See all required methods: Interface Methods Guide
4. Subscribe to Raw Video
#include <windows.h>
#include <cstdint>
#include <rawdata/zoom_rawdata_api.h>
#include <rawdata/rawdata_renderer_interface.h>
#include <zoom_sdk_raw_data_def.h> // REQUIRED for YUVRawDataI420
#include "ZoomSDKRendererDelegate.h"
IZoomSDKRenderer* videoHelper = nullptr;
ZoomSDKRendererDelegate* videoSource = new ZoomSDKRendererDelegate();
bool StartVideoCapture(uint32_t userId) {
// STEP 1: Start raw recording FIRST (required!)
IMeetingRecordingController* recordCtrl =
meetingService->GetMeetingRecordingController();
SDKError canStart = recordCtrl->CanStartRawRecording();
if (canStart != SDKERR_SUCCESS) {
std::cout << "Cannot start recording: " << canStart << std::endl;
return false;
}
recordCtrl->StartRawRecording();
// Wait for recording to initialize
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// STEP 2: Create renderer
SDKError err = createRenderer(&videoHelper, videoSource);
if (err != SDKERR_SUCCESS || !videoHelper) {
std::cout << "createRenderer failed: " << err << std::endl;
return false;
}
// STEP 3: Set resolution and subscribe
videoHelper->setRawDataResolution(ZoomSDKResolution_720P);
err = videoHelper->subscribe(userId, RAW_DATA_TYPE_VIDEO);
if (err != SDKERR_SUCCESS) {
std::cout << "Subscribe failed: " << err << std::endl;
return false;
}
std::cout << "Video capture started! Frames arrive in onRawDataFrameReceived()" << std::endl;
return true;
}ZoomSDKRendererDelegate.h:
#include <windows.h>
#include <cstdint>
#include <rawdata/rawdata_renderer_interface.h>
#include <zoom_sdk_raw_data_def.h>
#include <fstream>
#include <iostream>
using namespace ZOOM_SDK_NAMESPACE;
class ZoomSDKRendererDelegate : public IZoomSDKRendererDelegate {
public:
void onRawDataFrameReceived(YUVRawDataI420* data) override {
if (!data) return;
// YUV420 (I420) format: Y plane + U plane + V plane
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Calculate buffer sizes
// Y = full resolution, U/V = quarter resolution each
size_t ySize = width * height;
size_t uvSize = ySize / 4; // (width/2) * (height/2)
// Total size: width * height * 1.5 bytes
// Save to file (playback: ffplay -f rawvideo -pixel_format yuv420p -video_size 1280x720 output.yuv)
std::ofstream outputFile("output.yuv", std::ios::binary | std::ios::app);
outputFile.write(data->GetYBuffer(), ySize); // Brightness
outputFile.write(data->GetUBuffer(), uvSize); // Blue-difference
outputFile.write(data->GetVBuffer(), uvSize); // Red-difference
outputFile.close();
}
void onRawDataStatusChanged(RawDataStatus status) override {
std::cout << "Raw data status: " << status << std::endl;
}
void onRendererBeDestroyed() override {
std::cout << "Renderer destroyed" << std::endl;
}
};Complete video capture guide: Raw Video Capture Guide
5. Subscribe to Raw Audio
#include <rawdata/rawdata_audio_helper_interface.h>
class ZoomSDKAudioRawDataDelegate : public IZoomSDKAudioRawDataDelegate {
public:
void onMixedAudioRawDataReceived(AudioRawData* data) override {
// Process PCM audio (mixed from all participants)
std::ofstream pcmFile("audio.pcm", std::ios::binary | std::ios::app);
pcmFile.write((char*)data->GetBuffer(), data->GetBufferLen());
pcmFile.close();
}
void onOneWayAudioRawDataReceived(AudioRawData* data, uint32_t node_id) override {
// Process audio from specific participant
}
};
// Subscribe to audio
IZoomSDKAudioRawDataHelper* audioHelper = GetAudioRawdataHelper();
audioHelper->subscribe(new ZoomSDKAudioRawDataDelegate());6. Main Message Loop (CRITICAL!)
⚠️ WITHOUT THIS, CALLBACKS WON'T FIRE!
#include <windows.h>
#include <thread>
#include <chrono>
int main() {
// Initialize COM
CoInitialize(NULL);
// Load config and initialize
LoadConfig();
InitMeetingSDK();
AuthenticateSDK(sdk_jwt);
// CRITICAL: Windows message loop for SDK callbacks
// SDK uses Windows message pump to dispatch callbacks
// Without this, callbacks are queued but NEVER fire!
MSG msg;
while (!g_exit) {
// Process all pending Windows messages
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
g_exit = true;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Small sleep to avoid busy-waiting
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// Cleanup
CleanSDK();
CoUninitialize();
return 0;
}Why message loop is critical: The SDK uses Windows COM/messaging for async callbacks. Without PeekMessage(), the SDK queues messages but they're never retrieved/dispatched, so callbacks never execute.
Symptoms without message loop:
Authentication timeout (even with valid JWT)
Meeting join timeout
No callback events fire
Appears like network/auth issue but it's a message loop issue
See detailed explanation: Windows Message Loop Guide
Common Issues & Solutions
| Issue | Solution |
|---|---|
| Callbacks don't fire / Auth timeout | Add Windows message loop → Guide |
uint32_t / AudioType errors | Fix include order → Guide |
| Abstract class error | Implement all virtual methods → Guide |
| How to implement [feature]? | Follow universal pattern → Guide |
| Authentication fails | Check JWT token & error codes → Guide |
| No video frames received | Call StartRawRecording() first → Guide |
Complete troubleshooting: Common Issues Guide
How to Implement Any Feature
The SDK has 35+ feature controllers (audio, video, chat, recording, participants, screen sharing, breakout rooms, webinars, Q&A, polling, whiteboard, captions, AI companion, etc.).
Universal pattern that works for ALL features:
Get the controller (singleton):
IMeetingAudioController* audioCtrl = meetingService->GetMeetingAudioController(); IMeetingChatController* chatCtrl = meetingService->GetMeetingChatController(); // ... 33 more controllers availableImplement event listener (observer pattern):
class MyAudioListener : public IMeetingAudioCtrlEvent { void onUserAudioStatusChange(IList<IUserAudioStatus*>* lst) override { // React to audio events } // ... implement all required methods };Register and use:
audioCtrl->SetEvent(new MyAudioListener()); audioCtrl->MuteAudio(userId, true); // Use feature
Complete guide with examples: SDK Architecture Pattern
Available Examples
| Example | Description |
|---|---|
| SkeletonDemo | Minimal join meeting - start here |
| GetVideoRawData | Subscribe to raw video streams |
| GetAudioRawData | Subscribe to raw audio streams |
| SendVideoRawData | Send custom video as virtual camera |
| SendAudioRawData | Send custom audio as virtual mic |
| GetShareRawData | Capture screen share content |
| LocalRecording | Local MP4 recording |
| ChatDemo | In-meeting chat functionality |
| CaptionDemo | Closed caption/live transcription |
| BreakoutDemo | Breakout room management |
Detailed References
🎯 Core Concepts (START HERE!)
concepts/sdk-architecture-pattern.md - Universal pattern for implementing ANY feature - Understanding this unlocks the entire SDK!
📚 Complete Examples
examples/authentication-pattern.md - Complete working authentication with JWT tokens
examples/raw-video-capture.md - YUV420 video capture with detailed format explanation
🔧 Troubleshooting Guides
troubleshooting/windows-message-loop.md - Why callbacks don't fire (MOST CRITICAL!)
troubleshooting/build-errors.md - SDK header dependency issues and fixes
troubleshooting/common-issues.md - Quick diagnostic workflow and error code tables
📖 References
references/interface-methods.md - How to implement ALL required virtual methods
references/windows-reference.md - Dependencies, Visual Studio setup
../references/authorization.md - SDK JWT generation
../references/bot-authentication.md - Bot token types (ZAK, OBF, JWT)
🎨 Feature-Specific Guides
../references/breakout-rooms.md - Programmatic breakout room management
../references/ai-companion.md - AI Companion controls
Sample Repositories
| Repository | Description |
|---|---|
| meetingsdk-windows-raw-recording-sample | Official raw data capture samples |
| meetingsdk-windows-local-recording-sample | Local recording with Docker |
Playing Raw Video/Audio Files
Raw YUV/PCM files have no headers - you must specify format explicitly.
Play Raw YUV Video
ffplay -video_size 1280x720 -pixel_format yuv420p -f rawvideo output.yuvConvert YUV to MP4
ffmpeg -video_size 1280x720 -pixel_format yuv420p -f rawvideo -i output.yuv -c:v libx264 output.mp4Play Raw PCM Audio
ffplay -f s16le -ar 32000 -ac 1 audio.pcmConvert PCM to WAV
ffmpeg -f s16le -ar 32000 -ac 1 -i audio.pcm output.wavCombine Video + Audio
ffmpeg -video_size 1280x720 -pixel_format yuv420p -f rawvideo -i output.yuv ^
-f s16le -ar 32000 -ac 1 -i audio.pcm ^
-c:v libx264 -c:a aac -shortest output.mp4Key flags:
| Flag | Description |
|---|---|
-video_size WxH | Frame dimensions (e.g., 1280x720) |
-pixel_format yuv420p | I420/YUV420 planar format |
-f rawvideo | Raw video input (no container) |
-f s16le | Signed 16-bit little-endian PCM |
-ar 32000 | Sample rate (Zoom uses 32kHz) |
-ac 1 | Mono (use -ac 2 for stereo) |
Authentication Requirements (2026 Update)
Important: Beginning March 2, 2026, apps joining meetings outside their account must be authorized.
Use one of:
App Privilege Token (OBF) - Recommended for bots (
app_privilege_tokenin JoinParam)ZAK Token - Zoom Access Key (
userZAKin JoinParam)On Behalf Token - For specific use cases (
onBehalfTokenin JoinParam)
📖 Complete Documentation Library
This skill includes comprehensive guides created from real-world debugging:
🎯 Start Here
SDK Architecture Pattern - Master document: Universal pattern for ANY feature
SKILL.md - Complete navigation guide
📚 By Category
Core Concepts:
SDK Architecture Pattern - How every feature works (singleton + observer pattern)
Complete Examples:
Authentication Pattern - Working JWT auth with all code
Raw Video Capture - YUV420 video capture explained
Troubleshooting:
Windows Message Loop - CRITICAL: Why callbacks don't fire
Build Errors - SDK header dependency fixes
Common Issues - Quick diagnostics & error codes
References:
Interface Methods - All required virtual methods (6 auth + 9 meeting)
Windows Reference - Platform setup
Authorization - JWT generation
Bot Authentication - Bot token types
Breakout Rooms - Breakout room API
AI Companion - AI features
🚨 Most Critical Issues (From Real Debugging)
Callbacks not firing → Missing Windows message loop (99% of issues)
Build errors → SDK header dependencies (
uint32_t,AudioType, etc.)See: Build Errors Guide
Abstract class errors → Missing virtual method implementations
💡 Key Insight
Once you learn the 3-step pattern, you can implement ANY of the 35+ features:
Get controller → 2. Implement event listener → 3. Register and use
Official Resources
Official docs: https://developers.zoom.us/docs/meeting-sdk/windows/
API Reference: https://marketplacefront.zoom.us/sdk/meeting/windows/annotated.html
Developer forum: https://devforum.zoom.us/
SDK download: https://marketplace.zoom.us/
Documentation Version: Based on Zoom Windows Meeting SDK v6.7.2.26830
Need help? Start with SKILL.md for complete navigation.
Merged from meeting-sdk/windows/SKILL.md
Zoom Windows Meeting SDK - Complete Documentation Index
🚀 Quick Start Path
If you're new to the SDK, follow this order:
Read the architecture pattern → concepts/sdk-architecture-pattern.md
This teaches you the universal formula that applies to ALL features
Once you understand this, you can implement any feature by reading the
.hfiles
Fix build errors → troubleshooting/build-errors.md
SDK header dependencies issues
Required include order
Implement authentication → examples/authentication-pattern.md
Complete working JWT authentication code
Fix callback issues → troubleshooting/windows-message-loop.md
CRITICAL: Why callbacks don't fire without Windows message loop
This was the hardest issue to diagnose!
Implement virtual methods → references/interface-methods.md
Complete lists of all required methods
How to avoid abstract class errors
Capture video (optional) → examples/raw-video-capture.md
YUV420 format explained
Complete raw data capture workflow
Troubleshoot any issues → troubleshooting/common-issues.md
Quick diagnostic checklist
Error code tables
"If you see X, do Y" reference
📂 Documentation Structure
meeting-sdk/windows/
├── SKILL.md # Main skill overview
├── SKILL.md # This file - navigation guide
│
├── concepts/ # Core architectural patterns
│ ├── sdk-architecture-pattern.md # THE MOST IMPORTANT DOC
│ │ # Universal formula for ANY feature
│ ├── singleton-hierarchy.md # Navigation guide for SDK services
│ │ # 4-level deep service tree, when/how
│ ├── custom-ui-architecture.md # How Custom UI rendering works
│ │ # Child HWNDs, D3D, layout, events
│ └── custom-ui-vs-raw-data.md # SDK-rendered vs self-rendered
│ # Decision guide for Custom UI approach
│
├── examples/ # Complete working code
│ ├── authentication-pattern.md # JWT auth with full code
│ ├── raw-video-capture.md # Video capture with YUV420 details
│ │ # Recording vs Streaming, permissions
│ ├── custom-ui-video-rendering.md # Custom UI with video container
│ │ # Active speaker + gallery layout
│ ├── breakout-rooms.md # Complete breakout room guide
│ │ # 5 roles, create/manage/join
│ ├── chat.md # Send/receive chat messages
│ │ # Rich text, threading, file transfer
│ ├── captions-transcription.md # Live transcription & closed captions
│ │ # Multi-language translation
│ ├── local-recording.md # Local MP4 recording
│ │ # Permission flow, encoder monitoring
│ ├── share-raw-data-capture.md # Screen share raw data capture
│ │ # YUV420 frames from shared content
│ └── send-raw-data.md # Virtual camera/mic/share
│ # Send custom video/audio/share
│
├── troubleshooting/ # Problem solving guides
│ ├── windows-message-loop.md # CRITICAL - Why callbacks fail
│ ├── build-errors.md # Header dependency fixes + MSBuild
│ └── common-issues.md # Quick diagnostic workflow
│
└── references/ # Reference documentation
├── interface-methods.md # Required virtual methods
│ # Auth(6) + Meeting(9) + CustomUI(13)
├── windows-reference.md # Platform setup
├── authorization.md # JWT generation
├── bot-authentication.md # Bot token types
├── breakout-rooms.md # Breakout room features
└── ai-companion.md # AI Companion features🎯 By Use Case
I want to build a meeting bot
SDK Architecture Pattern - Understand the pattern
Authentication Pattern - Join meetings
Windows Message Loop - Fix callback issues
Interface Methods - Implement callbacks
I'm getting build errors
Build Errors Guide - SDK header dependencies
Interface Methods - Abstract class errors
Common Issues - Linker errors
I'm getting runtime errors
Windows Message Loop - Callbacks not firing
Authentication Pattern - Auth timeout
Common Issues - Error code tables
I want to build a Custom UI meeting app
Custom UI Architecture - How SDK rendering works
SDK-Rendered vs Self-Rendered - Choose your approach
Custom UI Video Rendering - Complete working code
Interface Methods - 13 Custom UI virtual methods
Build Errors Guide - MSBuild from git bash
I want to capture video/audio
Raw Video Capture - Complete video workflow
Recording vs Streaming approaches
Permission requirements (host, OAuth tokens)
Audio PCM capture
Share Raw Data Capture - Screen share capture
Subscribe to RAW_DATA_TYPE_SHARE
Handle dynamic resolution
SDK Architecture Pattern - Controller pattern
Common Issues - No frames received
I want to use breakout rooms
Breakout Rooms Guide - Complete breakout room workflow
5 roles: Creator, Admin, Data, Assistant, Attendee
Create, configure, manage, join/leave rooms
Common Issues - Breakout room error codes
I want to implement chat
Chat Guide - Send/receive messages
Rich text formatting (bold, italic, links)
Private messages and threading
File transfer events
I want to use live transcription
Captions & Transcription Guide - Live transcription
Automatic speech-to-text
Multi-language translation
Manual closed captions (host feature)
I want to record meetings
Local Recording Guide - Local MP4 recording
Permission request workflow
zTscoder.exe encoder monitoring
Gallery view vs active speaker
I want to implement a specific feature
SDK Architecture Pattern - START HERE!
Find the controller in
SDK/x64/h/meeting_service_interface.hFind the header in
SDK/x64/h/meeting_service_components/Follow the universal pattern: Get controller → Implement listener → Use methods
I want to understand the SDK architecture
SDK Architecture Pattern - Complete architecture overview
Singleton Hierarchy - Navigate the service tree (4 levels)
Interface Methods - Event listener pattern
Authentication Pattern - Service pattern
🔥 Most Critical Documents
1. SDK Architecture Pattern (⭐ MASTER DOCUMENT)
concepts/sdk-architecture-pattern.md
This is THE most important document. It teaches the universal 3-step pattern:
Get controller (singleton pattern)
Implement event listener (observer pattern)
Register and use
Once you understand this pattern, you can implement any of the 35+ features by just reading the SDK headers.
Key insight: The Zoom SDK follows a perfectly consistent architecture. Every feature works the same way.
2. Windows Message Loop (⚠️ MOST COMMON ISSUE)
troubleshooting/windows-message-loop.md
99% of "callbacks not firing" issues are caused by missing Windows message loop. This document explains:
Why SDK requires
PeekMessage()loopHow to implement it correctly
How to diagnose callback issues
This was the hardest bug to find during development (took ~2 hours).
3. Build Errors Guide
troubleshooting/build-errors.md
SDK headers have dependency bugs that cause build errors. This document provides:
Required include order
Missing
<cstdint>fixMissing
AudioTypefixMissing
YUVRawDataI420fix
📊 By Document Type
Concepts (Why and How)
SDK Architecture Pattern - Universal implementation pattern
Singleton Hierarchy - Navigation guide for SDK services (4 levels deep)
Examples (Complete Working Code)
Authentication Pattern - JWT authentication
Raw Video Capture - Video capture with YUV420, recording vs streaming
Custom UI Video Rendering - SDK-rendered video containers
Breakout Rooms - Create, manage, join breakout rooms
Chat - Send/receive messages with rich formatting
Captions & Transcription - Live transcription and closed captions
Local Recording - Local MP4 recording with permission flow
Share Raw Data Capture - Screen share raw data capture
Send Raw Data - Virtual camera, microphone, and share
Troubleshooting (Problem Solving)
Windows Message Loop - Callback issues
Build Errors - Compilation issues
Common Issues - Quick diagnostics
References (Lookup Information)
Interface Methods - Required virtual methods
Windows Reference - Platform setup
Authorization - JWT generation
Bot Authentication - Bot tokens
Breakout Rooms - Breakout room API
AI Companion - AI features
💡 Key Learnings from Real Debugging
These documents were created from actual debugging of a non-functional Zoom SDK sample. Here are the key insights:
Critical Discoveries:
Windows Message Loop is MANDATORY (not optional)
SDK uses Windows message pump for callbacks
Without it, callbacks are queued but never fire
Manifests as "authentication timeout" even with valid JWT
SDK Headers Have Dependency Bugs
Missing
#include <cstdint>in SDK headersmeeting_participants_ctrl_interface.hdoesn't includemeeting_audio_interface.hrawdata_renderer_interface.honly forward-declaresYUVRawDataI420See: Build Errors Guide
Include Order is CRITICAL
<windows.h>must be FIRST<cstdint>must be SECONDThen SDK headers in specific order
See: Build Errors Guide
ALL Virtual Methods Must Be Implemented
Including WIN32-conditional methods
SDK v6.7.2 requires 6 auth methods + 9 meeting methods
Different versions have different requirements
The Architecture is Beautifully Consistent
Every feature follows the same 3-step pattern
Controllers are singletons
Event listeners use observer pattern
Once you learn the pattern, you can implement any feature
🎓 Learning Path by Skill Level
Beginner (Never used Zoom SDK)
Read SDK Architecture Pattern to understand the overall design
Follow Authentication Pattern to join your first meeting
Reference Common Issues when you hit problems
Intermediate (Familiar with SDK basics)
Deep dive into SDK Architecture Pattern - implement multiple features
Learn Raw Video Capture for media processing
Use Interface Methods as reference
Advanced (Building production bots)
Study SDK Architecture Pattern - learn to implement ANY feature
Master Windows Message Loop - understand async callback flow
Reference SDK headers directly using the universal pattern
🔍 How to Find What You Need
"My code won't compile"
"Authentication times out"
"Callbacks never fire"
"Abstract class error"
"How do I implement [feature]?"
"How do I join a meeting?"
"How do I capture video?"
"What error code means what?"
→ Common Issues - Comprehensive error code tables (SDKERR, AUTHRET, Login, BO, Phone, OBF)
"How do I use breakout rooms?"
"How does the SDK work?"
"How do I navigate to a specific controller/feature?"
"How do I send/receive chat messages?"
"How do I use live transcription?"
→ Captions & Transcription Guide
"How do I record locally?"
"How do I capture screen share?"
📝 Document Version
All documents are based on Zoom Windows Meeting SDK v6.7.2.26830.
Different SDK versions may have:
Different required callback methods
Different error codes
Different API behavior
If using a different version, use grep "= 0" SDK/x64/h/*.h to verify required methods.
Remember: The SDK Architecture Pattern is the fastest way to understand how the Windows Meeting SDK fits together. Read it first if you are debugging custom UI or event flow issues.
Operations
RUNBOOK.md - 5-minute preflight and debugging checklist.
Files
- ATTRIBUTION.mdAdded by AutoGineer890 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md2.3 KiB
- SKILL.md44.1 KiB
- agents/openai.yaml427 B
- concepts/custom-ui-architecture.md8.8 KiB
- concepts/custom-ui-vs-raw-data.md6.1 KiB
- concepts/sdk-architecture-pattern.md20.0 KiB
- concepts/singleton-hierarchy.md20.2 KiB
- examples/ai-companion.md16.2 KiB
- examples/authentication-pattern.md20.4 KiB
- examples/breakout-rooms.md16.6 KiB
- examples/captions-transcription.md18.1 KiB
- examples/chat.md12.7 KiB
- examples/custom-ui-video-rendering.md7.9 KiB
- examples/local-recording.md17.4 KiB
- examples/raw-video-capture.md23.4 KiB
- examples/send-raw-data.md12.9 KiB
- examples/service-quality.md8.0 KiB
- examples/share-raw-data-capture.md15.9 KiB
- examples/video-advanced.md10.2 KiB
- references/deployment.md7.2 KiB
- references/interface-methods.md15.6 KiB
- references/windows-reference.md22.5 KiB
- troubleshooting/build-errors.md10.8 KiB
- troubleshooting/common-issues.md22.1 KiB
- troubleshooting/windows-message-loop.md9.6 KiB
examples/raw-video-capture.md
Raw Video Capture Example
Overview
This guide shows how to capture raw video data from Zoom meetings using the Windows Meeting SDK. Raw video data is provided in YUV420 (I420) format for video and PCM format for audio.
Raw Recording vs Raw Streaming
There are two ways to access raw data in the Zoom SDK:
| Method | Permission Source | How to Get Permission | Use Case |
|---|---|---|---|
| Raw Recording | Local recording permission | Host/co-host OR request from host | Capture data with "recording" consent dialog |
| Raw Streaming | Live streaming permission | Must be licensed Pro/Business/Education/Enterprise | Capture data with "streaming" consent dialog |
Key differences:
Raw Recording: Disables local recording (
.mp4) for the SDK user. Host can still cloud record.Raw Streaming: Other participants see "live streaming" notification instead of "recording".
Both give you the same raw data access (YUV420 video, PCM audio).
SDK Version Requirements:
Raw recording: SDK 5.9.0+
Raw streaming by host: SDK 5.11.0+
Raw streaming by non-host: SDK 5.12.8+
Request local recording permission: SDK 5.13.5+
Permission Requirements
For Raw Recording
The meeting must have local recording enabled, AND you must meet one of:
You are the meeting host or co-host
You have been granted local recording permission by the host
You joined with an OAuth app privilege token (see below)
For Raw Streaming
You must meet all of:
Current user has raw live streaming permission
Meeting host has a Pro, Business, Education, or Enterprise account
Meeting host is licensed for live streaming
OAuth App Privilege Token (Advanced)
You can skip the "request permission from host" step by using OAuth tokens:
For recording:
OAuth app requests
Meeting_token:read:admin:local_recording(admin) orMeeting_token:read:local_recording(user)Call REST API:
GET /meetings/{meetingId}/jointoken/local_recordingPass token to SDK via
app_privilege_tokenjoin parameter
For streaming:
OAuth app requests
Meeting_token:read:admin:live_streaming(admin) orMeeting_token:read:live_streaming(user)Call REST API:
GET /meetings/{meetingId}/jointoken/live_streamingPass token to SDK via
app_privilege_tokenjoin parameter
Complete Workflow
Step 1: Join Meeting Successfully
Before capturing video, you must:
✅ Initialize SDK
✅ Authenticate with JWT token
✅ Join meeting successfully (
MEETING_STATUS_INMEETING)
See Authentication Pattern for this setup.
Step 2: Start Raw Recording (or Streaming)
CRITICAL: You MUST call StartRawRecording() before you can capture video data.
void OnInMeeting() {
std::cout << "[VIDEO] In meeting! Starting video capture..." << std::endl;
// Get recording controller
IMeetingRecordingController* recordingCtrl =
meetingService->GetMeetingRecordingController();
if (!recordingCtrl) {
std::cerr << "[VIDEO] ERROR: Failed to get recording controller" << std::endl;
return;
}
// Check if we can start recording
SDKError canStart = recordingCtrl->CanStartRecording(false, 0);
if (canStart != SDKERR_SUCCESS) {
std::cerr << "[VIDEO] Cannot start recording: " << canStart << std::endl;
return;
}
// Start raw recording (this enables raw data capture)
SDKError err = recordingCtrl->StartRawRecording();
if (err != SDKERR_SUCCESS) {
std::cerr << "[VIDEO] StartRawRecording failed: " << err << std::endl;
return;
}
std::cout << "[VIDEO] Raw recording started!" << std::endl;
// Wait a moment for recording to initialize
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Continue to Step 3...
}Important notes:
StartRawRecording()does NOT create a recording file on diskIt only enables raw data capture via SDK callbacks
You need host/co-host permissions OR special SDK app privileges
If you get
SDKERR_WRONG_USAGE, you may lack permissions
Alternative: Use Raw Streaming instead:
// If you have streaming permission instead of recording permission
IMeetingLiveStreamController* streamCtrl = meetingService->GetMeetingLiveStreamController();
SDKError err = streamCtrl->StartRawLiveStream();
if (err != SDKERR_SUCCESS) {
std::cerr << "[VIDEO] StartRawLiveStream failed: " << err << std::endl;
return;
}
std::cout << "[VIDEO] Raw streaming started!" << std::endl;
// Same subscription process applies after thisRequesting permission at runtime:
// If you're not the host, request permission
IMeetingLiveStreamController* streamCtrl = meetingService->GetMeetingLiveStreamController();
streamCtrl->RequestRawLiveStream(L"My AI Bot", L"https://example.com/description");
// Listen for onRawLiveStreamPrivilegeChanged callback to know when grantedStep 3: Get Participant IDs
You need to know which user's video you want to capture:
// Get participants controller
IMeetingParticipantsController* participantsCtrl =
meetingService->GetMeetingParticipantsController();
if (!participantsCtrl) {
std::cerr << "[VIDEO] Failed to get participants controller" << std::endl;
return;
}
// Get list of all participants
IList<unsigned int>* participantList = participantsCtrl->GetParticipantsList();
if (!participantList || participantList->GetCount() == 0) {
std::cerr << "[VIDEO] No participants in meeting" << std::endl;
return;
}
// Get first participant's user ID
uint32_t userId = participantList->GetItem(0);
std::cout << "[VIDEO] Found participant: " << userId << std::endl;
// You can also get the user's name:
IUserInfo* userInfo = participantsCtrl->GetUserByUserID(userId);
if (userInfo) {
const wchar_t* userName = userInfo->GetUserName();
std::wcout << L"[VIDEO] User name: " << userName << std::endl;
}How to capture specific users:
All participants: Loop through
participantListand subscribe to eachActive speaker: Use
IMeetingVideoController()->GetActiveVideoUserID()Yourself: Use
IMeetingParticipantsController()->GetMySelfUser()->GetUserID()Specific name: Loop and match
GetUserName()
Step 4: Implement Renderer Delegate
This class receives the video frames:
ZoomSDKRendererDelegate.h:
#pragma once
#include <windows.h>
#include <cstdint>
#include <rawdata/rawdata_renderer_interface.h>
#include <zoom_sdk_raw_data_def.h>
#include <fstream>
#include <iostream>
using namespace ZOOM_SDK_NAMESPACE;
class ZoomSDKRendererDelegate : public IZoomSDKRendererDelegate {
public:
ZoomSDKRendererDelegate();
~ZoomSDKRendererDelegate();
// Called when a new video frame arrives
void onRawDataFrameReceived(YUVRawDataI420* data) override;
// Called when raw data status changes
void onRawDataStatusChanged(RawDataStatus status) override;
// Called before renderer is destroyed
void onRendererBeDestroyed() override;
private:
void SaveToRawYUVFile(YUVRawDataI420* data);
int frameCount;
};ZoomSDKRendererDelegate.cpp:
#include "ZoomSDKRendererDelegate.h"
ZoomSDKRendererDelegate::ZoomSDKRendererDelegate() : frameCount(0) {
std::cout << "[VIDEO] Renderer delegate created" << std::endl;
}
ZoomSDKRendererDelegate::~ZoomSDKRendererDelegate() {
std::cout << "[VIDEO] Renderer destroyed. Total frames: " << frameCount << std::endl;
}
void ZoomSDKRendererDelegate::onRawDataFrameReceived(YUVRawDataI420* data) {
if (!data) return;
// Get frame dimensions
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Get rotation (0, 90, 180, 270 degrees)
int rotation = data->GetRotation();
// Log every 30 frames (every ~1 second at 30fps)
if (frameCount % 30 == 0) {
std::cout << "[VIDEO] Frame " << frameCount
<< " - " << width << "x" << height
<< " (rotation: " << rotation << "°)" << std::endl;
}
// Process the frame (save to file, encode, analyze, etc.)
SaveToRawYUVFile(data);
frameCount++;
}
void ZoomSDKRendererDelegate::onRawDataStatusChanged(RawDataStatus status) {
switch (status) {
case RawData_On:
std::cout << "[VIDEO] Raw data ON" << std::endl;
break;
case RawData_Off:
std::cout << "[VIDEO] Raw data OFF" << std::endl;
break;
default:
std::cout << "[VIDEO] Status: " << status << std::endl;
break;
}
}
void ZoomSDKRendererDelegate::onRendererBeDestroyed() {
std::cout << "[VIDEO] Renderer being destroyed" << std::endl;
}
void ZoomSDKRendererDelegate::SaveToRawYUVFile(YUVRawDataI420* data) {
// Open output file in append binary mode
std::ofstream outputFile("output.yuv", std::ios::out | std::ios::binary | std::ios::app);
if (!outputFile.is_open()) {
std::cerr << "[VIDEO] Error opening output.yuv" << std::endl;
return;
}
// YUV420 format: Y plane + U plane + V plane
size_t ySize = data->GetStreamWidth() * data->GetStreamHeight();
size_t uvSize = ySize / 4; // U and V are quarter size each
// Write planes in order: Y, U, V
outputFile.write(data->GetYBuffer(), ySize);
outputFile.write(data->GetUBuffer(), uvSize);
outputFile.write(data->GetVBuffer(), uvSize);
outputFile.close();
}Step 5: Create Renderer and Subscribe
// Create renderer helper and delegate
IZoomSDKVideoSource* videoHelper = nullptr;
ZoomSDKRendererDelegate* videoDelegate = new ZoomSDKRendererDelegate();
SDKError err = createRenderer(&videoHelper, videoDelegate);
if (err != SDKERR_SUCCESS || !videoHelper) {
std::cerr << "[VIDEO] Failed to create renderer: " << err << std::endl;
delete videoDelegate;
return;
}
// Set desired resolution (affects CPU/bandwidth usage)
videoHelper->setRawDataResolution(ZoomSDKResolution_720P);
// Other options: ZoomSDKResolution_90P, 180P, 360P, 720P, 1080P
// Subscribe to user's video stream
err = videoHelper->subscribe(userId, RAW_DATA_TYPE_VIDEO);
if (err != SDKERR_SUCCESS) {
std::cerr << "[VIDEO] Subscribe failed: " << err << std::endl;
return;
}
std::cout << "[VIDEO] Successfully subscribed! Frames will arrive in onRawDataFrameReceived()" << std::endl;Important notes:
Use
createRenderer()(global function), notCreateRenderer()ornewSet resolution BEFORE subscribing
Higher resolutions = more CPU/bandwidth but better quality
Frames arrive asynchronously in
onRawDataFrameReceived()
YUV420 (I420) Format Explained
What is YUV420?
YUV420 separates image into:
Y (luma): Brightness information (full resolution)
U (Cb, blue-difference): Color information (quarter resolution)
V (Cr, red-difference): Color information (quarter resolution)
Memory Layout
For a 1920x1080 frame:
Total bytes: 1920 * 1080 * 1.5 = 3,110,400 bytes
Y plane: [0 to 2,073,599] (1920 * 1080 = 2,073,600 bytes)
U plane: [2,073,600 to 2,592,639] (960 * 540 = 518,400 bytes)
V plane: [2,592,640 to 3,110,399] (960 * 540 = 518,400 bytes)Formula:
size_t ySize = width * height;
size_t uSize = (width / 2) * (height / 2) = width * height / 4;
size_t vSize = (width / 2) * (height / 2) = width * height / 4;
size_t totalSize = ySize + uSize + vSize = width * height * 1.5;Accessing Frame Data
void ProcessFrame(YUVRawDataI420* data) {
int width = data->GetStreamWidth(); // e.g., 1920
int height = data->GetStreamHeight(); // e.g., 1080
// Get pointers to each plane
const char* yBuffer = data->GetYBuffer(); // Brightness
const char* uBuffer = data->GetUBuffer(); // Blue-difference
const char* vBuffer = data->GetVBuffer(); // Red-difference
// Calculate sizes
size_t ySize = width * height;
size_t uvSize = (width / 2) * (height / 2);
// Example: Access pixel at (x, y)
int x = 100, y = 50;
// Y value (full resolution)
unsigned char yValue = yBuffer[y * width + x];
// U and V values (subsampled 2x2)
unsigned char uValue = uBuffer[(y/2) * (width/2) + (x/2)];
unsigned char vValue = vBuffer[(y/2) * (width/2) + (x/2)];
std::cout << "Pixel (" << x << "," << y << "): "
<< "Y=" << (int)yValue << " "
<< "U=" << (int)uValue << " "
<< "V=" << (int)vValue << std::endl;
}Converting YUV420 to RGB
void YUVtoRGB(unsigned char y, unsigned char u, unsigned char v,
unsigned char& r, unsigned char& g, unsigned char& b) {
int c = y - 16;
int d = u - 128;
int e = v - 128;
int red = (298 * c + 409 * e + 128) >> 8;
int green = (298 * c - 100 * d - 208 * e + 128) >> 8;
int blue = (298 * c + 516 * d + 128) >> 8;
// Clamp to [0, 255]
r = (red < 0) ? 0 : (red > 255) ? 255 : red;
g = (green < 0) ? 0 : (green > 255) ? 255 : green;
b = (blue < 0) ? 0 : (blue > 255) ? 255 : blue;
}Complete Example: Save to File
Save Raw YUV File
void SaveToRawYUVFile(YUVRawDataI420* data) {
std::ofstream file("output.yuv", std::ios::binary | std::ios::app);
size_t ySize = data->GetStreamWidth() * data->GetStreamHeight();
size_t uvSize = ySize / 4;
file.write(data->GetYBuffer(), ySize);
file.write(data->GetUBuffer(), uvSize);
file.write(data->GetVBuffer(), uvSize);
file.close();
}Play back with ffplay:
ffplay -f rawvideo -pixel_format yuv420p -video_size 1920x1080 -framerate 30 output.yuvSave as PNG Images (Using OpenCV)
#include <opencv2/opencv.hpp>
void SaveAsPNG(YUVRawDataI420* data, int frameNumber) {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Create YUV Mat
cv::Mat yuvImage(height + height/2, width, CV_8UC1);
// Copy Y plane
memcpy(yuvImage.data, data->GetYBuffer(), width * height);
// Copy U plane
memcpy(yuvImage.data + width * height, data->GetUBuffer(), width * height / 4);
// Copy V plane
memcpy(yuvImage.data + width * height * 5/4, data->GetVBuffer(), width * height / 4);
// Convert to BGR
cv::Mat bgrImage;
cv::cvtColor(yuvImage, bgrImage, cv::COLOR_YUV2BGR_I420);
// Save as PNG
std::string filename = "frame_" + std::to_string(frameNumber) + ".png";
cv::imwrite(filename, bgrImage);
}Handling Video Rotation
Video streams may be rotated (0°, 90°, 180°, 270°):
void ProcessFrame(YUVRawDataI420* data) {
int rotation = data->GetRotation(); // 0, 90, 180, or 270
if (rotation == 0) {
// Normal orientation, no rotation needed
SaveFrame(data);
} else {
// Need to rotate frame before displaying/processing
RotateFrame(data, rotation);
SaveFrame(data);
}
}OpenCV rotation:
cv::Mat RotateImage(cv::Mat& image, int rotation) {
cv::Mat rotated;
switch (rotation) {
case 90:
cv::rotate(image, rotated, cv::ROTATE_90_CLOCKWISE);
break;
case 180:
cv::rotate(image, rotated, cv::ROTATE_180);
break;
case 270:
cv::rotate(image, rotated, cv::ROTATE_90_COUNTERCLOCKWISE);
break;
default:
rotated = image;
break;
}
return rotated;
}Performance Considerations
Frame Rate
Video typically arrives at:
720p: ~30 fps
1080p: ~30 fps
Lower resolutions: ~15-30 fps
Process frames quickly to avoid dropping:
void onRawDataFrameReceived(YUVRawDataI420* data) {
// Fast: Just save to buffer/queue
frameQueue.push(data); // Process in separate thread
// Slow: Heavy processing here
ConvertToRGB(data); // May drop frames!
EncodeToH264(data);
SaveToDisk(data);
}Memory Usage
Per frame:
720p (1280x720): 1.38 MB
1080p (1920x1080): 3.11 MB
At 30 fps:
720p: ~41 MB/sec
1080p: ~93 MB/sec
Use buffering and separate processing threads for high performance.
Resolution Selection
// Lower resolution = less CPU/bandwidth
videoHelper->setRawDataResolution(ZoomSDKResolution_360P); // Good for analysis
// Higher resolution = better quality
videoHelper->setRawDataResolution(ZoomSDKResolution_1080P); // Good for recordingUnsubscribing and Cleanup
Stop Capturing Video
// Unsubscribe from video stream
if (videoHelper) {
videoHelper->unSubscribe(userId, RAW_DATA_TYPE_VIDEO);
}
// Stop raw recording
if (recordingCtrl) {
recordingCtrl->StopRawRecording();
}Cleanup
// Renderer delegate is automatically deleted by SDK
// Don't call delete on videoDelegate yourself!
// Just null out the pointer
videoHelper = nullptr;
videoDelegate = nullptr;Audio Raw Data
Audio is available in PCM format through separate callbacks:
Set Up Audio Raw Data
class ZoomAudioRawDataDelegate : public IZoomSDKAudioRawDataDelegate {
public:
// Called for each participant's audio (no support for telephone participants)
void onOneWayAudioRawDataReceived(AudioRawData* data_, uint32_t node_id) override {
// Process individual participant's audio
std::cout << "[AUDIO] Received audio from user: " << node_id << std::endl;
}
// Called for mixed meeting audio (what all participants hear)
void onMixedAudioRawDataReceived(AudioRawData* data_) override {
// Process mixed audio from all participants
SavePCMData(data_);
}
};
// Subscribe to audio
IZoomSDKAudioRawDataHelper* audioHelper = GetAudioRawdataHelper();
ZoomAudioRawDataDelegate* audioDelegate = new ZoomAudioRawDataDelegate();
audioHelper->subscribe(audioDelegate);Two audio callbacks:
onOneWayAudioRawDataReceived: Individual participant audio (excludes phone participants)onMixedAudioRawDataReceived: Combined meeting audio (what you'd hear in the meeting)
Screen Share Raw Data
You can also capture screen share data (separate from video):
// Subscribe to share data instead of video
videoHelper->subscribe(userId, RAW_DATA_TYPE_SHARE);The same IZoomSDKRendererDelegate::onRawDataFrameReceived() callback is used, but you'll receive share content instead of camera video.
Alpha Channel Mode (Background Removal)
Meeting hosts with a raw streaming token can enable alpha channel mode, which provides a mask to remove participant backgrounds. This is useful for rendering meeting participants in custom virtual environments.
Requirements
Must be meeting host with raw streaming token
Used with raw data capture
Check and Enable Alpha Channel
IMeetingVideoController* videoCtrl = meetingService->GetMeetingVideoController();
// Check if alpha channel mode can be enabled
if (videoCtrl->CanEnableAlphaChannelMode()) {
// Enable alpha channel mode
SDKError err = videoCtrl->EnableAlphaChannelMode(true);
if (err == SDKERR_SUCCESS) {
std::cout << "[ALPHA] Alpha channel mode enabled" << std::endl;
}
}
// Check if currently enabled
bool isEnabled = videoCtrl->IsAlphaChannelModeEnabled();
std::cout << "[ALPHA] Alpha mode active: " << (isEnabled ? "yes" : "no") << std::endl;Listen for Alpha Mode Changes
Implement IMeetingVideoCtrlEvent callback:
class VideoCtrlEventListener : public IMeetingVideoCtrlEvent {
public:
void onVideoAlphaChannelStatusChanged(bool isAlphaModeOn) override {
std::cout << "[ALPHA] Alpha channel mode: "
<< (isAlphaModeOn ? "ON" : "OFF") << std::endl;
}
// ... other IMeetingVideoCtrlEvent methods
};Access Alpha Data in Raw Frames
When alpha channel mode is enabled, YUVRawDataI420 has additional methods:
void onRawDataFrameReceived(YUVRawDataI420* data) override {
// Get standard YUV data
char* yBuffer = data->GetYBuffer();
char* uBuffer = data->GetUBuffer();
char* vBuffer = data->GetVBuffer();
// Get alpha mask (only available when alpha mode is enabled)
char* alphaBuffer = data->GetAlphaBuffer();
unsigned int alphaLen = data->GetAlphaBufferLen();
if (alphaBuffer != nullptr && alphaLen > 0) {
// Alpha mask is available!
// Use it to remove background from video frame
ProcessWithAlphaMask(data, alphaBuffer, alphaLen);
} else {
// No alpha data (alpha mode not enabled or not available)
ProcessNormalFrame(data);
}
}Alpha Mask Format
The alpha buffer contains a grayscale mask where:
White (255) = Foreground (participant's body)
Black (0) = Background (to be removed)
Gray values = Edge blending
Use Cases
Custom Virtual Backgrounds: Replace participant backgrounds with custom scenes
Mixed Reality: Render participants in 3D virtual environments
Green Screen Alternative: Remove backgrounds without physical green screen
Video Compositing: Layer participants over other content
Important Performance Note
From official documentation:
Do not perform heavy operations from within the raw data callbacks. This may cause delayed or lost data.
Recommended pattern:
void onRawDataFrameReceived(YUVRawDataI420* data) {
// Fast: Copy to queue and return immediately
Frame copy;
copy.width = data->GetStreamWidth();
copy.height = data->GetStreamHeight();
memcpy(copy.buffer, data->GetYBuffer(), data->GetBufferLen());
frameQueue.enqueue(copy); // Let separate thread process
// DON'T: Heavy processing here
// EncodeH264(data); // Blocks callback!
// SaveToNetwork(data); // Too slow!
}Troubleshooting
No Frames Received
Checklist:
[ ] Called
StartRawRecording()orStartRawLiveStream()BEFORE subscribing[ ] Waited ~500ms after starting before subscribing
[ ] User ID is valid (not 0, exists in participant list)
[ ] User has their video turned on
[ ] Subscribed to correct data type (
RAW_DATA_TYPE_VIDEO)[ ] Renderer delegate implements
onRawDataFrameReceived()correctly[ ] You have the required permissions (host/co-host or granted permission)
Test: Subscribe to your own video stream first (easier to control)
Frames Look Corrupted
Checklist:
[ ] Using YUV420 (I420) format, not RGB
[ ] Buffer size is
width * height * 1.5bytes[ ] Writing/reading planes in correct order: Y, U, V
[ ] U and V planes are quarter size each, not half size
[ ] Handling rotation correctly
Test: Save raw YUV and play with ffplay to verify format
Performance Issues / Dropped Frames
Solutions:
Lower resolution:
setRawDataResolution(ZoomSDKResolution_360P)Process frames in separate thread
Use frame queue/buffer
Skip frames if processing is slow (process every 2nd or 3rd frame)
Complete Working Example
See the full working implementation in:
C:\tempsdk\zoom-windows-sdk-sample\src\
├── ZoomSDKRendererDelegate.h
├── ZoomSDKRendererDelegate.cpp
└── main.cpp (OnInMeeting function)Key files to reference:
Renderer delegate implementation
Subscription workflow in
OnInMeeting()YUV file saving logic
Related Documentation
Authentication Pattern - How to join meetings first
Windows Message Loop - Required for callbacks
Interface Methods - Required virtual methods
Common Issues - Troubleshooting guide
Last Updated: Based on Zoom Windows Meeting SDK v6.7.2.26830
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs a command-line tool
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/zoom/skills/meeting-sdk/windows · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download zoom-meeting-sdk-windows.zip