WendyOS Docs
Guides & TutorialsC++ Guides

Camera Feed in C++

Start a live camera feed app on WendyOS using the C++ camera template

Live Camera Feed with C++

Use the Wendy camera template when you want a browser-viewable camera stream without wiring the project from scratch. The template includes wendy.json, camera entitlements, a Dockerfile, C++ backend code, and the browser UI.

Prerequisites

  • Wendy CLI installed on your development machine
  • CMake 3.16 or later installed
  • A C++ compiler
  • A WendyOS device with a USB camera connected

Setting Up Your Project

Initialize the Project

wendy init cpp-camera --target wendyos --language cpp --template camera-feed --var APP_ID=cpp-camera --var PORT=7003 --assistant skip --git-init no
cd cpp-camera

The generated project is ready to run. After the first run, use the sections below as a code breakdown when you want to understand or customize camera handling, routes, or UI behavior.

Run on WendyOS

wendy run

Wendy will build the app, ask you to select a device if one is not already configured, deploy the container, and print the app URL.

Code Breakdown

Generated Project

The template creates a small Drogon service with GStreamer camera capture:

cpp-camera/
├── CMakeLists.txt
├── Dockerfile
├── index.html
├── main.cpp
├── assets/
│   └── wendy-logo.svg
└── wendy.json

wendy.json declares the runtime capabilities the app needs:

  • network in host mode so the browser can reach the Drogon server on the WendyOS device
  • camera so the container can access /dev/video*
  • gpu so the app can use accelerated media paths when available
  • readiness.tcpSocket so wendy run waits until the HTTP server is reachable
  • a postStart hook that opens the app URL from the Wendy CLI when possible

C++ Backend

main.cpp starts a Drogon HTTP server and a singleton MJPEGCamera that owns the GStreamer pipeline. The camera starts lazily when the first WebSocket client connects and stops after the last client disconnects, so the app does not hold camera resources when nobody is watching.

The capture pipeline reads from the selected V4L2 device, keeps the stream as MJPEG, and publishes frames through an appsink:

v4l2src device=/dev/video0
  ! image/jpeg
  ! jpegdec
  ! jpegenc quality=85
  ! appsink name=sink emit-signals=true max-buffers=2 drop=true sync=false

max-buffers=2 and drop=true keep latency low by dropping stale frames if the browser cannot keep up.

Routes and Streaming

The generated server exposes:

  • GET / from index.html
  • GET /cameras to list available V4L2 devices with v4l2-ctl --list-devices
  • WS /stream to send binary JPEG frames to the browser

When the browser sends a message like {"switch_camera": "/dev/video2"}, the WebSocket handler restarts the GStreamer pipeline against that device.

Frontend

index.html connects to /stream, receives binary JPEG frames, converts each frame to a data URL, and updates the full-screen image element. It also calls /cameras to populate the camera picker, shows connection state, displays FPS, and provides a fullscreen button.

Open the app URL from the Wendy run output to view the stream.

On this page