Generated banner for Zoom Meeting SDK Windows
Generated logo for Zoom Meeting SDK Windows

Zoom Meeting SDK Windows

AnthropicOfficialCreated by ZoomDistributed by Anthropic

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

Zoom Meeting SDK for Windows - Native C++ SDK for embedding Zoom meetings into Windows desktop applications. Supports custom UI architecture with raw video/audio data, headless bots, and deep integration with meeting features. Includes SDK architecture patterns and Windows message loop handling.
— the description by Zoom

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 meeting functionality directly into desktop applications. It supports a wide range of use cases, from building custom meeting UIs to creating headless bots for automated tasks. The SDK utilizes a controller-based architecture where developers implement event listeners to handle meeting lifecycle events, audio/video streams, and other features. Proper integration requires careful management of the Windows message loop to ensure callbacks are processed correctly and adherence to specific project structure guidelines.

What it does

  • Enables embedding Zoom meetings within native Windows desktop applications.
  • Supports headless bot development for automated meeting participation.
  • Provides access to raw audio and video data streams for custom processing.
  • Offers a consistent controller-based pattern for accessing 35+ meeting features.
  • Allows for custom UI rendering or standard SDK-provided interfaces.
  • Facilitates local recording and advanced meeting management controls.

When to use it

  • When building a custom Windows desktop application that requires integrated video conferencing.
  • When developing automated bots that need to join Zoom meetings to perform tasks.
  • When you need low-level access to raw audio/video frames from a Zoom meeting.
  • When you need to build a custom meeting interface that deviates from the standard Zoom client UI.

Advantages

  • Native C++ performance for resource-intensive media processing.
  • Full control over the meeting UI and user experience.
  • Universal architecture pattern simplifies implementing diverse features.
  • Supports both UI-based applications and headless background services.

Usage examples

“How do I initialize the Zoom Meeting SDK in my C++ Windows application?”
You must create an InitParam object, configure the web domain and language settings, and call the InitSDK function, ensuring you handle the return status for success.
“Why are my Zoom SDK callbacks not firing in my Windows app?”
This is typically caused by a missing Windows message loop. You must implement a PeekMessage/DispatchMessage loop to allow the SDK to process asynchronous callbacks.
“How can I capture raw video from a meeting participant?”
You must start raw recording, create an IZoomSDKRenderer, and subscribe to the specific user's video stream using the renderer, then implement the IZoomSDKRendererDelegate to receive frames.
“What is the recommended project structure for a Zoom SDK Windows app?”
Use a native Visual Studio .vcxproj project, organize SDK files into x64/x86 subdirectories, and ensure your config.json is included in the project for easy access.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/anthropics/zoom-meeting-sdk-windows/download
  2. Unzip 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:

  1. SDK Architecture Pattern - Learn the universal pattern that works for ALL 35+ features

  2. Authentication Pattern - Get a working bot joining meetings

  3. Windows Message Loop - Fix the #1 reason callbacks don't fire

Building a Custom UI?

Having issues?

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 .sln to open in Visual Studio immediately

  • Project 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:

  1. Included in the .vcxproj as a <None> item with <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

  2. Placed in a "Config" filter in the .vcxproj.filters file so it appears under a "Config" folder in Solution Explorer

  3. Easily 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:

  1. Get controller (singleton): meetingService->Get[Feature]Controller()

  2. Implement event listener: class MyListener : public I[Feature]Event { ... }

  3. 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.json

Copy 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-windows
4. 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\include

Project Properties → Linker → General → Additional Library Directories:

$(SolutionDir)SDK\$(PlatformTarget)\lib

Project Properties → Linker → Input → Additional Dependencies:

sdk.lib

Post-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:

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

IssueSolution
Callbacks don't fire / Auth timeoutAdd Windows message loop → Guide
uint32_t / AudioType errorsFix include order → Guide
Abstract class errorImplement all virtual methods → Guide
How to implement [feature]?Follow universal pattern → Guide
Authentication failsCheck JWT token & error codes → Guide
No video frames receivedCall 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:

  1. Get the controller (singleton):

    IMeetingAudioController* audioCtrl = meetingService->GetMeetingAudioController();
    IMeetingChatController* chatCtrl = meetingService->GetMeetingChatController();
    // ... 33 more controllers available
  2. Implement event listener (observer pattern):

    class MyAudioListener : public IMeetingAudioCtrlEvent {
        void onUserAudioStatusChange(IList<IUserAudioStatus*>* lst) override {
            // React to audio events
        }
        // ... implement all required methods
    };
  3. Register and use:

    audioCtrl->SetEvent(new MyAudioListener());
    audioCtrl->MuteAudio(userId, true);  // Use feature

Complete guide with examples: SDK Architecture Pattern

Available Examples

ExampleDescription
SkeletonDemoMinimal join meeting - start here
GetVideoRawDataSubscribe to raw video streams
GetAudioRawDataSubscribe to raw audio streams
SendVideoRawDataSend custom video as virtual camera
SendAudioRawDataSend custom audio as virtual mic
GetShareRawDataCapture screen share content
LocalRecordingLocal MP4 recording
ChatDemoIn-meeting chat functionality
CaptionDemoClosed caption/live transcription
BreakoutDemoBreakout room management

Detailed References

🎯 Core Concepts (START HERE!)
📚 Complete Examples
🔧 Troubleshooting Guides
📖 References
🎨 Feature-Specific Guides
  • ../references/breakout-rooms.md - Programmatic breakout room management

  • ../references/ai-companion.md - AI Companion controls

Sample Repositories

RepositoryDescription
meetingsdk-windows-raw-recording-sampleOfficial raw data capture samples
meetingsdk-windows-local-recording-sampleLocal 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.yuv
Convert YUV to MP4
ffmpeg -video_size 1280x720 -pixel_format yuv420p -f rawvideo -i output.yuv -c:v libx264 output.mp4
Play Raw PCM Audio
ffplay -f s16le -ar 32000 -ac 1 audio.pcm
Convert PCM to WAV
ffmpeg -f s16le -ar 32000 -ac 1 -i audio.pcm output.wav
Combine 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.mp4

Key flags:

FlagDescription
-video_size WxHFrame dimensions (e.g., 1280x720)
-pixel_format yuv420pI420/YUV420 planar format
-f rawvideoRaw video input (no container)
-f s16leSigned 16-bit little-endian PCM
-ar 32000Sample rate (Zoom uses 32kHz)
-ac 1Mono (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_token in JoinParam)

  • ZAK Token - Zoom Access Key (userZAK in JoinParam)

  • On Behalf Token - For specific use cases (onBehalfToken in JoinParam)

📖 Complete Documentation Library

This skill includes comprehensive guides created from real-world debugging:

🎯 Start Here
📚 By Category

Core Concepts:

Complete Examples:

Troubleshooting:

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)
  1. Callbacks not firing → Missing Windows message loop (99% of issues)

  2. Build errors → SDK header dependencies (uint32_t, AudioType, etc.)

  3. Abstract class errors → Missing virtual method implementations

💡 Key Insight

Once you learn the 3-step pattern, you can implement ANY of the 35+ features:

  1. Get controller → 2. Implement event listener → 3. Register and use

See: SDK Architecture Pattern

Official Resources


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:

  1. 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 .h files

  2. Fix build errors → troubleshooting/build-errors.md

    • SDK header dependencies issues

    • Required include order

  3. Implement authentication → examples/authentication-pattern.md

    • Complete working JWT authentication code

  4. 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!

  5. Implement virtual methods → references/interface-methods.md

    • Complete lists of all required methods

    • How to avoid abstract class errors

  6. Capture video (optional) → examples/raw-video-capture.md

    • YUV420 format explained

    • Complete raw data capture workflow

  7. 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
  1. SDK Architecture Pattern - Understand the pattern

  2. Authentication Pattern - Join meetings

  3. Windows Message Loop - Fix callback issues

  4. Interface Methods - Implement callbacks

I'm getting build errors
  1. Build Errors Guide - SDK header dependencies

  2. Interface Methods - Abstract class errors

  3. Common Issues - Linker errors

I'm getting runtime errors
  1. Windows Message Loop - Callbacks not firing

  2. Authentication Pattern - Auth timeout

  3. Common Issues - Error code tables

I want to build a Custom UI meeting app
  1. Custom UI Architecture - How SDK rendering works

  2. SDK-Rendered vs Self-Rendered - Choose your approach

  3. Custom UI Video Rendering - Complete working code

  4. Interface Methods - 13 Custom UI virtual methods

  5. Build Errors Guide - MSBuild from git bash

I want to capture video/audio
  1. Raw Video Capture - Complete video workflow

    • Recording vs Streaming approaches

    • Permission requirements (host, OAuth tokens)

    • Audio PCM capture

  2. Share Raw Data Capture - Screen share capture

    • Subscribe to RAW_DATA_TYPE_SHARE

    • Handle dynamic resolution

  3. SDK Architecture Pattern - Controller pattern

  4. Common Issues - No frames received

I want to use breakout rooms
  1. Breakout Rooms Guide - Complete breakout room workflow

    • 5 roles: Creator, Admin, Data, Assistant, Attendee

    • Create, configure, manage, join/leave rooms

  2. Common Issues - Breakout room error codes

I want to implement chat
  1. Chat Guide - Send/receive messages

    • Rich text formatting (bold, italic, links)

    • Private messages and threading

    • File transfer events

I want to use live transcription
  1. Captions & Transcription Guide - Live transcription

    • Automatic speech-to-text

    • Multi-language translation

    • Manual closed captions (host feature)

I want to record meetings
  1. 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
  1. SDK Architecture Pattern - START HERE!

  2. Find the controller in SDK/x64/h/meeting_service_interface.h

  3. Find the header in SDK/x64/h/meeting_service_components/

  4. Follow the universal pattern: Get controller → Implement listener → Use methods

I want to understand the SDK architecture
  1. SDK Architecture Pattern - Complete architecture overview

  2. Singleton Hierarchy - Navigate the service tree (4 levels)

  3. Interface Methods - Event listener pattern

  4. 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:

  1. Get controller (singleton pattern)

  2. Implement event listener (observer pattern)

  3. 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() loop

  • How 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> fix

  • Missing AudioType fix

  • Missing YUVRawDataI420 fix


📊 By Document Type

Concepts (Why and How)
Examples (Complete Working Code)
Troubleshooting (Problem Solving)
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:
  1. 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

    • See: Windows Message Loop Guide

  2. SDK Headers Have Dependency Bugs

    • Missing #include <cstdint> in SDK headers

    • meeting_participants_ctrl_interface.h doesn't include meeting_audio_interface.h

    • rawdata_renderer_interface.h only forward-declares YUVRawDataI420

    • See: Build Errors Guide

  3. Include Order is CRITICAL

    • <windows.h> must be FIRST

    • <cstdint> must be SECOND

    • Then SDK headers in specific order

    • See: Build Errors Guide

  4. 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

    • See: Interface Methods Guide

  5. 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

    • See: SDK Architecture Pattern


🎓 Learning Path by Skill Level

Beginner (Never used Zoom SDK)
  1. Read SDK Architecture Pattern to understand the overall design

  2. Follow Authentication Pattern to join your first meeting

  3. Reference Common Issues when you hit problems

Intermediate (Familiar with SDK basics)
  1. Deep dive into SDK Architecture Pattern - implement multiple features

  2. Learn Raw Video Capture for media processing

  3. Use Interface Methods as reference

Advanced (Building production bots)
  1. Study SDK Architecture Pattern - learn to implement ANY feature

  2. Master Windows Message Loop - understand async callback flow

  3. Reference SDK headers directly using the universal pattern


🔍 How to Find What You Need

"My code won't compile"

→ Build Errors Guide

"Authentication times out"

→ Windows Message Loop

"Callbacks never fire"

→ Windows Message Loop

"Abstract class error"

→ Interface Methods

"How do I implement [feature]?"

→ SDK Architecture Pattern

"How do I join a meeting?"

→ Authentication Pattern

"How do I capture video?"

→ Raw Video Capture

"What error code means what?"

→ Common Issues - Comprehensive error code tables (SDKERR, AUTHRET, Login, BO, Phone, OBF)

"How do I use breakout rooms?"

→ Breakout Rooms Guide

"How does the SDK work?"

→ SDK Architecture Pattern

"How do I navigate to a specific controller/feature?"

→ Singleton Hierarchy

"How do I send/receive chat messages?"

→ Chat Guide

"How do I use live transcription?"

→ Captions & Transcription Guide

"How do I record locally?"

→ Local Recording Guide

"How do I capture screen share?"

→ Share Raw Data Capture


📝 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

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:

MethodPermission SourceHow to Get PermissionUse Case
Raw RecordingLocal recording permissionHost/co-host OR request from hostCapture data with "recording" consent dialog
Raw StreamingLive streaming permissionMust be licensed Pro/Business/Education/EnterpriseCapture 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:

  1. OAuth app requests Meeting_token:read:admin:local_recording (admin) or Meeting_token:read:local_recording (user)

  2. Call REST API: GET /meetings/{meetingId}/jointoken/local_recording

  3. Pass token to SDK via app_privilege_token join parameter

For streaming:

  1. OAuth app requests Meeting_token:read:admin:live_streaming (admin) or Meeting_token:read:live_streaming (user)

  2. Call REST API: GET /meetings/{meetingId}/jointoken/live_streaming

  3. Pass token to SDK via app_privilege_token join parameter


Complete Workflow

Step 1: Join Meeting Successfully

Before capturing video, you must:

  1. ✅ Initialize SDK

  2. ✅ Authenticate with JWT token

  3. ✅ 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 disk

  • It 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 this

Requesting 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 granted

Step 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 participantList and subscribe to each

  • Active 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), not CreateRenderer() or new

  • Set 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.yuv
Save 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 recording

Unsubscribing 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
  1. Custom Virtual Backgrounds: Replace participant backgrounds with custom scenes

  2. Mixed Reality: Render participants in 3D virtual environments

  3. Green Screen Alternative: Remove backgrounds without physical green screen

  4. 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() or StartRawLiveStream() 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.5 bytes

  • [ ] 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



Last Updated: Based on Zoom Windows Meeting SDK v6.7.2.26830

Requirements and compatibility

Written for: Claude Code, Claude apps

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: Claude Code frontmatter, 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
Anthropic (Official)
Distributed by
Anthropic
Source
anthropics/knowledge-work-plugins · partner-built/zoom-plugin/skills/meeting-sdk/windows · commit e050c2e
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from partner-built/zoom-plugin/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Zoom or Anthropic.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download zoom-meeting-sdk-windows.zip