Setting the file. One moment.
Skill 126 · Zoom Meeting SDK Windows
Subchapter 126.16
examples/send-raw-data.mdMarkdown13 KBView on GitHub
Send custom video and audio into a Zoom meeting as a virtual camera or microphone. This enables:
The SDK provides three “send” interfaces:
All follow the same pattern:
#include <rawdata/rawdata_video_source_helper_interface.h>
class ZoomSDKVideoSource : public IZoomSDKVideoSource {
private:
IZoomSDKVideoSender* video_sender_ = nullptr;
string video_source_;
public:
ZoomSDKVideoSource(string video_source) : video_source_(video_source) {}
void onInitialize(IZoomSDKVideoSender* sender,
IList<VideoSourceCapability>* support_cap_list,
VideoSourceCapability& suggest_cap) override {
// Store the sender - you'll use this to send frames
video_sender_ = sender;
std::cout << "Video source initialized" << std::endl;
}
void onPropertyChange(IList<VideoSourceCapability>* support_cap_list,
VideoSourceCapability suggest_cap) override {
// SDK suggests resolution/framerate based on network
std::cout << "Suggested: " << suggest_cap.width << "x"
<< suggest_cap.height << " @ " << suggest_cap.frame << "fps" << std::endl;
}
void onStartSend() override {
// SDK is ready - start sending frames in a separate thread
std::thread([this]() { PlayVideo(); }).detach();
}
void onStopSend() override {
// Stop sending frames
running_ = false;
}
void onUninitialized() override {
video_sender_ = nullptr;
}
private:
bool running_ = false;
void PlayVideo() {
running_ = true;
// Open video file with OpenCV
cv::VideoCapture cap(video_source_);
if (!cap.isOpened()) {
std::cerr << "Failed to open video: " << video_source_ << std::endl;
return;
}
while (running_ && video_sender_) {
cv::Mat frame;
if (!cap.read(frame)) {
cap.set(cv::CAP_PROP_POS_FRAMES, 0); // Loop video
continue;
}
// Convert BGR to YUV420 (I420)
cv::Mat yuv;
cv::cvtColor(frame, yuv, cv::COLOR_BGR2YUV_I420);
// Send frame
char* buffer = (char*)yuv.data;
int width = frame.cols;
int height = frame.rows;
int frameLen = yuv.total() * yuv.elemSize();
SDKError err = video_sender_->sendVideoFrame(buffer, width, height, frameLen, 0);
if (err != SDKERR_SUCCESS) {
std::cerr << "sendVideoFrame failed: " << err << std::endl;
}
// Control framerate (e.g., 30fps = 33ms per frame)
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
}
};// Create your video source
ZoomSDKVideoSource* videoSource = new ZoomSDKVideoSource("my_video.mp4");
// Get the raw data helper
IZoomSDKVideoSourceHelper* videoSourceHelper = GetRawdataVideoSourceHelper();
// Set as external video source
videoSourceHelper->setExternalVideoSource(videoSource);
// Join meeting - your video will be the "camera"
meetingService->Join(joinParam);YUV420 Layout (1920x1080 example):
┌────────────────────────┐
│ Y plane │ 1920 x 1080 = 2,073,600 bytes
│ (luminance) │
├────────────────────────┤
│ U plane (Cb) │ 960 x 540 = 518,400 bytes
├────────────────────────┤
│ V plane (Cr) │ 960 x 540 = 518,400 bytes
└────────────────────────┘
Total: width * height * 1.5 = 3,110,400 bytes#include <rawdata/rawdata_audio_helper_interface.h>
class ZoomSDKVirtualAudioMic : public IZoomSDKVirtualAudioMicEvent {
private:
IZoomSDKAudioRawDataSender* audio_sender_ = nullptr;
string audio_source_;
bool running_ = false;
public:
ZoomSDKVirtualAudioMic(string audio_source) : audio_source_(audio_source) {}
void onMicInitialize(IZoomSDKAudioRawDataSender* pSender) override {
audio_sender_ = pSender;
std::cout << "Virtual mic initialized" << std::endl;
}
void onMicStartSend() override {
// SDK is ready - start sending audio
std::thread([this]() { PlayAudio(); }).detach();
}
void onMicStopSend() override {
running_ = false;
}
void onMicUninitialized() override {
audio_sender_ = nullptr;
}
private:
void PlayAudio() {
running_ = true;
// Open WAV file
std::ifstream file(audio_source_, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Failed to open audio: " << audio_source_ << std::endl;
return;
}
// Skip WAV header (44 bytes for standard WAV)
file.seekg(44, std::ios::beg);
// Audio parameters (must match your WAV file!)
const int sampleRate = 48000; // 48kHz
const size_t chunkSize = 640; // Send in 640-byte chunks
std::vector<char> buffer(chunkSize);
while (running_ && audio_sender_ && file.good()) {
file.read(buffer.data(), chunkSize);
std::streamsize bytesRead = file.gcount();
if (bytesRead > 0) {
// Send audio chunk
SDKError err = audio_sender_->send(
buffer.data(),
bytesRead,
sampleRate,
ZoomSDKAudioChannel_Mono // or ZoomSDKAudioChannel_Stereo
);
if (err != SDKERR_SUCCESS) {
std::cerr << "send audio failed: " << err << std::endl;
}
}
// Control timing based on sample rate and chunk size
// 640 bytes / 2 (16-bit) / 48000 Hz = ~6.67ms
std::this_thread::sleep_for(std::chrono::microseconds(6667));
}
file.close();
}
};// Create your audio source
ZoomSDKVirtualAudioMic* virtualMic = new ZoomSDKVirtualAudioMic("my_audio.wav");
// Get audio helper
IZoomSDKAudioRawDataHelper* audioHelper = GetAudioRawdataHelper();
// Set as virtual mic
audioHelper->setExternalAudioSource(virtualMic);
// Join meeting - your audio will be the "microphone"
meetingService->Join(joinParam);Required format:
- Encoding: PCM (signed 16-bit little-endian)
- Sample rates: 8000, 16000, 32000, 44100, or 48000 Hz
- Channels: Mono (1) or Stereo (2)
WAV File Structure:
┌──────────────────┐
│ RIFF Header │ 44 bytes (skip this)
├──────────────────┤
│ Audio Data │ PCM samples
│ (16-bit signed) │
└──────────────────┘#include <rawdata/rawdata_share_source_helper_interface.h>
class ZoomSDKShareSource : public IZoomSDKShareSource {
private:
IZoomSDKShareSender* share_sender_ = nullptr;
public:
void onShareSendStarted(IZoomSDKShareSender* pSender) override {
share_sender_ = pSender;
std::thread([this]() { SendShareFrames(); }).detach();
}
void onShareSendStopped() override {
share_sender_ = nullptr;
}
private:
void SendShareFrames() {
while (share_sender_) {
// Capture or generate your share content
cv::Mat frame = CaptureMyContent();
// Convert to YUV420
cv::Mat yuv;
cv::cvtColor(frame, yuv, cv::COLOR_BGR2YUV_I420);
char* buffer = (char*)yuv.data;
int width = frame.cols;
int height = frame.rows;
int frameLen = yuv.total() * yuv.elemSize();
share_sender_->sendShareFrame(buffer, width, height, frameLen);
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
}
};// Get share helper
IMeetingShareController* shareCtrl = meetingService->GetMeetingShareController();
// Create share source
ZoomSDKShareSource* shareSource = new ZoomSDKShareSource();
// Start sharing with your source
shareCtrl->StartShareWithPreviewEnabled(shareSource);#include <windows.h>
#include <zoom_sdk.h>
#include <meeting_service_interface.h>
#include <rawdata/rawdata_video_source_helper_interface.h>
#include <opencv2/opencv.hpp>
#include <thread>
using namespace ZOOMSDK;
// Global references
IMeetingService* meetingService = nullptr;
IAuthService* authService = nullptr;
ZoomSDKVideoSource* videoSource = nullptr;
void onInMeeting() {
std::cout << "In meeting - video source active!" << std::endl;
}
void JoinMeeting() {
CreateMeetingService(&meetingService);
// Create video source BEFORE joining
videoSource = new ZoomSDKVideoSource("Big_Buck_Bunny.mp4");
IZoomSDKVideoSourceHelper* helper = GetRawdataVideoSourceHelper();
helper->setExternalVideoSource(videoSource);
// Configure join parameters
JoinParam joinParam;
joinParam.userType = SDK_UT_WITHOUT_LOGIN;
JoinParam4WithoutLogin& params = joinParam.param.withoutloginuserJoin;
params.meetingNumber = 1234567890;
params.psw = L"password";
params.userName = L"Video Bot";
params.isVideoOff = false; // Video ON to use virtual camera
params.isAudioOff = true;
meetingService->SetEvent(new MeetingServiceEventListener(&onInMeeting));
meetingService->Join(joinParam);
}
void SDKAuth() {
CreateAuthService(&authService);
authService->SetEvent(new AuthServiceEventListener(&JoinMeeting));
AuthContext ctx;
ctx.jwt_token = L"your_jwt_token";
authService->SDKAuth(ctx);
}
int main() {
// Initialize
InitParam initParam;
initParam.strWebDomain = L"https://zoom.us";
InitSDK(initParam);
// Start auth flow
SDKAuth();
// Message loop
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Cleanup
CleanUPSDK();
return 0;
}Sending frames in callbacks will block the SDK. Always use a separate thread:
void onStartSend() override {
std::thread([this]() { SendFrames(); }).detach();
}The SDK suggests resolution/framerate in onPropertyChange. Respect these suggestions:
void onPropertyChange(IList<VideoSourceCapability>* caps, VideoSourceCapability suggest) override {
target_width_ = suggest.width;
target_height_ = suggest.height;
target_fps_ = suggest.frame;
}void onStopSend() override {
running_ = false; // Signal thread to stop
}
void onStartSend() override {
running_ = true; // Signal thread to start
std::thread([this]() { SendLoop(); }).detach();
}Using OpenCV:
cv::Mat yuv;
cv::cvtColor(bgrFrame, yuv, cv::COLOR_BGR2YUV_I420);Manual conversion:
// Y = 0.299*R + 0.587*G + 0.114*B
// U = -0.169*R - 0.331*G + 0.500*B + 128
// V = 0.500*R - 0.419*G - 0.081*B + 128| Issue | Cause | Solution |
|---|---|---|
| No video showing | isVideoOff = true in join params | Set isVideoOff = false |
| Frames not sending | Blocking in callback | Use separate thread |
| Wrong colors | BGR instead of YUV | Convert to YUV420 |
| Choppy video | Wrong timing | Match suggested FPS |
| Audio glitches | Wrong sample rate | Use 48000 Hz |
| Audio silent | Wrong chunk size | Use 640 byte chunks |
Local samples demonstrating these concepts:
C:\tempsdk\zoom_meetingsdk_windows_rawdatademos\SendVideoRawData\C:\tempsdk\zoom_meetingsdk_windows_rawdatademos\SendAudioRawData\C:\tempsdk\zoom_meetingsdk_windows_rawdatademos\SendShareScreenRawData\