# Canvas API vs Raw Data Pipe

## The Two Rendering Paths

The Zoom Video SDK provides **two distinct ways** to render video. Your choice affects quality, performance, and capabilities.

```
┌─────────────────────────────────────────────────────────────────┐
│                     RENDERING DECISION                          │
├─────────────────────────────────────────────────────────────────┤
│  Canvas API (SDK-Rendered)     │  Raw Data Pipe (Self-Rendered) │
│  ─────────────────────────────│──────────────────────────────── │
│  SDK renders to your HWND      │  You receive YUV420 frames     │
│  Best quality, zero effort     │  Full control, more work       │
│  Standard video apps           │  AI, effects, recording        │
└─────────────────────────────────────────────────────────────────┘
```

---

## Quick Decision Guide

| Use Case | Recommended Approach |
|----------|---------------------|
| Standard video conferencing UI | **Canvas API** |
| Video grid/gallery layout | **Canvas API** |
| Simple video display | **Canvas API** |
| Custom video effects/filters | Raw Data Pipe |
| AI/ML video processing | Raw Data Pipe |
| Custom recording format | Raw Data Pipe |
| Video compositing | Raw Data Pipe |

**Default recommendation: Canvas API** unless you need frame-level access.

---

## Canvas API (SDK-Rendered)

### How It Works

```
IZoomVideoSDKUser
    └── GetVideoCanvas()
        └── IZoomVideoSDKCanvas
            └── subscribeWithView(HWND, aspect, resolution)
                └── SDK renders directly to your window
                    └── Hardware-accelerated, optimized
```

### Code Example

```cpp
// Get user's canvas
IZoomVideoSDKCanvas* canvas = user->GetVideoCanvas();

// Subscribe - SDK renders to your window
ZoomVideoSDKErrors err = canvas->subscribeWithView(
    hwnd,                                    // Your window handle
    ZoomVideoSDKVideoAspect_PanAndScan,     // Aspect ratio handling
    ZoomVideoSDKResolution_Auto              // Let SDK choose
);

if (err == ZoomVideoSDKErrors_Success) {
    // Done! SDK is now rendering to your window
}

// To stop
canvas->unSubscribeWithView(hwnd);
```

### Aspect Ratio Options

| Option | Behavior |
|--------|----------|
| `ZoomVideoSDKVideoAspect_Original` | Letterbox/pillarbox, no cropping |
| `ZoomVideoSDKVideoAspect_FullFilled` | Fill window, may crop edges |
| `ZoomVideoSDKVideoAspect_PanAndScan` | Smart crop to fill window |
| `ZoomVideoSDKVideoAspect_LetterBox` | Show full video with black bars |

### Resolution Options

| Option | Resolution |
|--------|------------|
| `ZoomVideoSDKResolution_90P` | 160x90 |
| `ZoomVideoSDKResolution_180P` | 320x180 |
| `ZoomVideoSDKResolution_360P` | 640x360 |
| `ZoomVideoSDKResolution_720P` | 1280x720 |
| `ZoomVideoSDKResolution_1080P` | 1920x1080 |
| `ZoomVideoSDKResolution_Auto` | SDK chooses (recommended) |

### Advantages

- **Best quality** - Hardware-accelerated rendering
- **No artifacts** - Professional video quality
- **Simple code** - 3 lines to subscribe
- **Better performance** - No CPU-intensive YUV conversion
- **Automatic scaling** - SDK handles window resizing
- **Aspect ratio** - Built-in handling

### Disadvantages

- No access to raw frames
- Can't apply custom effects
- Can't process video for AI/ML

---

## Raw Data Pipe (Self-Rendered)

### How It Works

```
IZoomVideoSDKUser
    └── GetVideoPipe()
        └── IZoomVideoSDKRawDataPipe
            └── subscribe(resolution, delegate)
                └── Your IZoomVideoSDKRawDataPipeDelegate
                    └── onRawDataFrameReceived(YUVRawDataI420*)
                        └── You convert YUV→RGB and render
```

### Code Example

```cpp
// Implement delegate to receive frames
class VideoRenderer : public IZoomVideoSDKRawDataPipeDelegate {
public:
    void onRawDataFrameReceived(YUVRawDataI420* data) override {
        if (!data) return;
        
        int width = data->GetStreamWidth();
        int height = data->GetStreamHeight();
        
        char* yBuffer = data->GetYBuffer();
        char* uBuffer = data->GetUBuffer();
        char* vBuffer = data->GetVBuffer();
        
        // Convert YUV420 to RGB
        ConvertYUVToRGB(yBuffer, uBuffer, vBuffer, width, height);
        
        // Render to window (GDI, DirectX, OpenGL, etc.)
        RenderToWindow(rgbBuffer, width, height);
    }
    
    void onRawDataStatusChanged(RawDataStatus status) override {
        if (status == RawData_On) {
            std::cout << "Video started" << std::endl;
        } else {
            std::cout << "Video stopped" << std::endl;
        }
    }
};

// Subscribe to raw frames
IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe();
VideoRenderer* renderer = new VideoRenderer();
pipe->subscribe(ZoomVideoSDKResolution_720P, renderer);

// To stop
pipe->unSubscribe(renderer);
```

### YUV420 to RGB Conversion

```cpp
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];
            
            // ITU-R BT.601 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 = (R < 0) ? 0 : (R > 255) ? 255 : R;
            G = (G < 0) ? 0 : (G > 255) ? 255 : G;
            B = (B < 0) ? 0 : (B > 255) ? 255 : B;
            
            // Store as BGR (Windows format)
            rgbBuffer[yIndex * 3 + 0] = (unsigned char)B;
            rgbBuffer[yIndex * 3 + 1] = (unsigned char)G;
            rgbBuffer[yIndex * 3 + 2] = (unsigned char)R;
        }
    }
}
```

### GDI Rendering

```cpp
void RenderToWindow(unsigned char* rgbBuffer, int width, int height, HWND hwnd) {
    HDC hdc = GetDC(hwnd);
    
    BITMAPINFO bmi = {};
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = -height;  // Negative for top-down
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biCompression = BI_RGB;
    
    RECT rect;
    GetClientRect(hwnd, &rect);
    
    StretchDIBits(hdc,
        0, 0, rect.right, rect.bottom,  // Destination
        0, 0, width, height,             // Source
        rgbBuffer, &bmi,
        DIB_RGB_COLORS, SRCCOPY);
    
    ReleaseDC(hwnd, hdc);
}
```

### Advantages

- Full access to raw YUV frames
- Can apply custom video effects
- Can process for AI/ML
- Can record to custom formats
- Can composite multiple streams

### Disadvantages

- **CPU intensive** - YUV conversion can cause frame drops
- **Artifacts** - Manual rendering may show tearing
- **Complex** - More code to maintain
- **Performance** - Slower than Canvas API

---

## Comparison Table

| Aspect | Canvas API | Raw Data Pipe |
|--------|------------|---------------|
| **Complexity** | 3 lines of code | 50+ lines of code |
| **Performance** | Hardware-accelerated | CPU-bound |
| **Quality** | Professional | Depends on implementation |
| **Frame Access** | No | Yes (YUV420) |
| **Custom Effects** | No | Yes |
| **AI Processing** | No | Yes |
| **Recording** | No | Yes |
| **Recommended For** | Standard apps | Advanced processing |

---

## Hybrid Approach

You can use **both** simultaneously:

```cpp
// Use Canvas for display
user->GetVideoCanvas()->subscribeWithView(displayHwnd, aspect, resolution);

// Use Raw Data for processing (different delegate)
user->GetVideoPipe()->subscribe(ZoomVideoSDKResolution_360P, processingDelegate);
```

**Tip**: Use lower resolution for processing to reduce CPU load.

---

## Performance Considerations

### Canvas API
- Zero CPU overhead for rendering
- SDK handles all optimization
- Scales automatically with window resize

### Raw Data Pipe
- YUV conversion: ~5-10ms per frame at 720p
- Memory allocation: Consider pre-allocated buffers
- Threading: Move conversion off UI thread
- Frame drops: Expect some at high resolutions

### Optimization Tips for Raw Data

```cpp
// Pre-allocate buffers
class OptimizedRenderer : public IZoomVideoSDKRawDataPipeDelegate {
    unsigned char* rgbBuffer = nullptr;
    int bufferWidth = 0;
    int bufferHeight = 0;
    
    void onRawDataFrameReceived(YUVRawDataI420* data) override {
        int w = data->GetStreamWidth();
        int h = data->GetStreamHeight();
        
        // Reallocate only if resolution changed
        if (w != bufferWidth || h != bufferHeight) {
            delete[] rgbBuffer;
            rgbBuffer = new unsigned char[w * h * 3];
            bufferWidth = w;
            bufferHeight = h;
        }
        
        // Convert and render
        ConvertYUV420ToRGB(..., rgbBuffer);
        RenderToWindow(rgbBuffer, w, h);
    }
};
```

---

## Related Documentation

- [Video Rendering Example](../examples/video-rendering.md) - Canvas API code
- [Raw Video Capture Example](../examples/raw-video-capture.md) - Raw Data code
- [Singleton Hierarchy](singleton-hierarchy.md) - Canvas/Pipe navigation
- [API Reference](../references/windows-reference.md) - Method details

---

**TL;DR**: Use Canvas API for standard video display. Use Raw Data Pipe only when you need frame-level access for AI, effects, or custom recording.
