HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/meeting-sdk/windows/examples/send-raw-data.md
# Send Raw Data (Virtual Camera & Microphone)

Send custom video and audio into a Zoom meeting as a virtual camera or microphone. This enables:
- Streaming pre-recorded video files
- AI-generated video/audio
- Custom video effects
- Screen capture injection
- Audio playback into meetings

---

## Overview

The SDK provides three "send" interfaces:
1. **IZoomSDKVideoSource** - Virtual camera (send video)
2. **IZoomSDKVirtualAudioMicEvent** - Virtual microphone (send audio)
3. **IZoomSDKShareSource** - Virtual share source (send screen share)

All follow the same pattern:
1. Implement the interface
2. Pass it during meeting join/start
3. SDK calls your callbacks when ready to send

---

## Send Video (Virtual Camera)

### Interface: IZoomSDKVideoSource

```cpp
#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));
        }
    }
};
```

### Register Virtual Camera

```cpp
// 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);
```

### Frame Format: YUV420 (I420)

```
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
```

---

## Send Audio (Virtual Microphone)

### Interface: IZoomSDKVirtualAudioMicEvent

```cpp
#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();
    }
};
```

### Register Virtual Microphone

```cpp
// 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);
```

### Audio Format: PCM 16-bit

```
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) │
└──────────────────┘
```

---

## Send Share Screen (Virtual Share)

### Interface: IZoomSDKShareSource

```cpp
#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));
        }
    }
};
```

### Start Virtual Share

```cpp
// Get share helper
IMeetingShareController* shareCtrl = meetingService->GetMeetingShareController();

// Create share source
ZoomSDKShareSource* shareSource = new ZoomSDKShareSource();

// Start sharing with your source
shareCtrl->StartShareWithPreviewEnabled(shareSource);
```

---

## Complete Example: Video Bot

```cpp
#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;
}
```

---

## Key Patterns

### 1. Threading is Required

Sending frames in callbacks will block the SDK. Always use a separate thread:

```cpp
void onStartSend() override {
    std::thread([this]() { SendFrames(); }).detach();
}
```

### 2. Match Frame Rate to Network

The SDK suggests resolution/framerate in `onPropertyChange`. Respect these suggestions:

```cpp
void onPropertyChange(IList<VideoSourceCapability>* caps, VideoSourceCapability suggest) override {
    target_width_ = suggest.width;
    target_height_ = suggest.height;
    target_fps_ = suggest.frame;
}
```

### 3. Handle Stop/Restart Gracefully

```cpp
void onStopSend() override {
    running_ = false;  // Signal thread to stop
}

void onStartSend() override {
    running_ = true;   // Signal thread to start
    std::thread([this]() { SendLoop(); }).detach();
}
```

### 4. YUV420 Conversion

Using OpenCV:
```cpp
cv::Mat yuv;
cv::cvtColor(bgrFrame, yuv, cv::COLOR_BGR2YUV_I420);
```

Manual conversion:
```cpp
// 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
```

---

## Common Issues

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

---

## Related Documentation

- [Raw Video Capture](raw-video-capture.md) - Receiving video (opposite direction)
- [SDK Architecture Pattern](../concepts/sdk-architecture-pattern.md) - Universal 3-step pattern
- [Authentication Pattern](authentication-pattern.md) - Complete auth workflow

---

## Sample Projects

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\`