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)
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
Pointer to the UVC frame structure containing the image data and metadata.
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
Pointer to the raw image data. The format is specified by frame_format.
Total size of the image data buffer in bytes.
Pixel data format (e.g., MJPEG, YUYV, etc.).
Number of bytes per horizontal line. Undefined for compressed formats.
Frame sequence number. May skip values but is strictly monotonically increasing. Use to detect dropped frames.
Estimated system time when the device started capturing the image.
Estimated system time when the device finished receiving the image.
Handle to the device that produced the image.
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
Pointer to frame metadata if available, otherwise NULL.
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
}
}
#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
- Keep callbacks short: Minimize processing time in the callback
- Use flags for communication: Set flags in the callback and process in main loop
- Avoid blocking operations: No delays, file I/O, or network operations
- Copy data if needed: If you need to process data later, copy it to a buffer
- 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.