Skip to content

Commit

Permalink
feat(uvc): Bulk WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
tore-espressif committed Nov 1, 2024
1 parent 0b16fc0 commit be6d6fd
Showing 1 changed file with 143 additions and 1 deletion.
144 changes: 143 additions & 1 deletion host/class/uvc/usb_host_uvc_2/uvc_bulk.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

#include <stdbool.h>
#include <string.h> // For memcpy

#include "esp_log.h"

Expand All @@ -17,5 +18,146 @@ static const char *TAG = "uvc-bulk";

void bulk_transfer_callback(usb_transfer_t *transfer)
{
ESP_LOGI(TAG, "Implement me!");
ESP_LOGD(TAG, "%s", __FUNCTION__);
uvc_stream_t *uvc_stream = (uvc_stream_t *)transfer->context;

UVC_ENTER_CRITICAL();
bool streaming_on = uvc_stream->streaming;
UVC_EXIT_CRITICAL();
if (!streaming_on) {
return; // If the streaming was turned off, we don't have to do anything
}

// In BULK implementation, 'payload' is a constant pointer to constant data,
// meaning both the pointer and the data it points to cannot be changed.
// This contrasts with the ISOC implementation, where 'payload' is a variable
// pointer and is increased after every ISOC packet processing
const uint8_t *const payload = transfer->data_buffer;

// Check USB transfer status
switch (transfer->status) {
case USB_TRANSFER_STATUS_COMPLETED:
break;
case USB_TRANSFER_STATUS_NO_DEVICE:
case USB_TRANSFER_STATUS_CANCELED:
return; // No need to process the rest
case USB_TRANSFER_STATUS_ERROR:
case USB_TRANSFER_STATUS_OVERFLOW:
case USB_TRANSFER_STATUS_STALL:
ESP_LOGW(TAG, "usb err %d", transfer->status);
uvc_stream->skip_current_frame = true;
goto resubmit_transfer;

case USB_TRANSFER_STATUS_TIMED_OUT:
goto resubmit_transfer; // Timed out BULK transfers are not an issue (not supported by USB Host Lib)
case USB_TRANSFER_STATUS_SKIPPED: // Should never happen to BULK transfer
default:
assert(false);
}

// Check for Zero Length Packet
if (transfer->actual_num_bytes == 0) {
goto resubmit_transfer;
}

// Check for start of new frame
const uvc_payload_header_t *payload_header = (const uvc_payload_header_t *)payload;
const bool start_of_frame = (uvc_stream->current_frame_id != payload_header->bmHeaderInfo.frame_id);
if (start_of_frame) {
// We detected start of new frame. Update Frame ID and start fetching this frame
uvc_stream->current_frame_id = payload_header->bmHeaderInfo.frame_id;
uvc_stream->skip_current_frame = false;

// Get free frame buffer for this new frame
UVC_ENTER_CRITICAL();
bool need_new_frame = (uvc_stream->streaming && !uvc_stream->current_frame);
if (need_new_frame) {
UVC_EXIT_CRITICAL();
uvc_stream->current_frame = uvc_frame_get_empty(uvc_stream);
if (uvc_stream->current_frame == NULL) {
// There is no free frame buffer now, skipping this frame
uvc_stream->skip_current_frame = true;

// Inform the user about the underflow
uvc_host_stream_callback_t stream_cb = uvc_stream->stream_cb;
if (stream_cb) {
const uvc_host_stream_event_data_t event = {
.type = UVC_HOST_FRAME_BUFFER_UNDERFLOW,
};
stream_cb(&event, uvc_stream->cb_arg);
}
goto resubmit_transfer;
}
} else {
// We received SoF but current_frame is not NULL: We missed EoF - reset the frame buffer
uvc_frame_reset(uvc_stream->current_frame);
UVC_EXIT_CRITICAL();
}
} else if (uvc_stream->skip_current_frame) {
// Previous packets indicated we must skip this frame
goto resubmit_transfer;
}

// Check for error flag
if (payload_header->bmHeaderInfo.error) {
uvc_stream->skip_current_frame = true;
goto resubmit_transfer;
}

// Add received data to frame buffer
const uint8_t *payload_data = payload + payload_header->bHeaderLength;
const size_t payload_data_len = transfer->actual_num_bytes - payload_header->bHeaderLength;
esp_err_t ret = uvc_frame_add_data(uvc_stream->current_frame, payload_data, payload_data_len);
if (ret != ESP_OK) {
// Frame buffer overflow
uvc_stream->skip_current_frame = true;

// Inform the user about the overflow
uvc_host_stream_callback_t stream_cb = uvc_stream->stream_cb;
if (stream_cb) {
const uvc_host_stream_event_data_t event = {
.type = UVC_HOST_FRAME_BUFFER_OVERFLOW,
};
stream_cb(&event, uvc_stream->cb_arg);
}
goto resubmit_transfer;
}

// End of frame detected. Pass the completed frame to the user
if (payload_header->bmHeaderInfo.end_of_frame) {
bool return_frame = true; // Default to returning the frame in case streaming has been stopped

// Get the current frame being processed and clear it from the stream,
// so no more data is written to this frame after the end of frame
UVC_ENTER_CRITICAL(); // Enter critical section to safely check and modify the stream state.
uvc_host_frame_t *this_frame = uvc_stream->current_frame;
uvc_stream->current_frame = NULL;

// Determine if we should invoke the frame callback:
// Only invoke the callback if streaming is active, a frame callback exists,
// and we have a valid frame to pass to the user.
const bool invoke_fb_callback = (uvc_stream->streaming && uvc_stream->frame_cb && this_frame);
uvc_host_frame_callback_t fb_callback = uvc_stream->frame_cb; // Capture the frame callback safely within the critical section.
UVC_EXIT_CRITICAL();

if (invoke_fb_callback) {
memcpy((uvc_host_stream_format_t *)&this_frame->vs_format, &uvc_stream->vs_format, sizeof(uvc_host_stream_format_t));

// Call the user's frame callback. If the callback returns false,
// we do not return the frame to the empty queue (i.e., the user wants to keep it for processing)
return_frame = fb_callback(this_frame, uvc_stream->cb_arg);
}
if (return_frame) {
// If the user has processed the frame (or the stream is stopped), return it to the empty frame queue
uvc_host_frame_return(uvc_stream, this_frame);
}
}

resubmit_transfer:
UVC_ENTER_CRITICAL();
streaming_on = uvc_stream->streaming;
UVC_EXIT_CRITICAL();
if (streaming_on) {
usb_host_transfer_submit(transfer); // Restart the transfer
}
}

0 comments on commit be6d6fd

Please sign in to comment.