Skip to main content

uvcCamRegisterCb()

Registers a callback function to handle incoming assembled UVC frames from the USB camera. The callback is invoked each time a complete frame is received.

Signature

void uvcCamRegisterCb(uvc_frame_callback_t newFunction, void *cb_arg);

Parameters

newFunction
uvc_frame_callback_t
required
Pointer to the callback function that will be invoked for each received frame. The function signature must match:
void callback(uvc_frame *frame, void *user_ptr)
cb_arg
void*
required
User-defined pointer that will be passed to the callback function as user_ptr. Use this to pass context or state to your callback. Pass NULL if not needed.

Callback Function Type

uvc_frame_callback_t

The callback function type for handling UVC frames.
typedef void(*uvc_frame_callback_t)(struct uvc_frame *frame, void *user_ptr);

Callback Parameters

frame
uvc_frame*
Pointer to the UVC frame structure containing the image data and metadata.
user_ptr
void*
User-defined pointer that was passed to uvcCamRegisterCb() as cb_arg.

uvc_frame Structure

The uvc_frame structure contains the frame data and metadata:
typedef struct uvc_frame {
    void *data;                           // Image data for this frame
    size_t data_bytes;                    // Size of image data buffer
    uint32_t width;                       // Width of image in pixels
    uint32_t height;                      // Height of image in pixels
    enum uvc_frame_format frame_format;   // Pixel data format
    size_t step;                          // Bytes per horizontal line
    uint32_t sequence;                    // Frame number (monotonically increasing)
    struct timeval capture_time;          // System time when capture started
    struct timespec capture_time_finished; // System time when capture finished
    uvc_device_handle_t *source;          // Device that produced the image
    uint8_t library_owns_data;            // Whether library owns the buffer
    void *metadata;                       // Metadata for this frame
    size_t metadata_bytes;                // Size of metadata buffer
} uvc_frame_t;

Frame Structure Fields

data
void*
Pointer to the raw image data. The format is specified by frame_format.
data_bytes
size_t
Total size of the image data buffer in bytes.
width
uint32_t
Frame width in pixels.
height
uint32_t
Frame height in pixels.
frame_format
enum uvc_frame_format
Pixel data format (e.g., MJPEG, YUYV, etc.).
step
size_t
Number of bytes per horizontal line. Undefined for compressed formats.
sequence
uint32_t
Frame sequence number. May skip values but is strictly monotonically increasing. Use to detect dropped frames.
capture_time
struct timeval
Estimated system time when the device started capturing the image.
capture_time_finished
struct timespec
Estimated system time when the device finished receiving the image.
source
uvc_device_handle_t*
Handle to the device that produced the image.
library_owns_data
uint8_t
Indicates whether the library owns the data buffer:
  • 1: Library owns the buffer and may reallocate it
  • 0: User owns the buffer; library will not reallocate or free it
metadata
void*
Pointer to frame metadata if available, otherwise NULL.
metadata_bytes
size_t
Size of the metadata buffer in bytes.

Examples

Basic Frame Callback

#include <USB_STREAM.h>

// Simple callback that counts frames
unsigned long frameCount = 0;

void myFrameCallback(uvc_frame *frame, void *user_ptr) {
    frameCount++;
    
    Serial.printf("Frame %lu: %dx%d, %d bytes\n",
                  frameCount,
                  frame->width,
                  frame->height,
                  frame->data_bytes);
}

void setup() {
    Serial.begin(115200);
    
    // Configure UVC...
    
    // Register callback
    USBStream.uvcCamRegisterCb(myFrameCallback, NULL);
    
    Serial.println("Frame callback registered");
}

void loop() {
    // Your code here
}

Callback with User Context

#include <USB_STREAM.h>

// Structure to pass context to callback
typedef struct {
    unsigned long frameCount;
    unsigned long droppedFrames;
    uint32_t lastSequence;
    bool firstFrame;
} FrameContext;

FrameContext context = {0, 0, 0, true};

void frameCallbackWithContext(uvc_frame *frame, void *user_ptr) {
    FrameContext *ctx = (FrameContext *)user_ptr;
    
    ctx->frameCount++;
    
    // Detect dropped frames using sequence number
    if (!ctx->firstFrame) {
        uint32_t expectedSeq = ctx->lastSequence + 1;
        if (frame->sequence != expectedSeq) {
            uint32_t dropped = frame->sequence - expectedSeq;
            ctx->droppedFrames += dropped;
            Serial.printf("WARNING: %d frames dropped!\n", dropped);
        }
    }
    
    ctx->lastSequence = frame->sequence;
    ctx->firstFrame = false;
    
    // Print statistics every 100 frames
    if (ctx->frameCount % 100 == 0) {
        Serial.printf("Stats - Received: %lu, Dropped: %lu\n",
                      ctx->frameCount, ctx->droppedFrames);
    }
}

void setup() {
    Serial.begin(115200);
    
    // Configure UVC...
    
    // Register callback with context
    USBStream.uvcCamRegisterCb(frameCallbackWithContext, &context);
    
    Serial.println("Callback registered with context");
}

Frame Processing and Saving

#include <USB_STREAM.h>
#include <SD.h>

volatile bool saveNextFrame = false;

void frameProcessor(uvc_frame *frame, void *user_ptr) {
    // Process frame data
    Serial.printf("Processing frame: %dx%d, format: %d\n",
                  frame->width,
                  frame->height,
                  frame->frame_format);
    
    // Save frame to SD card if requested
    if (saveNextFrame && frame->data != NULL) {
        char filename[32];
        snprintf(filename, sizeof(filename), "/frame_%lu.jpg", frame->sequence);
        
        File file = SD.open(filename, FILE_WRITE);
        if (file) {
            file.write((uint8_t *)frame->data, frame->data_bytes);
            file.close();
            Serial.printf("Frame saved to %s\n", filename);
            saveNextFrame = false;
        } else {
            Serial.println("Failed to open file for writing");
        }
    }
}

void setup() {
    Serial.begin(115200);
    
    // Initialize SD card
    if (!SD.begin()) {
        Serial.println("SD card initialization failed!");
        return;
    }
    
    // Configure UVC...
    
    // Register frame processor
    USBStream.uvcCamRegisterCb(frameProcessor, NULL);
}

void loop() {
    // Trigger frame capture on button press
    if (digitalRead(BUTTON_PIN) == LOW) {
        saveNextFrame = true;
        delay(200); // Debounce
    }
}

Performance Monitoring

#include <USB_STREAM.h>

typedef struct {
    unsigned long totalFrames;
    unsigned long totalBytes;
    unsigned long lastReportTime;
    float avgFps;
    float avgBandwidth;
} PerformanceStats;

PerformanceStats stats = {0, 0, 0, 0.0, 0.0};

void performanceCallback(uvc_frame *frame, void *user_ptr) {
    PerformanceStats *s = (PerformanceStats *)user_ptr;
    
    s->totalFrames++;
    s->totalBytes += frame->data_bytes;
    
    // Calculate and report stats every second
    unsigned long now = millis();
    if (now - s->lastReportTime >= 1000) {
        unsigned long elapsed = now - s->lastReportTime;
        
        // Calculate FPS
        s->avgFps = (s->totalFrames * 1000.0) / elapsed;
        
        // Calculate bandwidth in MB/s
        s->avgBandwidth = (s->totalBytes / 1024.0 / 1024.0) / (elapsed / 1000.0);
        
        Serial.printf("Performance: %.2f FPS, %.2f MB/s\n",
                      s->avgFps, s->avgBandwidth);
        
        // Reset counters
        s->totalFrames = 0;
        s->totalBytes = 0;
        s->lastReportTime = now;
    }
}

void setup() {
    Serial.begin(115200);
    
    // Configure UVC...
    
    stats.lastReportTime = millis();
    USBStream.uvcCamRegisterCb(performanceCallback, &stats);
    
    Serial.println("Performance monitoring enabled");
}

Motion Detection

#include <USB_STREAM.h>

typedef struct {
    uint8_t *previousFrame;
    size_t frameSize;
    unsigned long motionEvents;
    uint32_t motionThreshold;
} MotionDetector;

MotionDetector detector = {NULL, 0, 0, 50000};

void motionDetectionCallback(uvc_frame *frame, void *user_ptr) {
    MotionDetector *md = (MotionDetector *)user_ptr;
    
    // Initialize previous frame buffer on first call
    if (md->previousFrame == NULL) {
        md->frameSize = frame->data_bytes;
        md->previousFrame = (uint8_t *)malloc(md->frameSize);
        if (md->previousFrame != NULL) {
            memcpy(md->previousFrame, frame->data, md->frameSize);
        }
        return;
    }
    
    // Calculate difference from previous frame
    uint32_t diff = 0;
    uint8_t *current = (uint8_t *)frame->data;
    
    for (size_t i = 0; i < md->frameSize && i < frame->data_bytes; i++) {
        diff += abs(current[i] - md->previousFrame[i]);
    }
    
    // Check if motion detected
    if (diff > md->motionThreshold) {
        md->motionEvents++;
        Serial.printf("MOTION DETECTED! Event #%lu (diff: %u)\n",
                      md->motionEvents, diff);
    }
    
    // Update previous frame
    memcpy(md->previousFrame, frame->data, frame->data_bytes);
}

void setup() {
    Serial.begin(115200);
    
    // Configure UVC...
    
    USBStream.uvcCamRegisterCb(motionDetectionCallback, &detector);
    
    Serial.println("Motion detection enabled");
}

void loop() {
    // Main loop can adjust sensitivity
    static unsigned long lastAdjust = 0;
    if (millis() - lastAdjust > 10000) {
        // Adjust threshold based on conditions
        detector.motionThreshold = analogRead(A0) * 100;
        Serial.printf("Threshold adjusted to: %u\n", detector.motionThreshold);
        lastAdjust = millis();
    }
}

Thread Safety Notes

Critical: Do NOT call any UVC functions (like uvcCamSuspend(), uvcCamResume(), etc.) from within the frame callback. This can cause deadlocks or undefined behavior.
The frame callback is invoked from the USB streaming thread, not your main application thread. Keep callback execution time minimal to avoid dropping frames.

Best Practices for Callbacks

  1. Keep callbacks short: Minimize processing time in the callback
  2. Use flags for communication: Set flags in the callback and process in main loop
  3. Avoid blocking operations: No delays, file I/O, or network operations
  4. Copy data if needed: If you need to process data later, copy it to a buffer
  5. Don’t call UVC functions: Never call UVC control functions from the callback

Safe Communication Pattern

// Global flags for thread-safe communication
volatile bool newFrameAvailable = false;
volatile uint32_t latestFrameSequence = 0;
uint8_t *frameDataCopy = NULL;
size_t frameDataSize = 0;

void safeCallback(uvc_frame *frame, void *user_ptr) {
    // Quick flag update - safe from callback
    latestFrameSequence = frame->sequence;
    
    // Optionally copy data for later processing
    if (frameDataCopy != NULL && frame->data_bytes <= frameDataSize) {
        memcpy(frameDataCopy, frame->data, frame->data_bytes);
        newFrameAvailable = true;
    }
}

void setup() {
    Serial.begin(115200);
    
    // Allocate buffer for frame copies
    frameDataSize = 640 * 480 * 2;
    frameDataCopy = (uint8_t *)malloc(frameDataSize);
    
    // Configure UVC...
    USBStream.uvcCamRegisterCb(safeCallback, NULL);
}

void loop() {
    // Process frames in main loop, not in callback
    if (newFrameAvailable) {
        newFrameAvailable = false;
        
        // Safe to call UVC functions here
        Serial.printf("Processing frame %lu\n", latestFrameSequence);
        
        // Safe to do heavy processing here
        processFrameData(frameDataCopy, frameDataSize);
    }
}

Notes

Register the callback before starting USB streaming to ensure you don’t miss any frames.
The sequence field in the frame structure is useful for detecting dropped frames. It’s strictly monotonically increasing but may skip values if frames are dropped.
If library_owns_data is 1, do not keep references to the data pointer after the callback returns. The library may reallocate or free the buffer. Copy the data if you need it later.

Build docs developers (and LLMs) love