Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

usbd_video: use pingpang buffer to improve tx performance #277

Merged
merged 1 commit into from
Nov 25, 2024

Conversation

chenzhihong007
Copy link
Contributor

@chenzhihong007 chenzhihong007 commented Nov 25, 2024

  • use pingpang buffer to improve tx performance

Summary by CodeRabbit

  • New Features

    • Implemented a dual-buffer mechanism for video streaming, improving data handling efficiency.
  • Bug Fixes

    • Enhanced control flow for video transmission by ensuring proper management of multiple video packets.
  • Documentation

    • Updated function signatures and variable declarations to reflect changes in buffer handling for video streaming.

Copy link

coderabbitai bot commented Nov 25, 2024

Walkthrough

The pull request introduces significant modifications to the video streaming functionality within the USB device framework. Key changes include the expansion of the usbd_video_priv structure to support dual endpoint buffers, enhancing data management during video transmission. The usbd_video_stream_start_write function's signature has been updated to accept two buffers, reflecting this dual-buffer approach. Additionally, several demo files have been modified to utilize two-dimensional arrays for video packet storage, allowing for improved handling of video data during streaming operations.

Changes

File Path Change Summary
class/video/usbd_video.c Expanded usbd_video_priv structure with new fields for dual buffers. Updated usbd_video_stream_split_transfer and usbd_video_stream_start_write function signatures for dual buffer handling.
class/video/usbd_video.h Updated usbd_video_stream_start_write function signature to accept two buffer parameters instead of one.
demo/video_audiov1_hid_template.c Changed video_packet_buffer from a 1D to a 2D array for dual buffer usage. Updated calls to usbd_video_stream_start_write.
demo/video_static_h264_template.c Changed packet_buffer from a 1D to a 2D array. Updated calls to usbd_video_stream_start_write.
demo/video_static_mjpeg_template.c Changed packet_buffer from a 1D to a 2D array. Updated calls to usbd_video_stream_start_write.
demo/video_static_yuyv_template.c Changed packet_buffer from a 1D to a 2D array. Updated calls to usbd_video_stream_start_write.

Possibly related PRs

  • update hpmicro port files #274: The changes in demo/video_static_h264_template.c involve modifications to the usbd_video_stream_start_write function to accommodate a dual-buffer structure, which aligns with the enhancements made in the main PR regarding the dual-buffer mechanism for video streaming.

🐇 In the realm of bytes and streams,
Two buffers dance, fulfilling dreams.
Video flows with grace anew,
Hopping fast, as rabbits do!
With every frame, the magic grows,
In dual paths, the data flows.
🥕✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (7)
demo/video_static_mjpeg_template.c (2)

209-209: Ensure proper initialization of both ping-pong buffers

The memset call correctly initializes both buffers since it uses sizeof(packet_buffer). However, consider adding a comment to clarify the ping-pong buffer strategy for future maintainers.

 USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE];
+// Ping-pong buffers for double buffering to improve USB video streaming performance
 
 void video_test(uint8_t busid)
 {
     memset(packet_buffer, 0, sizeof(packet_buffer));

Also applies to: 211-212


Line range hint 218-223: Consider adding timeout mechanism to prevent indefinite blocking

While the implementation correctly uses both ping-pong buffers, the busy-wait loop could potentially block indefinitely. Consider adding a timeout mechanism to ensure the system remains responsive.

             usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_mjpeg, sizeof(cherryusb_mjpeg));
+            uint32_t timeout = 1000; // Adjust timeout value based on your requirements
             while (iso_tx_busy) {
                 if (tx_flag == 0) {
                     break;
                 }
+                if (--timeout == 0) {
+                    // Handle timeout - could reset state or log error
+                    iso_tx_busy = false;
+                    break;
+                }
+                // Consider adding a small delay here if needed
             }
demo/video_static_yuyv_template.c (1)

Line range hint 220-225: Consider improving transfer handling robustness.

While the ping-pong buffer implementation is correct, consider these improvements:

  1. Add timeout mechanism to prevent infinite waiting
  2. Add error handling for transfer failures
  3. Consider using events/callbacks instead of busy waiting

Example improvement:

-            while (iso_tx_busy) {
-                if (tx_flag == 0) {
-                    break;
-                }
-            }
+            uint32_t timeout = 1000; // Adjust timeout value as needed
+            while (iso_tx_busy && tx_flag && timeout > 0) {
+                if (usbd_video_get_transfer_status(busid, VIDEO_IN_EP) < 0) {
+                    // Handle transfer error
+                    break;
+                }
+                timeout--;
+                // Add small delay if needed
+            }
+            if (timeout == 0) {
+                // Handle timeout error
+            }
demo/video_static_h264_template.c (2)

209-209: Define a macro for the buffer count to enhance maintainability

To improve code readability and ease future modifications, consider defining a macro for the buffer count value 2 used in the declaration of packet_buffer. This change ensures that any future adjustments to the buffer count require changes in only one place.

Apply this diff to define a macro and update the array declaration:

+#define BUFFER_COUNT 2

 USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][40 * 1024];
-
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[BUFFER_COUNT][40 * 1024];

217-217: Use sizeof(packet_buffer) instead of hardcoded size in memset

To ensure the memset operation correctly reflects the size of packet_buffer, especially if the buffer size changes in the future, consider using sizeof(packet_buffer) instead of the hardcoded value 2* 40 * 1024.

Apply this diff to update the memset call:

-memset(packet_buffer, 0, 2* 40 * 1024);
+memset(packet_buffer, 0, sizeof(packet_buffer));
demo/video_audiov1_hid_template.c (2)

483-483: Simplify buffer pointers in function call

You can simplify the buffer pointers by passing video_packet_buffer[0] and video_packet_buffer[1] instead of &video_packet_buffer[0][0] and &video_packet_buffer[1][0] to improve readability.

Apply this diff to simplify the code:

-usbd_video_stream_start_write(busid, VIDEO_IN_EP, &video_packet_buffer[0][0], &video_packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_mjpeg, sizeof(cherryusb_mjpeg));
+usbd_video_stream_start_write(busid, VIDEO_IN_EP, video_packet_buffer[0], video_packet_buffer[1], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_mjpeg, sizeof(cherryusb_mjpeg));

Line range hint 483-490: Optimize the video_test function to avoid busy-waiting

In the video_test function, the inner while loop busy-waits on video_iso_tx_busy, which can lead to high CPU usage. Consider adding a small delay within the loop or using a synchronization mechanism to reduce CPU load.

Apply this diff to add a delay within the busy-wait loop:

 while (video_iso_tx_busy) {
     if (video_tx_flag == 0) {
         break;
     }
+    // Add a small delay to prevent CPU overuse
+    usbd_delay_ms(1);
 }

Alternatively, consider using event-driven programming or synchronization primitives provided by your system to handle the busy-wait more efficiently.

🛑 Comments failed to post (2)
class/video/usbd_video.c (2)

805-844: 🛠️ Refactor suggestion

Refactor duplicate code blocks to improve maintainability

The code blocks from lines 805~ to 844~ and 846~ to 879~ are nearly identical, differing only in the use of ep_buf0 versus ep_buf1. This duplication can make the code harder to maintain and increase the risk of inconsistencies.

Consider refactoring these blocks into a helper function that handles buffer filling. This will reduce code duplication and enhance code readability.

Example refactoring:

static void fill_ep_buffer(uint8_t busid, uint8_t *ep_buf, uint32_t *ep_buf_len, bool *ep_buf_ready) {
    uint32_t remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;

    if (remain == 0) {
        g_usbd_video[busid].stream_frameid ^= 1;
        g_usbd_video[busid].stream_finish = true;
    } else {
        uint32_t len = MIN(remain, (g_usbd_video[busid].probe.dwMaxPayloadTransferSize - g_usbd_video[busid].stream_headerlen) * g_usbd_video[busid].max_packets);

        uint32_t offset = 0;
        while (len > 0) {
            struct video_payload_header *header = (struct video_payload_header *)&ep_buf[offset];
            header->bHeaderLength = g_usbd_video[busid].stream_headerlen;
            header->headerInfoUnion.bmheaderInfo = 0;
            header->headerInfoUnion.headerInfoBits.endOfHeader = 1;
            header->headerInfoUnion.headerInfoBits.endOfFrame = 0;
            header->headerInfoUnion.headerInfoBits.frameIdentifier = g_usbd_video[busid].stream_frameid;

            uint32_t len2 = MIN(len, g_usbd_video[busid].probe.dwMaxPayloadTransferSize - g_usbd_video[busid].stream_headerlen);

            usb_memcpy(&ep_buf[offset + g_usbd_video[busid].stream_headerlen],
                       &g_usbd_video[busid].stream_buf[g_usbd_video[busid].stream_offset],
                       len2);

            g_usbd_video[busid].stream_offset += len2;
            len -= len2;
            offset += (len2 + g_usbd_video[busid].stream_headerlen);

            if (g_usbd_video[busid].stream_offset == g_usbd_video[busid].stream_len) {
                header->headerInfoUnion.headerInfoBits.endOfFrame = 1;
            }
        }
        *ep_buf_len = offset;
        *ep_buf_ready = true;
    }
}

Then, replace the duplicated code with calls to this helper function:

if (!g_usbd_video[busid].ep_buf0_ready) {
    fill_ep_buffer(busid, g_usbd_video[busid].ep_buf0, &g_usbd_video[busid].ep_buf0_len, &g_usbd_video[busid].ep_buf0_ready);
    if (!g_usbd_video[busid].ep_buf1_ready) {
        g_usbd_video[busid].ep_buf_idx = 0;
        usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);
    }
}

if (!g_usbd_video[busid].ep_buf1_ready) {
    fill_ep_buffer(busid, g_usbd_video[busid].ep_buf1, &g_usbd_video[busid].ep_buf1_len, &g_usbd_video[busid].ep_buf1_ready);
}

Also applies to: 846-879


791-799: ⚠️ Potential issue

Potential logical error in buffer readiness flags

In the conditions starting at lines 791~ and 795~, the assignment of buffer readiness flags may be incorrect.

At line 792~, when ep_buf1_ready is true and ep_buf_idx is 0, the code sets ep_buf0_ready = false;. It seems that ep_buf1_ready should be cleared instead since ep_buf1 is about to be sent.

Similarly, at line 796~, when ep_buf0_ready is true and ep_buf_idx is 1, the code sets ep_buf1_ready = false;, whereas it should likely clear ep_buf0_ready since ep_buf0 is about to be sent.

This could lead to buffer readiness flags not accurately reflecting the state of the buffers, potentially causing synchronization issues.

Apply the following diff to correct the buffer readiness flags:

@@ -792,7 +792,7 @@
     if (g_usbd_video[busid].ep_buf1_ready && (g_usbd_video[busid].ep_buf_idx == 0)) {    /* callback: buf1 ready and buf0 was sent */
-        g_usbd_video[busid].ep_buf0_ready = false;    /* clear buf0 ready flag */
+        g_usbd_video[busid].ep_buf1_ready = false;    /* clear buf1 ready flag */
         g_usbd_video[busid].ep_buf_idx = 1;
         usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf1, g_usbd_video[busid].ep_buf1_len);    /* send buf1 data */
     } else if (g_usbd_video[busid].ep_buf0_ready && (g_usbd_video[busid].ep_buf_idx == 1)) {    /* callback: buf0 ready and buf1 was sent */
@@ -796,7 +796,7 @@
-        g_usbd_video[busid].ep_buf1_ready = false;    /* clear buf1 ready flag */
+        g_usbd_video[busid].ep_buf0_ready = false;    /* clear buf0 ready flag */
         g_usbd_video[busid].ep_buf_idx = 0;
         usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);    /* send buf0 data */
     } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (g_usbd_video[busid].ep_buf1_ready && (g_usbd_video[busid].ep_buf_idx == 0)) {    /* callback: buf1 ready and buf0 was sent */
        g_usbd_video[busid].ep_buf1_ready = false;    /* clear buf1 ready flag */
        g_usbd_video[busid].ep_buf_idx = 1;
        usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf1, g_usbd_video[busid].ep_buf1_len);    /* send buf1 data */
    } else if (g_usbd_video[busid].ep_buf0_ready && (g_usbd_video[busid].ep_buf_idx == 1)) {    /* callback: buf0 ready and buf1 was sent */
        g_usbd_video[busid].ep_buf0_ready = false;    /* clear buf0 ready flag */
        g_usbd_video[busid].ep_buf_idx = 0;
        usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);    /* send buf0 data */
    } else {

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (7)
demo/video_static_h264_template.c (2)

209-209: Consider using defines for buffer dimensions.

The buffer dimensions should be defined using preprocessor constants for better maintainability and clarity. This would also ensure consistency with other size-related constants already defined in the file (like MAX_PAYLOAD_SIZE).

+#define PACKET_BUFFER_COUNT 2
+#define PACKET_BUFFER_SIZE (40 * 1024)
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][40 * 1024];
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[PACKET_BUFFER_COUNT][PACKET_BUFFER_SIZE];

Line range hint 221-226: Add timeout and error handling to the transfer loop.

The current implementation has two potential issues:

  1. The busy wait loop could block indefinitely
  2. There's no error handling for the stream write call

Consider implementing a timeout mechanism and error handling:

+#define TRANSFER_TIMEOUT_MS 1000
+
+            uint32_t start_time = get_sys_tick_ms();
             usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], 
-                MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_h264, sizeof(cherryusb_h264));
+                MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_h264, sizeof(cherryusb_h264));
+            if (ret != 0) {
+                // Handle error
+                continue;
+            }
             while (iso_tx_busy) {
                 if (tx_flag == 0) {
                     break;
                 }
+                if ((get_sys_tick_ms() - start_time) > TRANSFER_TIMEOUT_MS) {
+                    // Handle timeout
+                    break;
+                }
             }
demo/video_static_yuyv_template.c (2)

211-211: LGTM! Consider cache line alignment for optimal DMA performance.

The ping-pong buffer implementation with USB_NOCACHE_RAM_SECTION and USB_MEM_ALIGNX looks good. For further optimization, consider aligning each buffer to cache line size to prevent false sharing and improve DMA performance.

-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE];
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE] __attribute__((aligned(CONFIG_CPU_CACHE_LINE_SIZE)));

Line range hint 211-225: Consider frame drop handling and buffer sizing strategy.

The current implementation might block on iso_tx_busy when frames can't be transmitted fast enough. Consider:

  1. Implementing frame dropping logic when falling behind
  2. Making buffer sizes configurable based on resolution/fps
  3. Adding statistics collection for monitoring performance

This will help prevent video stuttering and provide better QoS under varying conditions.

demo/video_audiov1_hid_template.c (3)

272-272: Good implementation of ping-pong buffering!

The change from a single buffer to a dual-buffer system enables efficient video streaming by allowing one buffer to be filled while the other is being transmitted, which can help prevent data underruns and maintain consistent frame rates.

This ping-pong buffer implementation provides several benefits:

  1. Reduced latency by overlapping data preparation and transmission
  2. Prevention of data underruns during USB transmission
  3. More consistent frame rates due to always having a buffer ready

483-483: Consider improving the streaming implementation.

While the ping-pong buffer usage is correct, there are several potential improvements to consider:

  1. Add error handling for the video streaming call
  2. Consider using a callback-based approach instead of busy-waiting
  3. Initialize only the active buffer before streaming

Here's a suggested improvement:

- usbd_video_stream_start_write(busid, VIDEO_IN_EP, &video_packet_buffer[0][0], &video_packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_mjpeg, sizeof(cherryusb_mjpeg));
+ if (usbd_video_stream_start_write(busid, VIDEO_IN_EP, 
+     &video_packet_buffer[0][0], 
+     &video_packet_buffer[1][0], 
+     MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, 
+     (uint8_t *)cherryusb_mjpeg, 
+     sizeof(cherryusb_mjpeg)) != 0) {
+     // Handle error
+     continue;
+ }

Line range hint 478-489: Consider restructuring the video test loop for better efficiency.

The current busy-wait implementation in the video test function could impact system performance.

Consider implementing a more efficient approach:

void video_test(uint8_t busid)
{
    memset(video_packet_buffer, 0, sizeof(video_packet_buffer));
+   uint8_t active_buffer = 0;

    while (1) {
        if (video_tx_flag) {
-           video_iso_tx_busy = true;
-           usbd_video_stream_start_write(busid, VIDEO_IN_EP, &video_packet_buffer[0][0], &video_packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_mjpeg, sizeof(cherryusb_mjpeg));
-           while (video_iso_tx_busy) {
-               if (video_tx_flag == 0) {
-                   break;
-               }
-           }
+           // Prepare next buffer while current one is being transmitted
+           prepare_video_data(&video_packet_buffer[active_buffer][0]);
+           
+           if (!video_iso_tx_busy) {
+               video_iso_tx_busy = true;
+               if (usbd_video_stream_start_write(busid, VIDEO_IN_EP,
+                   &video_packet_buffer[active_buffer][0],
+                   &video_packet_buffer[1-active_buffer][0],
+                   MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE,
+                   (uint8_t *)cherryusb_mjpeg,
+                   sizeof(cherryusb_mjpeg)) == 0) {
+                   active_buffer = 1 - active_buffer;
+               }
+           }
+           
+           // Add a small delay or yield to prevent tight spinning
+           usbd_delay_ms(1);
        }
    }
}

This implementation:

  1. Alternates between buffers efficiently
  2. Prepares the next buffer while current one is being transmitted
  3. Reduces CPU usage by avoiding tight loops
  4. Adds basic error handling
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between efd683f and 328ef42.

📒 Files selected for processing (6)
  • class/video/usbd_video.c (2 hunks)
  • class/video/usbd_video.h (1 hunks)
  • demo/video_audiov1_hid_template.c (2 hunks)
  • demo/video_static_h264_template.c (1 hunks)
  • demo/video_static_mjpeg_template.c (2 hunks)
  • demo/video_static_yuyv_template.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • class/video/usbd_video.h
  • demo/video_static_mjpeg_template.c
🔇 Additional comments (6)
demo/video_static_h264_template.c (2)

217-217: LGTM! Good use of sizeof operator.

The initialization correctly clears both buffers and will automatically adapt if buffer dimensions change.


Line range hint 209-226: Verify buffer switching implementation in USB stack.

While the ping-pong buffer structure is set up correctly, we should verify that the buffer switching logic is properly implemented in the USB stack's video stream implementation.

✅ Verification successful

Buffer switching implementation is correctly handled in the USB stack

The buffer switching implementation in the USB stack is properly implemented with the following key aspects:

  • The usbd_video structure maintains proper state tracking with ep_buf0_ready, ep_buf1_ready, and ep_buf_idx flags
  • Buffer switching logic in usbd_video_stream_split_transfer:
    • Switches to buffer 1 when buffer 0 was sent and buffer 1 is ready
    • Switches to buffer 0 when buffer 1 was sent and buffer 0 is ready
    • Properly manages buffer states during transitions
  • Initialization in usbd_video_stream_start_write correctly sets up the ping-pong buffer state

The implementation follows proper ping-pong buffering practices and maintains consistent buffer states throughout the video streaming process.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for buffer switching logic in the USB video implementation
rg -A 10 "usbd_video_stream_start_write" --type c

Length of output: 3883


Script:

#!/bin/bash
# Let's check the complete implementation of buffer switching logic
ast-grep --pattern 'struct usbd_video {
  $$$
}'

# Also check for any buffer switching related callbacks
rg -A 10 "in_ep_callback" --type c

Length of output: 85


Script:

#!/bin/bash
# Let's try a different approach to find the video structure and buffer handling
rg -A 15 "struct.*usbd_video" --type c

# Also search for the buffer switching implementation
rg -A 15 "ep_buf.*ready" --type c

# And check for ISO transfer completion handling
rg -A 15 "iso.*complete" --type c

Length of output: 14428

demo/video_static_yuyv_template.c (1)

Line range hint 220-225: Verify the synchronization mechanism for ping-pong buffer.

The busy-wait synchronization looks correct but could potentially miss buffer switches if the callback is too quick. Let's verify the implementation in the video class driver.

✅ Verification successful

Let me generate additional scripts to verify the implementation of usbd_video_stream_start_write and its buffer handling mechanism.


Let me generate one final script to check the implementation of the split transfer mechanism, which is crucial for understanding the buffer switching logic.


Based on the analysis of the code, I can now provide a final response about the synchronization mechanism:

The ping-pong buffer synchronization is properly implemented and safe.

The implementation shows a robust synchronization mechanism:

  • The iso_tx_busy flag is properly managed across all state transitions:
    • Set to true before starting a new transfer
    • Reset to false when a frame completes (usbd_video_stream_split_transfer returns true)
    • Reset to false during USB reset and close events
  • The tx_flag acts as a higher-level control to safely terminate transfers
  • The busy-wait loop has a proper exit condition through tx_flag, preventing deadlocks
  • Buffer switching is handled by the USB stack through usbd_video_stream_start_write, with proper handover in the ISO callback
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of usbd_video_stream_start_write and its synchronization mechanism

# Search for the implementation of usbd_video_stream_start_write
ast-grep --pattern 'usbd_video_stream_start_write($$$)' 

# Check for potential race conditions in buffer switching
rg -A 5 'iso_tx_busy'

# Verify the split transfer implementation
ast-grep --pattern 'usbd_video_stream_split_transfer($$$)'

Length of output: 11604


Script:

#!/bin/bash
# Search for the implementation of usbd_video_stream_start_write to understand buffer handling
rg -A 10 "usbd_video_stream_start_write.*\{" 

# Check for any buffer switching logic in the video class implementation
rg -A 5 "packet_buffer\[.*\]"

# Look for any additional synchronization mechanisms
rg -A 5 "tx_flag"

Length of output: 20982


Script:

#!/bin/bash
# Search for the implementation of usbd_video_stream_split_transfer and its buffer handling
rg -A 15 "usbd_video_stream_split_transfer.*\{" 

# Look for any additional buffer synchronization in the video class implementation
rg -A 10 "class.*video.*\{" --type cpp

Length of output: 3736

class/video/usbd_video.c (3)

21-28: LGTM: Well-structured dual buffer management fields

The new fields in usbd_video_priv provide a clean implementation of ping-pong buffering with clear separation of buffer states and metadata.


791-802: LGTM: Clean buffer switching implementation

The implementation properly manages buffer alternation with clear state transitions and flag management.


886-905: LGTM: Clean initialization of dual buffer system

The function properly validates inputs and initializes all necessary state for the ping-pong buffer implementation.

Comment on lines 805 to 848
if (!g_usbd_video[busid].ep_buf0_ready) {
remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;

uint32_t len2 = MIN(len, g_usbd_video[busid].probe.dwMaxPayloadTransferSize - g_usbd_video[busid].stream_headerlen);

usb_memcpy(&g_usbd_video[busid].ep_buffer[offset + g_usbd_video[busid].stream_headerlen],
&g_usbd_video[busid].stream_buf[g_usbd_video[busid].stream_offset],
len2);
if (remain == 0) {
g_usbd_video[busid].stream_frameid ^= 1;
g_usbd_video[busid].stream_finish = true;
} else {
len = MIN(remain, (g_usbd_video[busid].probe.dwMaxPayloadTransferSize - g_usbd_video[busid].stream_headerlen) * g_usbd_video[busid].max_packets);

offset = 0;
while (len > 0) {
header = (struct video_payload_header *)&g_usbd_video[busid].ep_buf0[offset];
header->bHeaderLength = g_usbd_video[busid].stream_headerlen;
header->headerInfoUnion.bmheaderInfo = 0;
header->headerInfoUnion.headerInfoBits.endOfHeader = 1;
header->headerInfoUnion.headerInfoBits.endOfFrame = 0;
header->headerInfoUnion.headerInfoBits.frameIdentifier = g_usbd_video[busid].stream_frameid;

uint32_t len2 = MIN(len, g_usbd_video[busid].probe.dwMaxPayloadTransferSize - g_usbd_video[busid].stream_headerlen);

usb_memcpy(&g_usbd_video[busid].ep_buf0[offset + g_usbd_video[busid].stream_headerlen],
&g_usbd_video[busid].stream_buf[g_usbd_video[busid].stream_offset],
len2);

g_usbd_video[busid].stream_offset += len2;
len -= len2;
offset += (len2 + g_usbd_video[busid].stream_headerlen);

if (g_usbd_video[busid].stream_offset == g_usbd_video[busid].stream_len) {
header->headerInfoUnion.headerInfoBits.endOfFrame = 1;
}
}
g_usbd_video[busid].ep_buf0_len = offset;
g_usbd_video[busid].ep_buf0_ready = true;
if (!g_usbd_video[busid].ep_buf1_ready) {
g_usbd_video[busid].ep_buf_idx = 0;
usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring duplicated buffer preparation logic

The buffer preparation code for buf0 and buf1 is nearly identical, which could lead to maintenance issues. Consider extracting this into a shared helper function.

+static void prepare_video_buffer(struct usbd_video_priv *video, uint8_t *buffer,
+                               uint32_t *buffer_len, bool *buffer_ready) {
+    struct video_payload_header *header;
+    uint32_t remain = video->stream_len - video->stream_offset;
+    
+    if (remain == 0) {
+        video->stream_frameid ^= 1;
+        video->stream_finish = true;
+        return;
+    }
+    
+    uint32_t len = MIN(remain, (video->probe.dwMaxPayloadTransferSize - 
+                       video->stream_headerlen) * video->max_packets);
+    uint32_t offset = 0;
+    
+    while (len > 0) {
+        header = (struct video_payload_header *)&buffer[offset];
+        header->bHeaderLength = video->stream_headerlen;
+        header->headerInfoUnion.bmheaderInfo = 0;
+        header->headerInfoUnion.headerInfoBits.endOfHeader = 1;
+        header->headerInfoUnion.headerInfoBits.endOfFrame = 0;
+        header->headerInfoUnion.headerInfoBits.frameIdentifier = video->stream_frameid;
+
+        uint32_t len2 = MIN(len, video->probe.dwMaxPayloadTransferSize - 
+                           video->stream_headerlen);
+
+        usb_memcpy(&buffer[offset + video->stream_headerlen],
+                   &video->stream_buf[video->stream_offset],
+                   len2);
+
+        video->stream_offset += len2;
+        len -= len2;
+        offset += (len2 + video->stream_headerlen);
+
+        if (video->stream_offset == video->stream_len) {
+            header->headerInfoUnion.headerInfoBits.endOfFrame = 1;
+        }
+    }
+    *buffer_len = offset;
+    *buffer_ready = true;
+}

Usage:

-    if (!g_usbd_video[busid].ep_buf0_ready) {
-        remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;
-        // ... existing buf0 preparation code
-    }
+    if (!g_usbd_video[busid].ep_buf0_ready) {
+        prepare_video_buffer(&g_usbd_video[busid], g_usbd_video[busid].ep_buf0,
+                           &g_usbd_video[busid].ep_buf0_len,
+                           &g_usbd_video[busid].ep_buf0_ready);
+    }

-    if (!g_usbd_video[busid].ep_buf1_ready) {
-        remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;
-        // ... existing buf1 preparation code
-    }
+    if (!g_usbd_video[busid].ep_buf1_ready) {
+        prepare_video_buffer(&g_usbd_video[busid], g_usbd_video[busid].ep_buf1,
+                           &g_usbd_video[busid].ep_buf1_len,
+                           &g_usbd_video[busid].ep_buf1_ready);
+    }

Also applies to: 846-879

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
demo/video_static_h264_template.c (2)

209-209: LGTM! Memory allocation and initialization look correct.

The buffer declaration with USB_NOCACHE_RAM_SECTION and USB_MEM_ALIGNX attributes is appropriate for DMA operations. The initialization properly clears both buffers.

Consider documenting the memory requirements (80KB total) in comments, as this might be important for resource-constrained systems.

Also applies to: 217-217


Line range hint 221-226: Consider improving the busy-wait implementation.

While the ping-pong buffer implementation is good for performance, the busy-wait loop could be optimized:

  1. No timeout mechanism could lead to infinite waiting
  2. No error handling for transfer failures
  3. CPU is blocked during waiting

Consider implementing this more robust version:

-            usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_h264, sizeof(cherryusb_h264));
-            while (iso_tx_busy) {
-                if (tx_flag == 0) {
-                    break;
-                }
-            }
+            uint32_t timeout = 1000; // Adjust timeout value as needed
+            int ret = usbd_video_stream_start_write(busid, VIDEO_IN_EP, 
+                &packet_buffer[0][0], &packet_buffer[1][0],
+                MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE,
+                (uint8_t *)cherryusb_h264, sizeof(cherryusb_h264));
+            
+            if (ret < 0) {
+                // Handle transfer start error
+                continue;
+            }
+            
+            while (iso_tx_busy && timeout > 0 && tx_flag) {
+                // Consider adding a small delay here to reduce CPU usage
+                timeout--;
+            }
+            
+            if (timeout == 0) {
+                // Handle timeout error
+                iso_tx_busy = false;
+            }
demo/video_static_yuyv_template.c (2)

Line range hint 220-225: Add error handling for stream write operation.

While the ping-pong buffer implementation is correct, the stream write operation should include error handling to manage potential failures gracefully.

Consider applying this improvement:

-            usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_yuyv, sizeof(cherryusb_yuyv));
+            int ret = usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], 
+                                                   MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, 
+                                                   (uint8_t *)cherryusb_yuyv, sizeof(cherryusb_yuyv));
+            if (ret < 0) {
+                iso_tx_busy = false;
+                // Add appropriate error handling (e.g., logging, retry logic)
+                continue;
+            }

Line range hint 9-9: Consider optimizing packet count for better throughput.

The current implementation uses MAX_PACKETS_IN_ONE_TRANSFER = 1, which might limit throughput, especially in high-speed USB mode. Consider increasing this value to allow multiple packets per transfer, which could better utilize the ping-pong buffer implementation and improve overall performance.

Note that this would require:

  1. Adjusting buffer sizes accordingly
  2. Ensuring the USB controller supports larger transfer sizes
  3. Testing with different packet counts to find the optimal value for your use case

Also applies to: 211-225

class/video/usbd_video.c (1)

759-791: Consider optimizing buffer preparation logic.

The function is well-structured and handles buffer preparation correctly. However, there's an opportunity to improve error handling and bounds checking.

Consider adding these safety checks:

 static uint32_t usbd_video_prepare_ep_buf_data(uint8_t busid, uint32_t remain, uint8_t *ep_buf)
 {
+    if (!ep_buf || remain == 0) {
+        return 0;
+    }
+
     struct video_payload_header *header;
     uint32_t len;
     uint32_t offset;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 328ef42 and cfe1325.

📒 Files selected for processing (6)
  • class/video/usbd_video.c (3 hunks)
  • class/video/usbd_video.h (1 hunks)
  • demo/video_audiov1_hid_template.c (2 hunks)
  • demo/video_static_h264_template.c (1 hunks)
  • demo/video_static_mjpeg_template.c (2 hunks)
  • demo/video_static_yuyv_template.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • class/video/usbd_video.h
  • demo/video_audiov1_hid_template.c
  • demo/video_static_mjpeg_template.c
🔇 Additional comments (3)
demo/video_static_h264_template.c (1)

Line range hint 209-226: Verify memory alignment and USB stack integration.

The ping-pong buffer implementation looks good, but let's verify:

  1. Memory alignment requirements for the USB controller
  2. Integration with the USB stack's DMA operations
✅ Verification successful

Based on the shell script results, I can now generate the final response:

Memory alignment and USB stack integration verified successfully.

The implementation follows the correct memory alignment patterns used throughout the codebase:

  • Uses USB_NOCACHE_RAM_SECTION and USB_MEM_ALIGNX macros consistently
  • Similar ping-pong buffer implementations found in other video templates (e.g., MJPEG, YUYV) with identical alignment attributes
  • Alignment size is controlled via CONFIG_USB_ALIGN_SIZE as defined in common/usb_util.h
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check memory alignment requirements in USB controller definitions
rg -A 2 "USB_MEM_ALIGNX|DMA.*ALIGN" 

# Check for similar ping-pong buffer implementations in other USB endpoints
ast-grep --pattern 'USB_NOCACHE_RAM_SECTION.*\[\d+\]\[\d+\]'

Length of output: 17584

demo/video_static_yuyv_template.c (1)

211-211: LGTM! Verify alignment requirements for USB DMA transfers.

The implementation of ping-pong buffering using a 2D array with proper memory section and alignment attributes looks good. This change should improve transmission performance by allowing one buffer to be filled while the other is being transmitted.

✅ Verification successful

Let me analyze the findings:

  1. The USB_MEM_ALIGNX macro is defined in common/usb_util.h as:
#define USB_MEM_ALIGNX __attribute__((aligned(CONFIG_USB_ALIGN_SIZE)))
  1. The macro is consistently used across the codebase for DMA-capable buffers, including:
  • USB control endpoint buffers
  • CDC/MSC/HID class buffers
  • Video/Audio streaming buffers

Let me verify the alignment size configuration:


The alignment implementation is correct and consistent with USB DMA requirements

The implementation of the packet buffer with USB_MEM_ALIGNX is properly aligned for DMA transfers:

  • The macro expands to __attribute__((aligned(4))) as CONFIG_USB_ALIGN_SIZE is defined as 4 bytes across all configurations
  • This 4-byte alignment is standard for USB DMA operations and is consistently used throughout the codebase for all DMA-capable buffers
  • The combination of USB_NOCACHE_RAM_SECTION and USB_MEM_ALIGNX ensures both proper memory section placement and alignment for efficient DMA transfers
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify USB_MEM_ALIGNX macro definition and usage
# Expected: Should find alignment macro definition that matches USB controller requirements

rg -A 2 "USB_MEM_ALIGNX"

Length of output: 17508


Script:

#!/bin/bash
# Check for USB alignment size configuration
rg "CONFIG_USB_ALIGN_SIZE"

Length of output: 2790

class/video/usbd_video.c (1)

21-28: LGTM! Well-structured dual buffer implementation.

The new fields in usbd_video_priv provide a clean and organized way to manage the ping-pong buffer mechanism. The naming is clear and the state tracking is comprehensive.

Comment on lines +836 to 860
if (!g_usbd_video[busid].ep_buf0_ready) {
remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;
if (remain == 0) {
g_usbd_video[busid].stream_frameid ^= 1;
g_usbd_video[busid].stream_finish = true;
} else {
g_usbd_video[busid].ep_buf0_len = usbd_video_prepare_ep_buf_data(busid, remain, g_usbd_video[busid].ep_buf0);
g_usbd_video[busid].ep_buf0_ready = true;
if (!g_usbd_video[busid].ep_buf1_ready) {
g_usbd_video[busid].ep_buf_idx = 0;
usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);
}
}
}

if (g_usbd_video[busid].stream_offset == g_usbd_video[busid].stream_len) {
header->headerInfoUnion.headerInfoBits.endOfFrame = 1;
if (!g_usbd_video[busid].ep_buf1_ready) {
remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;
if (remain == 0) {
g_usbd_video[busid].stream_frameid ^= 1;
g_usbd_video[busid].stream_finish = true;
} else {
g_usbd_video[busid].ep_buf1_len = usbd_video_prepare_ep_buf_data(busid, remain, g_usbd_video[busid].ep_buf1);
g_usbd_video[busid].ep_buf1_ready = true;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor duplicated buffer preparation logic.

The buffer preparation code for buf0 and buf1 is nearly identical. This duplication could lead to maintenance issues.

Consider extracting the common logic into a helper function:

+static void prepare_buffer(struct usbd_video_priv *video, uint8_t *buf,
+                         uint32_t *buf_len, bool *buf_ready, uint8_t ep,
+                         bool check_other_buffer) {
+    uint32_t remain = video->stream_len - video->stream_offset;
+    if (remain == 0) {
+        video->stream_frameid ^= 1;
+        video->stream_finish = true;
+    } else {
+        *buf_len = usbd_video_prepare_ep_buf_data(busid, remain, buf);
+        *buf_ready = true;
+        if (check_other_buffer && !other_buffer_ready) {
+            video->ep_buf_idx = buffer_index;
+            usbd_ep_start_write(busid, ep, buf, *buf_len);
+        }
+    }
+}

Usage:

-    if (!g_usbd_video[busid].ep_buf0_ready) {
-        remain = g_usbd_video[busid].stream_len - g_usbd_video[busid].stream_offset;
-        if (remain == 0) {
-            g_usbd_video[busid].stream_frameid ^= 1;
-            g_usbd_video[busid].stream_finish = true;
-        } else {
-            g_usbd_video[busid].ep_buf0_len = usbd_video_prepare_ep_buf_data(busid, remain, g_usbd_video[busid].ep_buf0);
-            g_usbd_video[busid].ep_buf0_ready = true;
-            if (!g_usbd_video[busid].ep_buf1_ready) {
-                g_usbd_video[busid].ep_buf_idx = 0;
-                usbd_ep_start_write(busid, ep, g_usbd_video[busid].ep_buf0, g_usbd_video[busid].ep_buf0_len);
-            }
-        }
-    }
+    if (!g_usbd_video[busid].ep_buf0_ready) {
+        prepare_buffer(&g_usbd_video[busid], g_usbd_video[busid].ep_buf0,
+                      &g_usbd_video[busid].ep_buf0_len,
+                      &g_usbd_video[busid].ep_buf0_ready,
+                      ep, true);
+    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 821 to 884
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if (usb_device_is_configured(busid) == 0) {
if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
return -1;
}

g_usbd_video[busid].ep_buffer = ep_buffer;
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;

usbd_video_stream_split_transfer(busid, ep);
return 0;
} No newline at end of file
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add comprehensive input validation.

While the function performs basic checks, it should validate all input parameters to ensure robust operation.

Consider adding these validations:

 int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
 {
-    if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
+    if ((usb_device_is_configured(busid) == 0) || 
+        (stream_len == 0) ||
+        !ep_buf0 || !ep_buf1 || !stream_buf ||
+        ep_bufsize < g_usbd_video[busid].probe.dwMaxPayloadTransferSize) {
         return -1;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if (usb_device_is_configured(busid) == 0) {
if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
return -1;
}
g_usbd_video[busid].ep_buffer = ep_buffer;
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;
usbd_video_stream_split_transfer(busid, ep);
return 0;
}
\ No newline at end of file
}
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if ((usb_device_is_configured(busid) == 0) ||
(stream_len == 0) ||
!ep_buf0 || !ep_buf1 || !stream_buf ||
ep_bufsize < g_usbd_video[busid].probe.dwMaxPayloadTransferSize) {
return -1;
}
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;
usbd_video_stream_split_transfer(busid, ep);
return 0;
}

- use pingpang buffer to improve tx performance

Signed-off-by: Zhihong Chen <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
demo/video_static_h264_template.c (2)

209-209: Consider dynamic buffer sizing based on MAX_PAYLOAD_SIZE

The static allocation of 40KB per buffer might be excessive for some configurations. Consider calculating the buffer size based on MAX_PAYLOAD_SIZE and MAX_PACKETS_IN_ONE_TRANSFER:

-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][40 * 1024];
+#define BUFFER_SIZE (MAX_PAYLOAD_SIZE * MAX_PACKETS_IN_ONE_TRANSFER)
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t packet_buffer[2][BUFFER_SIZE];

This would make the code more maintainable and prevent potential waste of memory in resource-constrained embedded systems.

Also applies to: 217-217


Line range hint 221-226: Consider adding explicit buffer switching mechanism

While the ping-pong buffer implementation is a good performance improvement, the current implementation could benefit from:

  1. Explicit buffer switching logic to ensure proper alternation
  2. Non-blocking wait mechanism to improve system responsiveness
+static uint8_t current_buffer = 0;
 while (1) {
     if (tx_flag) {
         iso_tx_busy = true;
-        usbd_video_stream_start_write(busid, VIDEO_IN_EP, &packet_buffer[0][0], &packet_buffer[1][0], MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, (uint8_t *)cherryusb_h264, sizeof(cherryusb_h264));
+        usbd_video_stream_start_write(busid, VIDEO_IN_EP, 
+            &packet_buffer[current_buffer][0], 
+            &packet_buffer[!current_buffer][0], 
+            MAX_PACKETS_IN_ONE_TRANSFER * MAX_PAYLOAD_SIZE, 
+            (uint8_t *)cherryusb_h264, 
+            sizeof(cherryusb_h264));
+        current_buffer = !current_buffer;
         while (iso_tx_busy) {
+            /* Add a small delay or yield to prevent tight spinning */
+            usbd_sleep_ms(1);
             if (tx_flag == 0) {
                 break;
             }
         }
     }
 }
demo/video_audiov1_hid_template.c (1)

Line range hint 483-488: Consider adding timeout and error handling.

While the implementation correctly uses the ping-pong buffers, the while loop could be improved with a timeout mechanism to prevent potential deadlocks.

-            while (video_iso_tx_busy) {
-                if (video_tx_flag == 0) {
-                    break;
-                }
-            }
+            uint32_t timeout = 1000; // Adjust timeout value as needed
+            while (video_iso_tx_busy && timeout > 0) {
+                if (video_tx_flag == 0) {
+                    break;
+                }
+                timeout--;
+                // Add small delay if needed
+            }
+            if (timeout == 0) {
+                // Handle timeout error
+                USB_LOG_RAW("Video transfer timeout\r\n");
+            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cfe1325 and e8ae0ba.

📒 Files selected for processing (6)
  • class/video/usbd_video.c (3 hunks)
  • class/video/usbd_video.h (1 hunks)
  • demo/video_audiov1_hid_template.c (2 hunks)
  • demo/video_static_h264_template.c (1 hunks)
  • demo/video_static_mjpeg_template.c (2 hunks)
  • demo/video_static_yuyv_template.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • class/video/usbd_video.h
  • demo/video_static_mjpeg_template.c
  • demo/video_static_yuyv_template.c
🔇 Additional comments (5)
demo/video_static_h264_template.c (1)

Line range hint 209-226: Verify buffer synchronization with USB endpoints

The ping-pong buffer implementation needs verification across different USB speeds and transfer scenarios:

Additionally, consider:

  1. Adding debug assertions to verify buffer switching correctness
  2. Documenting the minimum buffer size requirements for different USB speeds
  3. Adding stress test scenarios to the test suite
✅ Verification successful

Let me gather more information about the buffer synchronization implementation.


Based on the analysis of the shell script results, I can now provide a final response:

Buffer synchronization implementation is properly handled across USB speeds

The implementation correctly handles buffer synchronization:

  • Proper payload size configuration for both HS (1024 bytes) and FS (1020 bytes) modes
  • Synchronized buffer switching using iso_tx_busy flag
  • Appropriate memory alignment and cache settings with USB_NOCACHE_RAM_SECTION and USB_MEM_ALIGNX
  • Clean state management through event handlers that reset iso_tx_busy flag

The ping-pong buffer implementation is safe because:

  • Buffer sizes are properly defined based on USB speed
  • Transfer completion is properly tracked via iso_tx_busy
  • Buffer synchronization is maintained through the event-driven architecture
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential buffer synchronization issues in the codebase

# Look for related endpoint configuration
rg -l "usbd_video_stream_start_write.*packet_buffer" .

# Check for buffer size definitions across different USB speeds
rg -A 2 "CONFIG_USB_HS.*MAX_PAYLOAD_SIZE" .

# Look for potential race conditions in buffer handling
ast-grep --pattern 'iso_tx_busy = $value'

Length of output: 291


Script:

#!/bin/bash
# Check the implementation details across video templates

# Look for buffer size definitions and endpoint configurations
rg -A 5 "MAX_PAYLOAD_SIZE|MAX_PACKETS_IN_ONE_TRANSFER" .

# Check how iso_tx_busy is used in the codebase
rg -B 2 -A 2 "iso_tx_busy" .

# Look for USB speed configurations and their handling
rg -A 3 "CONFIG_USB_(HS|FS)" .

# Check for any existing synchronization mechanisms
rg -A 3 "usbd_video_stream.*callback" .

Length of output: 44792

demo/video_audiov1_hid_template.c (1)

272-272: LGTM! Verify memory alignment requirements.

The change to a 2D array for ping-pong buffering is well-structured and maintains the required memory attributes (USB_NOCACHE_RAM_SECTION, USB_MEM_ALIGNX).

✅ Verification successful

Based on the search results, I can see that:

  1. The USB_NOCACHE_RAM_SECTION macro is consistently defined across different configurations to place variables in non-cacheable memory using the .noncacheable section attribute.

  2. The USB_MEM_ALIGNX macro is defined in common/usb_util.h as:

#define USB_MEM_ALIGNX __attribute__((aligned(CONFIG_USB_ALIGN_SIZE)))
  1. Similar buffer declarations across multiple demo files (video, audio, HID) follow the same pattern of using both attributes to ensure proper memory alignment and cache behavior.

  2. The change to a 2D array maintains these critical attributes while adding ping-pong buffering capability.

Given this evidence, I can generate the final response:

✓ Memory alignment and cache attributes are correctly maintained

The buffer declaration properly uses both required attributes:

  • USB_NOCACHE_RAM_SECTION: Places the buffer in non-cacheable memory section
  • USB_MEM_ALIGNX: Ensures proper alignment based on USB configuration

The change to 2D array preserves these critical memory requirements while enabling ping-pong buffering.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify memory alignment macros are properly defined
rg -A 2 "USB_MEM_ALIGNX|USB_NOCACHE_RAM_SECTION" --type c

Length of output: 21906

class/video/usbd_video.c (3)

21-28: LGTM: Well-structured dual buffer management fields.

The new fields in usbd_video_priv are well-organized and provide a solid foundation for implementing the ping-pong buffer mechanism.


759-791: LGTM: Well-implemented buffer preparation logic.

The function correctly handles video payload preparation, including headers and data copying. The use of MIN macro ensures safe length calculations.

The previous review comment about extracting this into a shared helper function is still valid. This would help reduce code duplication in the buffer preparation blocks.


822-833: Consider adding synchronization for buffer state changes.

While the ping-pong buffer switching logic is well-implemented, there's a potential race condition if both buffers become ready simultaneously. Consider adding atomic operations or a mutex to protect the buffer state changes.

Also applies to: 836-849, 851-858

Comment on lines 821 to 884
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if (usb_device_is_configured(busid) == 0) {
if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
return -1;
}

g_usbd_video[busid].ep_buffer = ep_buffer;
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;

usbd_video_stream_split_transfer(busid, ep);
return 0;
} No newline at end of file
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance input parameter validation.

While basic validation is present, consider adding these checks:

  • Validate that ep_buf0 and ep_buf1 are not NULL
  • Ensure ep_bufsize is sufficient for dwMaxPayloadTransferSize
  • Verify stream_buf is not NULL

Apply this diff to improve validation:

 int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
 {
-    if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
+    if ((usb_device_is_configured(busid) == 0) || 
+        (stream_len == 0) ||
+        !ep_buf0 || !ep_buf1 || !stream_buf ||
+        ep_bufsize < g_usbd_video[busid].probe.dwMaxPayloadTransferSize) {
         return -1;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if (usb_device_is_configured(busid) == 0) {
if ((usb_device_is_configured(busid) == 0) || (stream_len == 0)) {
return -1;
}
g_usbd_video[busid].ep_buffer = ep_buffer;
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;
usbd_video_stream_split_transfer(busid, ep);
return 0;
}
\ No newline at end of file
}
int usbd_video_stream_start_write(uint8_t busid, uint8_t ep, uint8_t *ep_buf0, uint8_t *ep_buf1, uint32_t ep_bufsize, uint8_t *stream_buf, uint32_t stream_len)
{
if ((usb_device_is_configured(busid) == 0) ||
(stream_len == 0) ||
!ep_buf0 || !ep_buf1 || !stream_buf ||
ep_bufsize < g_usbd_video[busid].probe.dwMaxPayloadTransferSize) {
return -1;
}
g_usbd_video[busid].ep_buf0 = ep_buf0;
g_usbd_video[busid].ep_buf1 = ep_buf1;
g_usbd_video[busid].ep_buf0_ready = false;
g_usbd_video[busid].ep_buf1_ready = false;
g_usbd_video[busid].ep_buf_idx = 0;
g_usbd_video[busid].stream_finish = false;
g_usbd_video[busid].max_packets = ep_bufsize / g_usbd_video[busid].probe.dwMaxPayloadTransferSize;
g_usbd_video[busid].stream_buf = stream_buf;
g_usbd_video[busid].stream_len = stream_len;
g_usbd_video[busid].stream_offset = 0;
usbd_video_stream_split_transfer(busid, ep);
return 0;
}

@sakumisu sakumisu merged commit 82a0d24 into cherry-embedded:master Nov 25, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants