Zoom Video SDK Linux
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Zoom Video SDK for Linux provides a C++ library for building custom video applications, including headless bots and UI-integrated clients. Unlike other platforms, the Linux SDK does not provide a Canvas API, requiring developers to use a raw data pipe for all audio and video processing. This skill covers the architectural patterns, environment setup, and critical implementation requirements for Linux-based Zoom integrations.
What it does
- Provides guidance for building headless bots and custom UI applications using Qt or GTK.
- Explains the mandatory raw data pipe architecture for video and audio handling.
- Details the integration of virtual audio and video devices for headless environments.
- Outlines the singleton-based SDK architecture and event delegate patterns.
- Offers troubleshooting steps for PulseAudio, Qt dependencies, and build configurations.
- Supports advanced features like cloud recording, live streaming, and transcription.
When to use it
- When building custom video conferencing applications on Linux.
- When developing headless bots for automated session participation.
- When integrating Zoom video capabilities into existing C++ applications.
- When implementing custom media processing or injection pipelines.
Advantages
- Enables full control over raw audio and video streams.
- Supports headless operation via Docker and virtual device injection.
- Provides a consistent singleton-based API for session management.
- Includes comprehensive documentation for Linux-specific environment setup.
Usage examples
- “How do I join a session using the Zoom Video SDK on Linux?”
- The agent provides the session join pattern, including initializing the SDK, setting up delegates, and configuring the session context with necessary credentials and virtual device parameters.
- “Why is my video not rendering in my Linux Zoom application?”
- The agent explains that Linux lacks a Canvas API and guides the user to implement a raw data pipe to process and render YUV420 frames manually.
- “How do I configure audio for a headless bot on Linux?”
- The agent provides instructions for installing PulseAudio, configuring the zoomus.conf file, and initializing virtual audio speakers and microphones in the session context.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/openai/zoom-video-sdk-linux/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip zoom-video-sdk-linux.zip -d .agents/skills
Documentation by Zoom
Zoom Video SDK - Linux Development
Expert guidance for developing with the Zoom Video SDK on Linux. Build headless bots, raw media capture/injection applications, and custom UI integrations with Qt/GTK.
Official Documentation: https://developers.zoom.us/docs/video-sdk/linux/ API Reference: https://marketplacefront.zoom.us/sdk/custom/linux/ Sample Repository: https://github.com/zoom/videosdk-linux-raw-recording-sample
Quick Links
New to Video SDK? Follow this path:
SDK Architecture Pattern - Universal 3-step pattern for ANY feature
Session Join Pattern - Complete working code to join a session
Raw Data vs Canvas - CRITICAL: Linux has NO Canvas API - raw data ONLY
Raw Video Capture - Capture and process YUV420 frames
Reference:
Singleton Hierarchy - 5-level SDK navigation map
API Reference - Complete API documentation
Qt/GTK Integration - UI framework patterns
Troubleshooting - Quick diagnostics
SKILL.md - Complete documentation navigation
Having issues?
PulseAudio setup → PulseAudio Guide
Qt dependencies → Qt Dependencies
Build errors → Build Errors Guide
Key Differences from Windows/macOS
| Feature | Linux | Windows/Mac |
|---|---|---|
| Canvas API | ❌ Not available | ✅ Available |
| Raw Data Pipe | ✅ ONLY option | ✅ Available |
| UI Integration | Qt, GTK, SDL2, OpenGL | Win32/WinForms/WPF, Cocoa |
| Headless Support | ✅ Excellent (Docker) | Limited |
| Audio | PulseAudio required | Native |
| Virtual Devices | ✅ Required for headless | Optional |
SDK Overview
The Zoom Video SDK for Linux is a C++ library optimized for:
Headless Bots: Docker/WSL support, no display required
Raw Data Access: Capture YUV420 video, PCM audio
Raw Data Injection: Virtual camera/mic for custom media
Screen Sharing: Capture or inject share data
Cloud Recording: Record sessions to Zoom cloud
Live Streaming: Stream to RTMP endpoints
Live Transcription: Real-time speech-to-text
Qt/GTK Integration: Full UI framework support
Prerequisites
System Requirements
OS: Ubuntu 20.04+, Debian 11+, or compatible
Architecture: x64 (recommended), ARM64
Compiler: GCC 9+, Clang 10+
CMake: 3.14 or later
Qt5: Bundled with SDK (do NOT install system Qt5)
Dependencies
sudo apt update
sudo apt install -y build-essential gcc cmake libglib2.0-dev liblzma-dev \
libxcb-image0 libxcb-keysyms1 libxcb-xfixes0 libxcb-xkb1 libxcb-shape0 \
libxcb-shm0 libxcb-randr0 libxcb-xtest0 libgbm1 libxtst6 libgl1 libnss3 \
libasound2 libpulse0
# For headless Linux
sudo apt install -y pulseaudio
# PulseAudio configuration (CRITICAL for audio)
mkdir -p ~/.config
echo "[General]" > ~/.config/zoomus.conf
echo "system.audio.type=default" >> ~/.config/zoomus.conf
# Log directory
mkdir -p ~/.zoom/logsQuick Start
#include "zoom_video_sdk_api.h"
#include "zoom_video_sdk_interface.h"
#include "zoom_video_sdk_delegate_interface.h"
USING_ZOOM_VIDEO_SDK_NAMESPACE
// 1. Create SDK
IZoomVideoSDK* sdk = CreateZoomVideoSDKObj();
// 2. Initialize
ZoomVideoSDKInitParams init_params;
init_params.domain = "https://zoom.us";
init_params.enableLog = true;
init_params.logFilePrefix = "bot";
init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
sdk->initialize(init_params);
// 3. Add delegate
sdk->addListener(myDelegate);
// 4. Join session
ZoomVideoSDKSessionContext ctx;
ctx.sessionName = "my-session";
ctx.userName = "Linux Bot";
ctx.token = "jwt-token";
ctx.audioOption.connect = true;
ctx.audioOption.mute = false;
ctx.videoOption.localVideoOn = false;
// For headless: Virtual audio speaker
ctx.virtualAudioSpeaker = new VirtualSpeaker();
IZoomVideoSDKSession* session = sdk->joinSession(ctx);See Session Join Pattern for complete code.
Key Features
| Feature | Linux Support | Guide |
|---|---|---|
| Session Management | ✅ Full | Session Join |
| Raw Video (YUV420) | ✅ ONLY rendering option | Raw Video |
| Raw Audio (PCM) | ✅ Full | Raw Audio |
| Virtual Camera/Mic | ✅ Full | Virtual Devices |
| Cloud Recording | ✅ Full | Recording |
| Live Streaming | ✅ Full | Live Stream |
| Live Transcription | ✅ Full | Transcription |
| Command Channel | ✅ Full | Commands |
| Chat | ✅ Full | Chat |
| Qt Integration | ✅ Recommended | Qt/GTK |
| GTK Integration | ✅ Supported | Qt/GTK |
| Docker/Headless | ✅ Excellent | Virtual Devices |
Critical Gotchas
⚠️ CRITICAL #1: No Canvas API on Linux
Problem: Linux SDK does NOT have Canvas API like Windows/Mac.
Solution: You MUST use Raw Data Pipe and implement your own rendering.
See: Raw Data vs Canvas
⚠️ CRITICAL #2: PulseAudio Required for Audio
Problem: SDK requires PulseAudio for raw audio functions.
Solution:
sudo apt install -y pulseaudio
mkdir -p ~/.config
echo "[General]" > ~/.config/zoomus.conf
echo "system.audio.type=default" >> ~/.config/zoomus.confSee: PulseAudio Setup
⚠️ CRITICAL #3: Qt5 Dependencies
Problem: SDK requires Qt5 libraries (bundled, NOT system Qt5).
Solution:
# Copy from SDK package
cp -r samples/qt_libs/Qt/lib/* lib/zoom_video_sdk/
# Create symlinks
cd lib/zoom_video_sdk
for lib in libQt5*.so.5; do ln -sf $lib ${lib%.5}; doneSee: Qt Dependencies
⚠️ CRITICAL #4: Heap Memory Mode
Always use heap mode for raw data:
init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;⚠️ CRITICAL #5: Virtual Audio for Headless
Problem: Docker/headless environments have no audio devices.
Solution: Use virtual audio speaker and mic.
session_context.virtualAudioSpeaker = new VirtualSpeaker();
session_context.virtualAudioMic = new VirtualMic();See: Virtual Audio/Video
Sample Repositories
Official Samples
| Repository | Description |
|---|---|
| raw-recording-sample | Raw audio/video capture |
| qt-quickstart | Qt6 UI integration |
| gtk-quickstart | GTK3 UI integration |
Sample Architecture
Headless Bot (Docker):
┌──────────────────────────────────┐
│ Virtual Audio Speaker/Mic │
├──────────────────────────────────┤
│ Raw Data Processing │
│ - YUV420 → File/Stream
## Merged from video-sdk/linux/SKILL.md
# Zoom Video SDK Linux - 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](concepts/sdk-architecture-pattern.md)
- Universal formula: Singleton → Delegate → Subscribe
- Once you understand this, you can implement any feature
2. **Understand Linux specifics** → [concepts/raw-data-vs-canvas.md](concepts/raw-data-vs-canvas.md)
- **CRITICAL**: Linux has NO Canvas API - raw data ONLY
3. **Implement session join** → [examples/session-join-pattern.md](examples/session-join-pattern.md)
- Complete working JWT + session join code
4. **Setup environment** → [troubleshooting/pulseaudio-setup.md](troubleshooting/pulseaudio-setup.md)
- PulseAudio configuration (required for audio)
- [troubleshooting/qt-dependencies.md](troubleshooting/qt-dependencies.md)
- Qt5 library setup (bundled with SDK)
5. **Implement features** → Choose from examples below
---
## Documentation Structure
video-sdk/linux/ ├── SKILL.md # Main skill overview ├── SKILL.md # This file - navigation guide ├── linux.md # Platform summary │ ├── concepts/ # Core architectural patterns │ ├── sdk-architecture-pattern.md # Universal formula for ANY feature │ ├── singleton-hierarchy.md # 5-level navigation guide │ └── raw-data-vs-canvas.md # Linux-specific: raw data ONLY │ ├── examples/ # Complete working code │ ├── session-join-pattern.md # JWT auth + session join │ └── command-channel.md # Command channel with threading │ ├── troubleshooting/ # Problem solving guides │ ├── pulseaudio-setup.md # Audio configuration │ ├── qt-dependencies.md # Qt5 library setup │ ├── build-errors.md # Common build issues │ └── common-issues.md # Quick diagnostic workflow │ └── references/ # Reference documentation └── linux-reference.md # API hierarchy, methods, error codes
---
## By Use Case
### I want to build a headless bot
1. [SDK Architecture Pattern](concepts/sdk-architecture-pattern.md) - Understand the pattern
2. [Session Join Pattern](examples/session-join-pattern.md) - Join sessions
3. [PulseAudio Setup](troubleshooting/pulseaudio-setup.md) - Configure audio
4. [Raw Data vs Canvas](concepts/raw-data-vs-canvas.md) - Understand Linux differences
### I'm getting build errors
1. [Build Errors Guide](troubleshooting/build-errors.md) - SDK build issues
2. [Qt Dependencies](troubleshooting/qt-dependencies.md) - Qt5 setup
3. [Common Issues](troubleshooting/common-issues.md) - Quick diagnostics
### I'm getting runtime errors
1. [PulseAudio Setup](troubleshooting/pulseaudio-setup.md) - Audio not working
2. [Qt Dependencies](troubleshooting/qt-dependencies.md) - Library not found
3. [Common Issues](troubleshooting/common-issues.md) - Error code tables
### I want to use command channel
1. [Command Channel](examples/command-channel.md) - Send/receive commands
2. [Common Issues](troubleshooting/common-issues.md) - Threading requirements
### I want to implement a specific feature
1. [SDK Architecture Pattern](concepts/sdk-architecture-pattern.md) - **START HERE!**
2. [Singleton Hierarchy](concepts/singleton-hierarchy.md) - Navigate to the feature
3. [API Reference](references/linux-reference.md) - Method signatures
---
## Most Critical Documents
### 1. SDK Architecture Pattern (MASTER DOCUMENT)
**[concepts/sdk-architecture-pattern.md](concepts/sdk-architecture-pattern.md)**
The universal 3-step pattern:
1. Get singleton (SDK, helpers, session, users)
2. Implement delegate (event callbacks)
3. Subscribe and use
### 2. Raw Data vs Canvas (LINUX-SPECIFIC)
**[concepts/raw-data-vs-canvas.md](concepts/raw-data-vs-canvas.md)**
**CRITICAL**: Unlike Windows/Mac, Linux SDK has NO Canvas API. You MUST use raw data pipe.
### 3. PulseAudio Setup (MOST COMMON ISSUE)
**[troubleshooting/pulseaudio-setup.md](troubleshooting/pulseaudio-setup.md)**
Audio requires PulseAudio configuration.
### 4. Qt Dependencies
**[troubleshooting/qt-dependencies.md](troubleshooting/qt-dependencies.md)**
SDK requires bundled Qt5 libraries, NOT system Qt5.
---
## Key Learnings
### Critical Discoveries:
1. **Linux has NO Canvas API**
- Windows/Mac have Canvas API for SDK-rendered video
- Linux MUST use Raw Data Pipe
- See: [Raw Data vs Canvas](concepts/raw-data-vs-canvas.md)
2. **PulseAudio is MANDATORY**
- SDK requires PulseAudio for raw audio
- Must configure ~/.config/zoomus.conf
- See: [PulseAudio Setup](troubleshooting/pulseaudio-setup.md)
3. **Use Bundled Qt5, NOT System Qt5**
- SDK includes specific Qt5 versions
- Copy from samples/qt_libs/
- See: [Qt Dependencies](troubleshooting/qt-dependencies.md)
4. **Helpers Control YOUR Streams Only**
- `videoHelper->startVideo()` starts YOUR camera
- To see others, subscribe to their VideoPipe
- See: [Singleton Hierarchy](concepts/singleton-hierarchy.md)
5. **Virtual Devices for Headless**
- Docker/headless needs virtual audio speaker/mic
- Set before joining session
- See: [Session Join Pattern](examples/session-join-pattern.md)
6. **Always Use Heap Memory Mode**
```cpp
init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;GLib Main Loop Required
while/sleep loops don't dispatch SDK events
Must use g_main_loop_run()
See: Common Issues
All SDK Calls Must Be on Main Thread
Background thread SDK calls return error 2 (Internal_Error)
Use g_idle_add() to schedule on GLib main thread
See: Command Channel
Command Channel is Session-Scoped
Does NOT span across different sessions
Both sender and receiver must be in the same session
See: Command Channel
Sample Repositories
raw-recording-sample - Official raw data sample
qt-quickstart - Qt6 UI integration
gtk-quickstart - GTK3 UI integration
Quick Reference
"My code won't compile"
"Audio not working"
"Library not found"
"How do I implement [feature]?"
"What error code means what?"
Document Version
Based on Zoom Video SDK for Linux v2.x
Happy coding!
Remember: The SDK Architecture Pattern is your key to unlocking the entire SDK. Read it first!
Operations
RUNBOOK.md - 5-minute preflight and debugging checklist.
Files
- ATTRIBUTION.mdAdded by AutoGineer882 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md2.2 KiB
- SKILL.md15.3 KiB
- agents/openai.yaml246 B
- concepts/raw-data-vs-canvas.md16.4 KiB
- concepts/sdk-architecture-pattern.md14.7 KiB
- concepts/singleton-hierarchy.md15.0 KiB
- examples/chat.md215 B
- examples/cloud-recording.md395 B
- examples/command-channel.md9.7 KiB
- examples/live-streaming.md234 B
- examples/qt-gtk-integration.md261 B
- examples/raw-audio-capture.md215 B
- examples/raw-video-capture.md251 B
- examples/session-join-pattern.md21.2 KiB
- examples/transcription.md255 B
- examples/virtual-audio-video.md279 B
- linux.md25.7 KiB
- references/linux-reference.md26.7 KiB
- troubleshooting/build-errors.md2.0 KiB
- troubleshooting/common-issues.md5.3 KiB
- troubleshooting/pulseaudio-setup.md1.7 KiB
- troubleshooting/qt-dependencies.md1.9 KiB
concepts/raw-data-vs-canvas.md
Raw Data - The ONLY Rendering Option on Linux
Linux vs Windows/Mac: Key Difference
CRITICAL: Unlike Windows and macOS, Linux SDK does NOT have Canvas API.
| Platform | Canvas API | Raw Data Pipe |
|---|---|---|
| Windows | ✅ Yes (SDK renders to HWND) | ✅ Yes (YUV420 frames) |
| macOS | ✅ Yes (SDK renders to NSView) | ✅ Yes (YUV420 frames) |
| Linux | ❌ NO | ✅ ONLY OPTION (YUV420 frames) |
What this means: On Linux, you MUST use the Raw Data Pipe and implement your own rendering. There is no built-in rendering like on Windows/Mac.
What is Raw Data?
Raw data is uncompressed, unprocessed media data:
Video: YUV420 (I420) format - separate Y, U, V planes
Audio: PCM 16-bit format - raw audio samples
Share: YUV420 format (same as video)
YUV420 (I420) Format
Y Plane (Luminance - full resolution):
[Y Y Y Y Y Y Y Y]
[Y Y Y Y Y Y Y Y]
[Y Y Y Y Y Y Y Y]
[Y Y Y Y Y Y Y Y]
U Plane (Chrominance - 1/4 resolution):
[U U U U]
[U U U U]
V Plane (Chrominance - 1/4 resolution):
[V V V V]
[V V V V]Why YUV420?
Efficient: 12 bits per pixel (vs 24 for RGB)
Standard: Used by video codecs (H.264, VP8, VP9)
Human vision: Less sensitive to color than brightness
PCM Audio Format
PCM 16-bit Mono (32kHz):
Sample Rate: 32000 Hz
Bit Depth: 16 bits per sample
Channels: 1 (mono)
Buffer: char* array of signed 16-bit integersRaw Data Pipe Architecture
Receive Pattern
// 1. Implement delegate
class VideoRenderer : public IZoomVideoSDKRawDataPipeDelegate {
public:
void onRawDataFrameReceived(YUVRawDataI420* data) override {
// Receive YUV420 frame
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
char* yBuffer = data->GetYBuffer();
char* uBuffer = data->GetUBuffer();
char* vBuffer = data->GetVBuffer();
// Convert YUV to RGB
unsigned char* rgbBuffer = ConvertYUVToRGB(yBuffer, uBuffer, vBuffer, width, height);
// Render with Qt/GTK/SDL/OpenGL
RenderFrame(rgbBuffer, width, height);
}
void onRawDataStatusChanged(RawDataStatus status) override {
if (status == RawData_On) {
printf("Video started\n");
} else {
printf("Video stopped\n");
}
}
};
// 2. Subscribe to user's video
IZoomVideoSDKUser* user = /* from callback */;
IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe();
pipe->subscribe(ZoomVideoSDKResolution_720P, new VideoRenderer());Send Pattern (Virtual Devices)
// 1. Implement virtual video source
class VirtualCamera : public IZoomVideoSDKVideoSource {
IZoomVideoSDKVideoSender* sender_;
public:
void onInitialize(IZoomVideoSDKVideoSender* sender,
IVideoSDKVector<VideoSourceCapability>* caps,
VideoSourceCapability& suggest) override {
sender_ = sender;
}
void onStartSend() override {
// Start sending frames
while (sending_) {
// Load or generate YUV420 frame
char* yBuffer = /* Y plane */;
char* uBuffer = /* U plane */;
char* vBuffer = /* V plane */;
sender_->sendVideoFrame(yBuffer, uBuffer, vBuffer,
width, height, 0, rotation);
std::this_thread::sleep_for(std::chrono::milliseconds(33)); // ~30 FPS
}
}
void onStopSend() override {
sending_ = false;
}
void onPropertyChange(...) override {}
void onUninitialized() override { sender_ = nullptr; }
private:
bool sending_ = false;
};
// 2. Set before joining
session_context.externalVideoSource = new VirtualCamera();YUV to RGB Conversion
Required for rendering: Most UI frameworks expect RGB/RGBA, not YUV.
ITU-R BT.601 Formula
void ConvertYUV420ToRGB(char* yBuffer, char* uBuffer, char* vBuffer,
int width, int height, unsigned char* rgbBuffer) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int yIndex = y * width + x;
int uvIndex = (y / 2) * (width / 2) + (x / 2);
int Y = (unsigned char)yBuffer[yIndex];
int U = (unsigned char)uBuffer[uvIndex];
int V = (unsigned char)vBuffer[uvIndex];
// YUV to RGB conversion
int C = Y - 16;
int D = U - 128;
int E = V - 128;
int R = (298 * C + 409 * E + 128) >> 8;
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
int B = (298 * C + 516 * D + 128) >> 8;
// Clamp to [0, 255]
R = std::max(0, std::min(255, R));
G = std::max(0, std::min(255, G));
B = std::max(0, std::min(255, B));
// Store RGB
int rgbIndex = yIndex * 3;
rgbBuffer[rgbIndex + 0] = (unsigned char)R;
rgbBuffer[rgbIndex + 1] = (unsigned char)G;
rgbBuffer[rgbIndex + 2] = (unsigned char)B;
}
}
}Optimized with libyuv (Recommended)
#include <libyuv/convert.h>
void ConvertYUV420ToRGB(char* yBuffer, char* uBuffer, char* vBuffer,
int width, int height, unsigned char* rgbBuffer) {
libyuv::I420ToRGB24(
(const uint8_t*)yBuffer, width,
(const uint8_t*)uBuffer, width / 2,
(const uint8_t*)vBuffer, width / 2,
rgbBuffer, width * 3,
width, height
);
}Install libyuv:
sudo apt install -y libyuv-devRendering Options
Option 1: Qt (Recommended for Cross-Platform)
class QtVideoWidget : public QWidget, public IZoomVideoSDKRawDataPipeDelegate {
Q_OBJECT
signals:
void frameReceived(QImage frame);
public:
QtVideoWidget(QWidget* parent = nullptr) : QWidget(parent) {
connect(this, &QtVideoWidget::frameReceived,
this, &QtVideoWidget::updateFrame);
}
void onRawDataFrameReceived(YUVRawDataI420* data) override {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Convert YUV to RGB
unsigned char* rgbBuffer = new unsigned char[width * height * 3];
ConvertYUV420ToRGB(data->GetYBuffer(), data->GetUBuffer(), data->GetVBuffer(),
width, height, rgbBuffer);
// Create QImage (takes ownership of buffer)
QImage img(rgbBuffer, width, height, width * 3, QImage::Format_RGB888,
[](void* ptr) { delete[] (unsigned char*)ptr; }, rgbBuffer);
// Emit signal (thread-safe)
emit frameReceived(img.copy());
}
void onRawDataStatusChanged(RawDataStatus status) override {}
private slots:
void updateFrame(QImage img) {
pixmap_ = QPixmap::fromImage(img);
update(); // Trigger repaint
}
protected:
void paintEvent(QPaintEvent*) override {
if (!pixmap_.isNull()) {
QPainter painter(this);
painter.drawPixmap(rect(), pixmap_);
}
}
private:
QPixmap pixmap_;
};Option 2: GTK with Cairo
class GtkVideoRenderer : public IZoomVideoSDKRawDataPipeDelegate {
public:
GtkVideoRenderer(GtkWidget* drawingArea) : drawing_area_(drawingArea) {
g_signal_connect(drawing_area_, "draw", G_CALLBACK(on_draw_static), this);
}
void onRawDataFrameReceived(YUVRawDataI420* data) override {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Allocate RGB buffer
std::lock_guard<std::mutex> lock(mutex_);
if (rgb_buffer_) delete[] rgb_buffer_;
rgb_buffer_ = new unsigned char[width * height * 3];
width_ = width;
height_ = height;
// Convert YUV to RGB
ConvertYUV420ToRGB(data->GetYBuffer(), data->GetUBuffer(), data->GetVBuffer(),
width, height, rgb_buffer_);
// Trigger redraw on main thread
g_idle_add([](gpointer user_data) {
gtk_widget_queue_draw((GtkWidget*)user_data);
return G_SOURCE_REMOVE;
}, drawing_area_);
}
void onRawDataStatusChanged(RawDataStatus status) override {}
private:
static gboolean on_draw_static(GtkWidget* widget, cairo_t* cr, gpointer user_data) {
return ((GtkVideoRenderer*)user_data)->on_draw(widget, cr);
}
gboolean on_draw(GtkWidget* widget, cairo_t* cr) {
std::lock_guard<std::mutex> lock(mutex_);
if (!rgb_buffer_) return FALSE;
// Create Cairo surface from RGB buffer
cairo_surface_t* surface = cairo_image_surface_create_for_data(
rgb_buffer_,
CAIRO_FORMAT_RGB24,
width_, height_,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, width_)
);
// Scale to widget size
int widget_width = gtk_widget_get_allocated_width(widget);
int widget_height = gtk_widget_get_allocated_height(widget);
cairo_scale(cr,
(double)widget_width / width_,
(double)widget_height / height_);
// Draw
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_surface_destroy(surface);
return TRUE;
}
GtkWidget* drawing_area_;
unsigned char* rgb_buffer_ = nullptr;
int width_ = 0;
int height_ = 0;
std::mutex mutex_;
};Option 3: SDL2 (Lightweight)
class SDL2VideoRenderer : public IZoomVideoSDKRawDataPipeDelegate {
public:
SDL2VideoRenderer() {
SDL_Init(SDL_INIT_VIDEO);
window_ = SDL_CreateWindow("Zoom Video",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1280, 720, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
renderer_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED);
}
~SDL2VideoRenderer() {
if (texture_) SDL_DestroyTexture(texture_);
if (renderer_) SDL_DestroyRenderer(renderer_);
if (window_) SDL_DestroyWindow(window_);
SDL_Quit();
}
void onRawDataFrameReceived(YUVRawDataI420* data) override {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// Create texture if needed
if (!texture_ || width_ != width || height_ != height) {
if (texture_) SDL_DestroyTexture(texture_);
texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING, width, height);
width_ = width;
height_ = height;
}
// Update texture with YUV data (no conversion needed!)
SDL_UpdateYUVTexture(texture_, nullptr,
(Uint8*)data->GetYBuffer(), width,
(Uint8*)data->GetUBuffer(), width / 2,
(Uint8*)data->GetVBuffer(), width / 2);
// Render
SDL_RenderClear(renderer_);
SDL_RenderCopy(renderer_, texture_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
void onRawDataStatusChanged(RawDataStatus status) override {}
private:
SDL_Window* window_ = nullptr;
SDL_Renderer* renderer_ = nullptr;
SDL_Texture* texture_ = nullptr;
int width_ = 0;
int height_ = 0;
};Advantage: SDL2 supports YUV textures natively - no RGB conversion needed!
Option 4: OpenGL (High Performance)
class OpenGLVideoRenderer : public IZoomVideoSDKRawDataPipeDelegate {
public:
void onRawDataFrameReceived(YUVRawDataI420* data) override {
// Upload YUV planes as textures
glBindTexture(GL_TEXTURE_2D, yTexture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
data->GetYBuffer());
glBindTexture(GL_TEXTURE_2D, uTexture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
width/2, height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
data->GetUBuffer());
glBindTexture(GL_TEXTURE_2D, vTexture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
width/2, height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
data->GetVBuffer());
// Use shader to convert YUV to RGB on GPU
RenderWithYUVShader();
}
void onRawDataStatusChanged(RawDataStatus status) override {}
private:
GLuint yTexture_, uTexture_, vTexture_;
};Performance Considerations
CPU vs GPU Conversion
| Method | Performance | Complexity |
|---|---|---|
| Manual CPU | Slowest | Simple |
| libyuv (SIMD) | Fast | Simple |
| SDL2 YUV texture | Very Fast | Simple |
| OpenGL shader | Fastest | Complex |
Recommendation:
Simple apps: libyuv
Cross-platform: Qt + libyuv
Performance-critical: SDL2 or OpenGL
Memory Management
CRITICAL: YUV frames can be large. Use heap mode and manage memory carefully.
// Initialize with heap mode
init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
// Reference counting for async processing
void onRawDataFrameReceived(YUVRawDataI420* data) override {
if (data->CanAddRef()) {
data->AddRef();
// Queue for background processing
processing_queue_.push(data);
// Later, after processing
data->Release();
}
}Frame Rate Control
class ThrottledRenderer : public IZoomVideoSDKRawDataPipeDelegate {
auto last_frame_ = std::chrono::steady_clock::now();
const int target_fps_ = 30;
void onRawDataFrameReceived(YUVRawDataI420* data) override {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_frame_);
// Throttle to target FPS
if (elapsed.count() < (1000 / target_fps_)) {
return; // Skip frame
}
last_frame_ = now;
RenderFrame(data);
}
};Audio Raw Data
Receive Audio
// In IZoomVideoSDKDelegate
void onMixedAudioRawDataReceived(AudioRawData* data) override {
char* buffer = data->GetBuffer(); // PCM 16-bit
unsigned int len = data->GetBufferLen(); // Bytes
unsigned int sampleRate = data->GetSampleRate(); // Hz
unsigned int channels = data->GetChannelNum(); // 1=mono, 2=stereo
// Play or save audio
PlayAudio(buffer, len, sampleRate, channels);
}
void onOneWayAudioRawDataReceived(AudioRawData* data, IZoomVideoSDKUser* user) override {
// Per-user audio
}Send Audio (Virtual Mic)
class VirtualMic : public IZoomVideoSDKVirtualAudioMic {
IZoomVideoSDKAudioSender* sender_;
void onMicInitialize(IZoomVideoSDKAudioSender* sender) override {
sender_ = sender;
}
void onMicStartSend() override {
// Load PCM audio file
char* audioBuffer = LoadPCMAudio("audio.pcm");
int length = GetAudioLength();
int sampleRate = 32000;
// Send in chunks
int chunkSize = sampleRate * 2 / 100; // 10ms chunks (16-bit = 2 bytes)
for (int offset = 0; offset < length; offset += chunkSize) {
sender_->Send(audioBuffer + offset, chunkSize, sampleRate);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void onMicStopSend() override {}
void onMicUninitialized() override { sender_ = nullptr; }
};Summary
| Feature | Linux SDK |
|---|---|
| Canvas API | ❌ Not available |
| Raw Data Pipe | ✅ ONLY rendering option |
| YUV to RGB | ✅ Required for most UIs |
| Qt Integration | ✅ Recommended |
| GTK Integration | ✅ Supported |
| SDL2 | ✅ Best performance |
| OpenGL | ✅ Maximum performance |
| Virtual Devices | ✅ For custom media injection |
| Memory Mode | ✅ Always use Heap |
Key Takeaway: Linux requires manual rendering. Choose your rendering framework (Qt, GTK, SDL2, OpenGL) and implement YUV to RGB conversion.
See Also
SDK Architecture Pattern - Universal pattern
Raw Video Capture - Complete capture example
Raw Audio Capture - Audio capture example
Qt/GTK Integration - UI framework integration
Virtual Audio/Video - Custom media injection
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.
Security
No issues found by our static scan· No issues found
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/video-sdk/linux · 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-video-sdk-linux.zip