Skip to main content

Overview

Lifecycle methods control the USB streaming session, including initialization, connection management, and shutdown. These methods must be called in the correct order to ensure proper operation.

start

Starts USB streaming with pre-configured settings. The driver creates internal FreeRTOS tasks to handle USB data from the stream pipe and executes user callbacks when new frames are ready.
void start();

Parameters

This method takes no parameters.

Return Value

This method returns void. The underlying C function returns esp_err_t, but the C++ wrapper does not expose error codes.

Example

#include "USB_STREAM.h"

USB_STREAM usb;

void setup() {
  Serial.begin(115200);
  
  // Configure UVC camera (example)
  uint8_t *xferBufferA = (uint8_t *)malloc(55 * 1024);
  uint8_t *xferBufferB = (uint8_t *)malloc(55 * 1024);
  uint8_t *frameBuffer = (uint8_t *)malloc(55 * 1024);
  
  usb.uvcConfiguration(
    640, 480,                    // width, height
    FRAME_INTERVAL_FPS_15,       // frame interval
    55 * 1024,                   // transfer buffer size
    xferBufferA, xferBufferB,    // transfer buffers
    55 * 1024,                   // frame buffer size
    frameBuffer                  // frame buffer
  );
  
  // Register callbacks before starting
  usb.uvcCamRegisterCb(onFrameReady, NULL);
  
  // Start streaming
  usb.start();
  Serial.println("USB streaming started");
}

void onFrameReady(uvc_frame_t *frame, void *ptr) {
  // Handle frame
  Serial.printf("Frame: %dx%d\n", frame->width, frame->height);
}

Notes

You must configure video/audio parameters using uvcConfiguration() and/or uacConfiguration() before calling start(). Starting without configuration may lead to undefined behavior.
Register state change and frame callbacks before calling start() to ensure you don’t miss any initial events.
The method creates internal FreeRTOS tasks that run continuously until stop() is called. These tasks handle USB data transfer and callback execution.

stop

Stops current USB streaming. Internal FreeRTOS tasks are deleted and related resources are freed.
void stop();

Parameters

This method takes no parameters.

Return Value

This method returns void. The underlying C function returns esp_err_t, but the C++ wrapper does not expose error codes.

Example

#include "USB_STREAM.h"

USB_STREAM usb;
bool streaming = false;

void setup() {
  Serial.begin(115200);
  
  // Configure and start streaming
  // ... configuration code ...
  
  usb.start();
  streaming = true;
  Serial.println("Streaming started");
}

void loop() {
  // Stop streaming after 30 seconds
  if (streaming && millis() > 30000) {
    Serial.println("Stopping stream...");
    usb.stop();
    streaming = false;
    Serial.println("Stream stopped");
  }
}

Example with Error Handling

void stopStreaming() {
  if (isStreamingActive) {
    Serial.println("Stopping USB streaming...");
    usb.stop();
    
    // Wait a moment for cleanup
    delay(100);
    
    // Free allocated buffers
    free(xferBufferA);
    free(xferBufferB);
    free(frameBuffer);
    
    isStreamingActive = false;
    Serial.println("Cleanup complete");
  }
}

Notes

Always call stop() before destroying the USB_STREAM object or deallocating buffers used in the configuration. Failing to do so may cause memory leaks or crashes.
The method blocks until all internal tasks are properly terminated. This may take a short moment to complete.
After calling stop(), you can reconfigure parameters and call start() again to restart streaming with different settings.

connectWait

Waits for a USB device to be connected, with a specified timeout.
void connectWait(int timeoutMs);

Parameters

timeoutMs
int
required
Timeout value in milliseconds. The method will block for up to this duration waiting for a device connection.
  • Set to 0 to return immediately
  • Set to -1 or large value for extended wait
  • Typical values: 5000 (5 seconds) to 30000 (30 seconds)

Return Value

This method returns void. The underlying C function returns esp_err_t with the following values:
  • ESP_OK: Device connected successfully
  • ESP_ERR_TIMEOUT: Timeout expired without connection
  • ESP_ERR_INVALID_STATE: Streaming not started

Example

#include "USB_STREAM.h"

USB_STREAM usb;

void setup() {
  Serial.begin(115200);
  
  // Configure streaming parameters
  // ... configuration code ...
  
  // Start streaming
  usb.start();
  Serial.println("Waiting for USB device...");
  
  // Wait up to 10 seconds for device connection
  usb.connectWait(10000);
  Serial.println("Device connected or timeout reached");
}

Example with User Feedback

void waitForDevice() {
  const int TIMEOUT_MS = 15000;
  
  Serial.println("Please connect a USB camera...");
  Serial.printf("Waiting up to %d seconds\n", TIMEOUT_MS / 1000);
  
  usb.start();
  usb.connectWait(TIMEOUT_MS);
  
  // You may want to check connection state after this
  Serial.println("Proceeding with streaming");
}

Example with State Callback

volatile bool deviceConnected = false;

void onStateChange(usb_stream_state_t state, void *arg) {
  if (state == STREAM_CONNECTED) {
    deviceConnected = true;
    Serial.println("Device connected!");
  } else {
    deviceConnected = false;
    Serial.println("Device disconnected!");
  }
}

void setup() {
  Serial.begin(115200);
  
  // Register state callback
  usb.registerState(onStateChange, NULL);
  
  // Configure and start
  // ... configuration code ...
  usb.start();
  
  // Wait for connection
  Serial.println("Waiting for device...");
  usb.connectWait(10000);
  
  if (deviceConnected) {
    Serial.println("Ready to stream!");
  } else {
    Serial.println("No device detected");
  }
}

Notes

This method must be called after start(). Calling it before starting streaming will result in an invalid state error.
The method blocks the calling task for the specified timeout duration or until a device connects, whichever comes first.
This method is useful for ensuring a device is connected before attempting to stream data, preventing errors from operations on a disconnected device.

registerState

Registers a callback function that will be invoked when the USB streaming state changes (device connection/disconnection).
void registerState(StateChangeCallback newFunction, void *usr_data);

Parameters

newFunction
StateChangeCallback
required
A pointer to a callback function with the signature:
void callback(usb_stream_state_t event, void *arg)
The event parameter will be:
  • STREAM_CONNECTED (0): USB device connected
  • STREAM_DISCONNECTED: USB device disconnected
usr_data
void*
required
A user-defined pointer that will be passed to the callback function as the second argument. Use NULL if not needed.This can point to:
  • Application context/state
  • Configuration structures
  • Class instances (for C++ callbacks)
  • Any user data needed in the callback

Return Value

This method returns void.

Example

#include "USB_STREAM.h"

USB_STREAM usb;

void onConnectionChange(usb_stream_state_t state, void *arg) {
  if (state == STREAM_CONNECTED) {
    Serial.println("✓ USB device connected");
    // Device is ready, can start operations
  } else if (state == STREAM_DISCONNECTED) {
    Serial.println("✗ USB device disconnected");
    // Handle disconnection, pause operations
  }
}

void setup() {
  Serial.begin(115200);
  
  // Register state callback before starting
  usb.registerState(onConnectionChange, NULL);
  
  // Configure and start streaming
  // ... configuration code ...
  usb.start();
}

Example with User Data

struct AppState {
  int frameCount;
  bool readyToStream;
  unsigned long lastConnectTime;
};

AppState appState = {0, false, 0};

void onConnectionChange(usb_stream_state_t state, void *arg) {
  AppState *app = (AppState *)arg;
  
  if (state == STREAM_CONNECTED) {
    Serial.println("Device connected");
    app->readyToStream = true;
    app->frameCount = 0;
    app->lastConnectTime = millis();
  } else {
    Serial.println("Device disconnected");
    app->readyToStream = false;
    Serial.printf("Total frames captured: %d\n", app->frameCount);
  }
}

void setup() {
  Serial.begin(115200);
  
  // Pass application state to callback
  usb.registerState(onConnectionChange, &appState);
  
  // Configure and start
  // ... configuration code ...
  usb.start();
}

Example with Class Method

class CameraManager {
private:
  USB_STREAM usb;
  bool connected;
  
  static void stateCallback(usb_stream_state_t state, void *arg) {
    CameraManager *manager = (CameraManager *)arg;
    manager->handleStateChange(state);
  }
  
  void handleStateChange(usb_stream_state_t state) {
    connected = (state == STREAM_CONNECTED);
    Serial.printf("Connection state: %s\n", connected ? "UP" : "DOWN");
  }
  
public:
  CameraManager() : connected(false) {}
  
  void begin() {
    // Register with 'this' pointer
    usb.registerState(stateCallback, this);
    
    // Configure and start
    // ... configuration code ...
    usb.start();
  }
  
  bool isConnected() { return connected; }
};

CameraManager camera;

void setup() {
  Serial.begin(115200);
  camera.begin();
}

Notes

Only one state callback can be registered at a time. Registering a new callback will overwrite any previously registered callback.
The callback is executed in the context of an internal FreeRTOS task. Keep the callback execution time minimal and avoid blocking operations like:
  • Long delays
  • Heavy computations
  • Blocking I/O operations
  • Serial.print() in high-frequency callbacks (use sparingly)
Register the state callback before calling start() to ensure you receive the initial connection event if a device is already connected.
The callback is useful for:
  • Updating UI/LED indicators
  • Pausing/resuming application logic
  • Logging connection events
  • Triggering reconnection attempts

Typical Lifecycle Sequence

Here’s a complete example showing the recommended order of operations:
#include "USB_STREAM.h"

USB_STREAM usb;
uint8_t *xferBufferA, *xferBufferB, *frameBuffer;
bool streaming = false;

void onStateChange(usb_stream_state_t state, void *arg) {
  Serial.printf("State: %s\n", 
    state == STREAM_CONNECTED ? "CONNECTED" : "DISCONNECTED");
}

void onFrame(uvc_frame_t *frame, void *ptr) {
  Serial.printf("Frame: %dx%d, %d bytes\n", 
    frame->width, frame->height, frame->data_bytes);
}

void setup() {
  Serial.begin(115200);
  
  // Step 1: Allocate buffers
  xferBufferA = (uint8_t *)malloc(55 * 1024);
  xferBufferB = (uint8_t *)malloc(55 * 1024);
  frameBuffer = (uint8_t *)malloc(55 * 1024);
  
  // Step 2: Register callbacks
  usb.registerState(onStateChange, NULL);
  usb.uvcCamRegisterCb(onFrame, NULL);
  
  // Step 3: Configure streaming
  usb.uvcConfiguration(
    640, 480,
    FRAME_INTERVAL_FPS_15,
    55 * 1024,
    xferBufferA, xferBufferB,
    55 * 1024,
    frameBuffer
  );
  
  // Step 4: Start streaming
  usb.start();
  streaming = true;
  Serial.println("Started");
  
  // Step 5: Wait for device
  usb.connectWait(10000);
  Serial.println("Ready");
}

void loop() {
  // Your application logic
  delay(100);
}

void cleanup() {
  if (streaming) {
    // Step 6: Stop streaming
    usb.stop();
    streaming = false;
    
    // Step 7: Free buffers
    free(xferBufferA);
    free(xferBufferB);
    free(frameBuffer);
  }
}

Build docs developers (and LLMs) love