-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Checklist
- Checked the issue tracker for similar issues to ensure this is not a duplicate.
- Described the feature in detail and justified the reason for the request.
- Provided specific use cases and examples.
Feature description
// Forward declaration
static esp_capture_err_t usb_video_src_open(esp_capture_video_src_if_t *src);
static esp_capture_err_t usb_video_src_get_support_codecs(esp_capture_video_src_if_t *src, const esp_capture_format_id_t **codecs, uint8_t *num);
static esp_capture_err_t usb_video_src_set_fixed_caps(esp_capture_video_src_if_t *src, const esp_capture_video_info_t *fixed_caps);
static esp_capture_err_t usb_video_src_negotiate_caps(esp_capture_video_src_if_t *src, esp_capture_video_info_t *in_caps, esp_capture_video_info_t *out_caps);
static esp_capture_err_t usb_video_src_start(esp_capture_video_src_if_t *src);
static esp_capture_err_t usb_video_src_acquire_frame(esp_capture_video_src_if_t *src, esp_capture_stream_frame_t *frame);
static esp_capture_err_t usb_video_src_release_frame(esp_capture_video_src_if_t *src, esp_capture_stream_frame_t *frame);
static esp_capture_err_t usb_video_src_stop(esp_capture_video_src_if_t *src);
static esp_capture_err_t usb_video_src_close(esp_capture_video_src_if_t *src);
// Supported codecs
static const esp_capture_format_id_t supported_codecs[] = {
ESP_CAPTURE_FMT_ID_MJPEG,
ESP_CAPTURE_FMT_ID_MJPEG,
};
static esp_capture_err_t usb_video_src_open(esp_capture_video_src_if_t *src)
{
ESP_LOGI(TAG, "USB video source open called");
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_get_support_codecs(esp_capture_video_src_if_t *src, const esp_capture_format_id_t **codecs, uint8_t *num)
{
*codecs = supported_codecs;
*num = sizeof(supported_codecs) / sizeof(supported_codecs[0]);
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_set_fixed_caps(esp_capture_video_src_if_t *src, const esp_capture_video_info_t *fixed_caps)
{
usb_video_src_t *usb_src = (usb_video_src_t *)src;
if (fixed_caps) {
memcpy(&usb_src->video_info, fixed_caps, sizeof(esp_capture_video_info_t));
}
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_negotiate_caps(esp_capture_video_src_if_t *src, esp_capture_video_info_t *in_caps, esp_capture_video_info_t *out_caps)
{
(void)src; // Unused parameter
out_caps->format_id = ESP_CAPTURE_FMT_ID_MJPEG;
out_caps->width = VIDEO_WIDTH;
out_caps->height = VIDEO_HEIGHT;
out_caps->fps = VIDEO_FPS;
ESP_LOGI(TAG, "Negotiated caps: format=%d, %dx%d@%dfps (MJPEG->YUV420->H.264 pipeline)",
out_caps->format_id, out_caps->width, out_caps->height, out_caps->fps);
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_start(esp_capture_video_src_if_t *src)
{
usb_video_src_t *usb_src = (usb_video_src_t *)src;
if (usb_src->started) {
ESP_LOGI(TAG, "USB video source already started");
return ESP_CAPTURE_ERR_OK;
}
usb_src->started = true;
ESP_LOGI(TAG, "USB video source started - ready to receive MJPEG frames for direct streaming");
return ESP_CAPTURE_ERR_OK;
}
// Helper function to start video source (can be called externally)
static void start_usb_video_src_internal(void)
{
if (g_usb_video_src && !g_usb_video_src->started) {
usb_video_src_start((esp_capture_video_src_if_t *)g_usb_video_src);
}
}
static esp_capture_err_t usb_video_src_acquire_frame(esp_capture_video_src_if_t *src, esp_capture_stream_frame_t *frame)
{
static int acquire_count = 0;
usb_video_src_t *usb_src = (usb_video_src_t *)src;
if (!usb_src->started) {
ESP_LOGW(TAG, "USB video source not started, trying to start it...");
usb_video_src_start(src);
if (!usb_src->started) {
ESP_LOGE(TAG, "Failed to start USB video source");
return ESP_CAPTURE_ERR_INVALID_STATE;
}
}
// Try to get a frame from queue (non-blocking)
queued_frame_t queued_frame;
if (xQueueReceive(usb_src->frame_queue, &queued_frame, pdMS_TO_TICKS(100)) == pdTRUE) {
frame->stream_type = ESP_CAPTURE_STREAM_TYPE_VIDEO;
frame->data = queued_frame.data;
frame->size = queued_frame.size;
frame->pts = queued_frame.pts;
usb_src->current_frame_data = queued_frame.data;
usb_src->current_frame_size = queued_frame.size;
usb_src->current_frame_pts = queued_frame.pts;
acquire_count++;
if (acquire_count <= 5 || acquire_count % 30 == 0) {
ESP_LOGI(TAG, "Frame acquired: size=%u, pts=%u, total=%d",
queued_frame.size, queued_frame.pts, acquire_count);
}
return ESP_CAPTURE_ERR_OK;
}
// No frame available
if (acquire_count < 5) {
ESP_LOGW(TAG, "No frame available in queue (acquire_count=%d)", acquire_count);
}
return ESP_CAPTURE_ERR_TIMEOUT;
}
static esp_capture_err_t usb_video_src_release_frame(esp_capture_video_src_if_t *src, esp_capture_stream_frame_t *frame)
{
usb_video_src_t *usb_src = (usb_video_src_t *)src;
// Free the frame data if it was allocated
if (frame->data && frame->data == usb_src->current_frame_data) {
heap_caps_free(frame->data);
usb_src->current_frame_data = NULL;
}
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_stop(esp_capture_video_src_if_t *src)
{
usb_video_src_t *usb_src = (usb_video_src_t *)src;
usb_src->started = false;
ESP_LOGI(TAG, "USB video source stopped");
return ESP_CAPTURE_ERR_OK;
}
static esp_capture_err_t usb_video_src_close(esp_capture_video_src_if_t *src)
{
ESP_LOGI(TAG, "USB video source close");
return ESP_CAPTURE_ERR_OK;
}
// Function to push frame from UVC callback
void media_sys_push_uvc_frame(uvc_frame_t *frame)
{
static int frame_push_count = 0;
if (!g_usb_video_src) {
if (frame_push_count < 5) {
ESP_LOGW(TAG, "g_usb_video_src is NULL, frame dropped (count=%d)", frame_push_count);
frame_push_count++;
}
return;
}
if (!g_usb_video_src->started) {
// Log first few times to debug
if (frame_push_count < 5) {
ESP_LOGW(TAG, "Video source not started yet, frame dropped (count=%d), started=%d",
frame_push_count, g_usb_video_src->started);
frame_push_count++;
}
return;
}
uint8_t *frame_data = NULL;
size_t frame_size = 0;
if (frame->frame_format == UVC_FRAME_FORMAT_MJPEG)
frame_size = frame->data_bytes;
frame_data = (uint8_t *)heap_caps_malloc(frame_size, MALLOC_CAP_8BIT);
if (!frame_data) {
ESP_LOGW(TAG, "Failed to allocate MJPEG frame buffer");
return;
}
memcpy(frame_data, frame->data, frame_size);
} else {
frame_size = frame->data_bytes;
frame_data = (uint8_t *)heap_caps_malloc(frame_size, MALLOC_CAP_8BIT);
if (!frame_data) {
ESP_LOGW(TAG, "Failed to allocate frame buffer");
return;
}
memcpy(frame_data, frame->data, frame_size);
}
queued_frame_t queued_frame = {
.data = frame_data,
.size = frame_size,
.pts = (uint32_t)(esp_timer_get_time() / 1000) // Convert to ms
};
// Send to queue (non-blocking, drop if queue is full)
BaseType_t queue_ret = xQueueSend(g_usb_video_src->frame_queue, &queued_frame, 0);
if (queue_ret != pdTRUE) {
// Queue full, free the frame
heap_caps_free(frame_data);
if (frame_push_count % 30 == 0) { // Log every 30 frames to avoid spam
ESP_LOGW(TAG, "MJPEG frame queue full, dropping frame (total pushed: %d), queue size: %d",
frame_push_count, uxQueueSpacesAvailable(g_usb_video_src->frame_queue));
}
} else {
frame_push_count++;
// Log first 10 frames after source started, then every 30 frames
if (frame_push_count <= 10 || frame_push_count % 30 == 0) {
ESP_LOGI(TAG, "MJPEG frame pushed to queue: size=%u, total=%d, queue free spaces: %d",
frame->data_bytes, frame_push_count,
uxQueueSpacesAvailable(g_usb_video_src->frame_queue));
}
}
}
int media_sys_buildup(void)
{
ESP_LOGI(TAG, "Media system buildup - USB camera MJPEG direct streaming mode");
// Create USB video source
g_usb_video_src = (usb_video_src_t *)calloc(1, sizeof(usb_video_src_t));
if (!g_usb_video_src) {
ESP_LOGE(TAG, "Failed to allocate USB video source");
return -1;
}
// Initialize video source interface
g_usb_video_src->base.open = usb_video_src_open;
g_usb_video_src->base.get_support_codecs = usb_video_src_get_support_codecs;
g_usb_video_src->base.set_fixed_caps = usb_video_src_set_fixed_caps;
g_usb_video_src->base.negotiate_caps = usb_video_src_negotiate_caps;
g_usb_video_src->base.start = usb_video_src_start;
g_usb_video_src->base.acquire_frame = usb_video_src_acquire_frame;
g_usb_video_src->base.release_frame = usb_video_src_release_frame;
g_usb_video_src->base.stop = usb_video_src_stop;
g_usb_video_src->base.close = usb_video_src_close;
// Initialize video info (YUV420 format for H.264 encoding)
g_usb_video_src->video_info.format_id = ESP_CAPTURE_FMT_ID_MJPEG;
g_usb_video_src->video_info.width = 640;
g_usb_video_src->video_info.height = 480;
g_usb_video_src->video_info.fps = 15;
// Create frame queue (hold up to 3 frames)
g_usb_video_src->frame_queue = xQueueCreate(3, sizeof(queued_frame_t));
if (!g_usb_video_src->frame_queue) {
ESP_LOGE(TAG, "Failed to create frame queue");
free(g_usb_video_src);
g_usb_video_src = NULL;
return -1;
}
// Create esp_capture configuration
esp_capture_cfg_t capture_cfg = {
.sync_mode = ESP_CAPTURE_SYNC_MODE_NONE,
.audio_src = NULL, // No audio for now
.video_src = (esp_capture_video_src_if_t *)g_usb_video_src,
};
// Open capture
esp_capture_err_t ret = esp_capture_open(&capture_cfg, &capture_handle);
if (ret != ESP_CAPTURE_ERR_OK) {
ESP_LOGE(TAG, "Failed to open capture: %d", ret);
vQueueDelete(g_usb_video_src->frame_queue);
free(g_usb_video_src);
g_usb_video_src = NULL;
return -1;
}
ESP_LOGI(TAG, "Media system initialized (USB camera), capture_handle=%p", capture_handle);
return 0;
}
Use cases
..
Alternatives
..
Additional context
...