Skip to main content

Common Issues and Solutions

Symptoms

  • usb_streaming_connect_wait() times out
  • No connection callback triggered
  • ESP_LOG shows no device enumeration

Possible Causes and Solutions

1. Hardware Connection Issues
# Check USB pins are correctly connected
# ESP32-S2/S3 USB pins are fixed and cannot be remapped
ESP32-S2 and ESP32-S3 have dedicated USB pins. Verify your hardware schematic matches the chip’s USB D+/D- pins.
2. Power Supply InsufficientUSB devices can draw significant current (up to 500mA). Ensure:
  • Adequate power supply (5V, 1A minimum)
  • VBUS properly powered if using USB host mode
  • Check power-on sequence
// Example power control from test code
#define IO_VBUS_POWER_CTRL 48

gpio_set_level(IO_VBUS_POWER_CTRL, 1);  // Power on VBUS
delay(100);  // Allow device to power up
usb_stream->start();
3. USB Driver Not StartedEnsure you call start() before waiting for connection:
usb_stream->start();  // Must call first
usb_stream->connectWait(10000);  // Then wait
4. Device IncompatibilityNot all USB devices are UVC/UAC compliant. Try:
  • Different camera/audio device
  • Check device works on PC first
  • Enable debug logging to see enumeration details
esp_log_level_set("*", ESP_LOG_DEBUG);

Symptoms

  • malloc() or heap_caps_malloc() returns NULL
  • Crash on startup during buffer allocation
  • Error: “Failed to allocate buffers”

Solutions

1. Insufficient DMA MemoryCheck available DMA memory before allocation:
size_t free_dma = heap_caps_get_free_size(MALLOC_CAP_DMA);
size_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);

ESP_LOGI(TAG, "Free DMA: %u bytes, Largest block: %u bytes",
         free_dma, largest_block);

// Need 165 KB for three 55KB buffers
if (largest_block < (55 * 1024)) {
    ESP_LOGE(TAG, "Insufficient DMA memory!");
    // Use smaller buffers or lower resolution
}
2. Reduce Buffer SizesFor ESP32-S2 or tight memory situations:
#ifdef CONFIG_IDF_TARGET_ESP32S2
    #define XFER_BUFFER_SIZE (45 * 1024)  // Smaller for S2
#else
    #define XFER_BUFFER_SIZE (55 * 1024)  // Larger for S3
#endif
3. PSRAM Cannot Be Used
USB buffers MUST be in internal DMA-capable RAM. PSRAM allocation will fail or cause crashes.
// Wrong - may allocate from PSRAM
uint8_t *buffer = (uint8_t *)malloc(55 * 1024);

// Correct - forces DMA-capable internal RAM
uint8_t *buffer = (uint8_t *)heap_caps_malloc(
    55 * 1024,
    MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT
);
4. Memory Leaks from Previous SessionsEnsure proper cleanup:
usb_stream->stop();
delete usb_stream;
heap_caps_free(xfer_buffer_a);
heap_caps_free(xfer_buffer_b);
heap_caps_free(frame_buffer);

Symptoms

  • Missing frames (sequence numbers skip)
  • Corrupted JPEG images
  • Irregular frame callback timing

Solutions

1. Insufficient Buffer SizeFrames larger than buffer size will be truncated:
// Check actual frame sizes in callback
void frame_callback(uvc_frame_t *frame, void *ptr) {
    if (frame->data_bytes > XFER_BUFFER_SIZE) {
        ESP_LOGW(TAG, "Frame too large: %u > %u bytes!",
                 frame->data_bytes, XFER_BUFFER_SIZE);
        // Increase buffer size
    }
}
2. Blocking in Callback
Never block in frame or audio callbacks! They run in USB task context.
// Bad - blocks USB task
void frame_callback(uvc_frame_t *frame, void *ptr) {
    save_to_sdcard(frame->data, frame->data_bytes);  // Blocks!
}

// Good - queue for async processing
void frame_callback(uvc_frame_t *frame, void *ptr) {
    xQueueSend(frame_queue, &frame, 0);
}
3. Frame Rate Too HighReduce frame rate if processing can’t keep up:
// Lower frame rate
usb->uvcCamSuspend(NULL);
usb->uvcCamFrameReset(640, 480, FRAME_INTERVAL_FPS_10);  // 10 fps
usb->uvcCamResume(NULL);
4. USB Bandwidth LimitationsHigh resolution + high frame rate may exceed USB bandwidth:
  • Reduce resolution, or
  • Reduce frame rate, or
  • Use isochronous mode if device supports it

Symptoms

  • Choppy audio playback
  • Gaps in microphone recording
  • “ESP_ERR_TIMEOUT” on audio reads/writes

Solutions

1. Insufficient Buffer SizeIncrease audio buffer sizes:
// Microphone - increase from 100ms to 200ms
size_t mic_buf = (16000 / 1000) * (16 / 8) * 1 * 200;  // 6,400 bytes

// Speaker - increase to 400-500ms to prevent underruns
size_t spk_buf = (16000 / 1000) * (16 / 8) * 1 * 500;  // 16,000 bytes
2. Not Continuously Feeding SpeakerSpeaker needs constant data supply:
// Bad - intermittent writes cause stuttering
delay(1000);
usb->uacWriteSpk(audio_data, size, 1000);

// Good - continuous streaming
while (streaming) {
    uint16_t *chunk = get_next_audio_chunk();
    usb->uacWriteSpk(chunk, chunk_size, portMAX_DELAY);
}
3. Blocking in Microphone CallbackProcess audio in separate task:
void mic_callback(mic_frame_t *frame, void *ptr) {
    // Quick copy to buffer
    memcpy(audio_buffer, frame->data, frame->data_bytes);
    xTaskNotify(audio_task_handle, frame->data_bytes, eSetValueWithOverwrite);
    // Return immediately - no blocking!
}
4. Sample Rate MismatchEnsure mic and speaker use compatible rates:
// Check actual device capabilities
size_t frame_size, frame_index;
usb->uacMicGetFrameListSize(&frame_size, &frame_index);

uac_frame_size_t *formats = 
    (uac_frame_size_t *)malloc(frame_size * sizeof(uac_frame_size_t));
usb->uacMicGetFrameSize(formats);

ESP_LOGI(TAG, "Device supports: %u Hz", formats[0].samples_frequence);
free(formats);

Symptoms

  • Error when calling configuration or control functions
  • “ESP_ERR_INVALID_STATE” returned from API calls

Common Causes

1. Configuring While RunningConfiguration must be set before start():
// Wrong order
usb->start();
usb->uvcConfiguration(...);  // ERROR: Already started!

// Correct order
usb->uvcConfiguration(...);
usb->start();
2. Controlling Before ConnectionWait for device connection before control operations:
usb->start();
usb->connectWait(10000);  // Wait for connection

// Now safe to control
usb->uvcCamSuspend(NULL);
3. Calling APIs on Stopped StreamCheck stream state before control calls:
if (stream_started && device_connected) {
    usb->uacMicVolume(&volume);
}

Symptoms

  • CTRL_SUSPEND or CTRL_RESUME has no effect
  • Stream doesn’t pause when suspended
  • Cannot change resolution after suspend

Solutions

1. Device Doesn’t Support SuspendSome devices may not respond to suspend control:
esp_err_t err = usb->uvcCamSuspend(NULL);
if (err == ESP_ERR_NOT_SUPPORTED) {
    ESP_LOGW(TAG, "Device doesn't support suspend");
    // Use stop/start instead
    usb->stop();
    // ... change config ...
    usb->start();
}
2. Need Delay After SuspendSome cameras require time to suspend:
test_apps/main/test_usb_stream.c
usb->uvcCamSuspend(NULL);
vTaskDelay(100 / portTICK_PERIOD_MS);  // Wait for suspend
usb->uvcCamFrameReset(640, 480, FRAME_INTERVAL_FPS_15);
usb->uvcCamResume(NULL);
3. Cannot Suspend Too Quickly After Start
test_apps/main/test_usb_stream.c
usb->start();
usb->connectWait(portMAX_DELAY);

// Some cameras need delay before accepting suspend
vTaskDelay(100 / portTICK_PERIOD_MS);
usb->uvcCamSuspend(NULL);

Symptoms

  • Unexpected reboot
  • “Task watchdog” timeout
  • Guru Meditation Error

Common Causes

1. Stack Overflow in CallbackCallbacks use USB task stack:
// Bad - large local array on stack
void frame_callback(uvc_frame_t *frame, void *ptr) {
    uint8_t temp_buffer[100000];  // Stack overflow!
    process_frame(temp_buffer);
}

// Good - use heap or static allocation
static uint8_t *temp_buffer = NULL;
void frame_callback(uvc_frame_t *frame, void *ptr) {
    if (!temp_buffer) {
        temp_buffer = (uint8_t *)malloc(100000);
    }
    process_frame(temp_buffer);
}
2. Accessing Freed MemoryDon’t free buffers while streaming:
// Wrong
heap_caps_free(xfer_buffer_a);
usb->stop();  // Crash - buffer already freed!

// Correct
usb->stop();  // Stop first
heap_caps_free(xfer_buffer_a);  // Then free
3. Invalid Buffer PointersAlways verify allocation:
uint8_t *buffer = (uint8_t *)heap_caps_malloc(55 * 1024, MALLOC_CAP_DMA);
assert(buffer != NULL);  // Catch allocation failure early
4. Blocking USB TaskLong operations in callbacks can trigger watchdog:
void frame_callback(uvc_frame_t *frame, void *ptr) {
    // Bad - takes too long
    heavy_processing(frame->data);
    
    // Good - delegate to another task
    xQueueSend(processing_queue, frame, 0);
}

Debug Logging

Enable verbose logging for troubleshooting:
// Enable DEBUG level for all components
esp_log_level_set("*", ESP_LOG_DEBUG);

// Or specific components
esp_log_level_set("USB_STREAM", ESP_LOG_DEBUG);
esp_log_level_set("usb_stream", ESP_LOG_VERBOSE);
From the test suite:
test_apps/main/test_usb_stream.c
TEST_CASE("test uvc any resolution", "[devkit][uvc][uvc_only]") {
    esp_log_level_set("*", ESP_LOG_DEBUG);
    // ... test code ...
}

Error Code Reference

Error CodeValueMeaningSolution
ESP_OK0Success-
ESP_FAIL-1Generic failureCheck logs for details
ESP_ERR_INVALID_STATE0x103Operation not allowed in current stateEnsure correct API call sequence
ESP_ERR_INVALID_ARG0x102Invalid parameterCheck NULL pointers, buffer sizes
ESP_ERR_TIMEOUT0x107Operation timed outIncrease timeout or check device connection
ESP_ERR_NO_MEM0x101Memory allocation failedFree memory or use smaller buffers
ESP_ERR_NOT_FOUND0x105Interface/device not foundDevice may not support requested feature
ESP_ERR_NOT_SUPPORTED0x106Feature not supportedDevice doesn’t support operation

USB Device Compatibility

Verified Working Devices

From the test suite and community reports:
  • Most UVC-compliant webcams (Logitech, Microsoft, etc.)
  • USB microphones with UAC support
  • USB speakers/headsets with UAC support

Known Issues

  • Some USB 3.0 devices may not work in USB 2.0 mode
  • Devices requiring more than 500mA may fail
  • Multi-configuration devices may need specific setup
  • Some cameras don’t support runtime resolution change

Performance Optimization Tips

1

Use Appropriate Resolution

Higher resolution requires more processing power:
// For real-time processing, use lower resolution
usb->uvcConfiguration(320, 240, FRAME_INTERVAL_FPS_15, ...);
2

Optimize Frame Processing

Process frames in separate task:
QueueHandle_t frame_queue;

void frame_callback(uvc_frame_t *frame, void *ptr) {
    // Quick copy, no processing
    frame_info_t info = {
        .data = malloc(frame->data_bytes),
        .size = frame->data_bytes
    };
    memcpy(info.data, frame->data, frame->data_bytes);
    xQueueSend(frame_queue, &info, 0);
}

void processing_task(void *param) {
    while (1) {
        frame_info_t info;
        if (xQueueReceive(frame_queue, &info, portMAX_DELAY)) {
            process_frame(info.data, info.size);
            free(info.data);
        }
    }
}
3

Reduce Frame Rate if Needed

Lower frame rates reduce CPU load:
// 10 fps instead of 30 fps
usb->uvcCamFrameReset(640, 480, FRAME_INTERVAL_FPS_10);

Getting Help

If you’re still experiencing issues:
  1. Check the logs with debug level enabled
  2. Verify hardware connections and power supply
  3. Test with known-working device to isolate issue
  4. Review examples in the library for reference patterns
  5. Check GitHub issues for similar problems

Useful Log Information

When reporting issues, include:
ESP_LOGI(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
ESP_LOGI(TAG, "Free heap: %u bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "Free DMA heap: %u bytes",
         heap_caps_get_free_size(MALLOC_CAP_DMA));

// After device connection
size_t frame_size, frame_index;
usb->uvcCamGetFrameListSize(&frame_size, &frame_index);
ESP_LOGI(TAG, "Camera supports %u resolutions, using index %u",
         frame_size, frame_index);

Next Steps

Build docs developers (and LLMs) love