diff --git a/lib/ESP32_USB_STREAM/.github/workflows/arduino_lint.yml b/lib/ESP32_USB_STREAM/.github/workflows/arduino_lint.yml new file mode 100644 index 0000000..0a6b9a8 --- /dev/null +++ b/lib/ESP32_USB_STREAM/.github/workflows/arduino_lint.yml @@ -0,0 +1,15 @@ +name: Arduino Lint Action + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update diff --git a/lib/ESP32_USB_STREAM/.github/workflows/check_versions.yml b/lib/ESP32_USB_STREAM/.github/workflows/check_versions.yml new file mode 100644 index 0000000..eb2d116 --- /dev/null +++ b/lib/ESP32_USB_STREAM/.github/workflows/check_versions.yml @@ -0,0 +1,30 @@ +name: Check Versions + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check_versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get latest release info of repository + id: last_release + uses: InsonusK/get-latest-release@v1.1.0 + with: + myToken: ${{ github.token }} + exclude_types: "draft|prerelease" + view_top: 1 + - name: Print result + run: | + echo "id: ${{ steps.last_release.outputs.id }}" + echo "name: ${{ steps.last_release.outputs.name }}" + echo "tag_name: ${{ steps.last_release.outputs.tag_name }}" + echo "created_at: ${{ steps.last_release.outputs.created_at }}" + echo "draft: ${{ steps.last_release.outputs.draft }}" + echo "prerelease: ${{ steps.last_release.outputs.prerelease }}" + echo "url: ${{ steps.last_release.outputs.url }}" + - name: Check & Compare versions + run: bash ./.github/scripts/check_versions.sh ${{ steps.last_release.outputs.tag_name }} + diff --git a/lib/ESP32_USB_STREAM/.github/workflows/pre-commit.yml b/lib/ESP32_USB_STREAM/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..51f77cd --- /dev/null +++ b/lib/ESP32_USB_STREAM/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/.gitignore b/lib/ESP32_USB_STREAM/.gitignore new file mode 100644 index 0000000..657ffab --- /dev/null +++ b/lib/ESP32_USB_STREAM/.gitignore @@ -0,0 +1,71 @@ +.config +*.o +*.pyc +*.orig + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Unit Test CMake compile log folder +log_ut_cmake + +TEST_LOGS + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +test_multi_heap_host + +# VS Code Settings +.vscode/ + +# VIM files +*.swp +*.swo + +# Clion IDE CMake build & config +.idea/ +cmake-build-*/ + +# Results for the checking of the Python coding style and static analysis +.mypy_cache +flake8_output.txt + +# esp-idf default build directory name +build +build_esp*/ +build_linux*/ +size_info.txt +sdkconfig +sdkconfig.old + +# lock files for examples and components +dependencies.lock + +# managed_components for examples +managed_components + +# pytest log +pytest_embedded_log/ +pytest_log/ +.pytest_cache/ +XUNIT_RESULT.xml diff --git a/lib/ESP32_USB_STREAM/.pre-commit-config.yaml b/lib/ESP32_USB_STREAM/.pre-commit-config.yaml new file mode 100644 index 0000000..88b4d93 --- /dev/null +++ b/lib/ESP32_USB_STREAM/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +exclude: 'src/original' +repos: +- repo: https://github.com/igrr/astyle_py.git + rev: master + hooks: + - id: astyle_py + args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper'] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + types_or: [c, c++] + - id: end-of-file-fixer + types_or: [c, c++] + - id: check-merge-conflict + - id: mixed-line-ending + types_or: [c, c++] + args: ['--fix=lf'] + description: Forces to replace line ending by the UNIX 'lf' character + +- repo: https://github.com/espressif/check-copyright/ + rev: v1.0.3 + hooks: + - id: check-copyright + args: ['--config', 'check_copyright_config.yaml'] \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/CHANGELOG.md b/lib/ESP32_USB_STREAM/CHANGELOG.md new file mode 100644 index 0000000..59a3770 --- /dev/null +++ b/lib/ESP32_USB_STREAM/CHANGELOG.md @@ -0,0 +1,11 @@ +# ChangeLog + +## v0.0.1 - [2023-11-10] + +### Enhancements: + +* Only support for ESP32-S2 and ESP32-S3 SoCs. +* Support video stream through UVC Stream interface. +* Support microphone stream and speaker stream through the UAC Stream interface +* Support volume, mute and other features control through the UAC Control interface +* Support stream separately suspend and resume \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/README.md b/lib/ESP32_USB_STREAM/README.md new file mode 100644 index 0000000..9553f2a --- /dev/null +++ b/lib/ESP32_USB_STREAM/README.md @@ -0,0 +1,69 @@ +[![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_USB_Stream/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_USB_Stream/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_USB_Stream/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_USB_Stream/actions/workflows/pre-commit.yml) + +# ESP32_USB_STREAM + +ESP32_USB_STREAM is an Arduino library designed to support USB UVC + UAC host driver for ESP32-S2/ESP32-S3. It supports read/write/control multimedia streaming from usb device. For example, at most one UVC + one Microphone + one Speaker streaming can be supported at the same time. + +ESP32_USB_STREAM encapsulates the component from the [Espressif Components Registry](https://components.espressif.com/). It is developed based on [arduino-esp32](https://github.com/espressif/arduino-esp32) and can be easily downloaded and integrated into the Arduino IDE. + +## Features + +* Only support for ESP32-S2 and ESP32-S3 SoCs. +* Support video stream through UVC Stream interface. +* Support microphone stream and speaker stream through the UAC Stream interface +* Support volume, mute and other features control through the UAC Control interface +* Support stream separately suspend and resume + +## Supported Drivers + +| **Driver** | **Version** | +| ------------------------------------------------------------------ | ----------- | +| [usb_stream](https://components.espressif.com/components/espressif/usb_stream) |1.2.0| + +## How to Use + +For information on how to use the library in the Arduino IDE, please refer to the documentation for [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library). + +## Dependencies Version + +| **Name** | **Version** | +| -------------------------------------------------------------------------- | ----------- | +| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v2.0.14 | + +### Examples + +* [Getting started with a UVC](examples/GettingStartUVC/): Demonstrates how to use usb video streaming. +* [Getting started with a UAC](examples/GettingStartUAC/): Demonstrates how to use usb audio streaming. + +### Detailed Usage + +```cpp +#include "USB_STREAM.h" + +// Instantiate a Ustream object +USB_STREAM *usb = new USB_STREAM(); + +// allocate memory +uint8_t *_xferBufferA = (uint8_t *)malloc(55 * 1024); +assert(_xferBufferA != NULL); +uint8_t *_xferBufferB = (uint8_t *)malloc(55 * 1024); +assert(_xferBufferB != NULL); +uint8_t *_frameBuffer = (uint8_t *)malloc(55 * 1024); +assert(_frameBuffer != NULL); + +// Config the parameter +usb->uvcConfiguration(FRAME_RESOLUTION_ANY, FRAME_RESOLUTION_ANY, FRAME_INTERVAL_FPS_15, 55 * 1024, _xferBufferA, _xferBufferB, 55 * 1024, _frameBuffer); + + +//Register the camera frame callback function +usb->uvcCamRegisterFrameCb(&cameraFramecb, NULL); + +usb->start(); + +/*Dont forget to free the allocated memory*/ +// free(_xferBufferA); +// free(_xferBufferB); +// free(_frameBuffer); + +``` +Note: For additional details and information about the **usb_stream** functionality, please refer to the documentation provided by [ESP-IOT Solutions](https://github.com/espressif/esp-iot-solution/tree/master/components/usb/usb_stream). \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/check_copyright_config.yaml b/lib/ESP32_USB_STREAM/check_copyright_config.yaml new file mode 100644 index 0000000..d0c1e09 --- /dev/null +++ b/lib/ESP32_USB_STREAM/check_copyright_config.yaml @@ -0,0 +1,41 @@ +DEFAULT: + perform_check: yes # should the check be performed? + # Sections setting this to 'no' don't need to include any other options as they are ignored + # When a file is using a section with the option set to 'no', no checks are performed. + + # what licenses (or license expressions) are allowed for files in this section + # when setting this option in a section, you need to list all the allowed licenses + allowed_licenses: + - Apache-2.0 + license_for_new_files: Apache-2.0 # license to be used when inserting a new copyright notice + new_notice_c: | # notice for new C, CPP, H, HPP and LD files + /* + * SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: {license} + */ + new_notice_python: | # notice for new python files + # SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + # SPDX-License-Identifier: {license} + + # comment lines matching: + # SPDX-FileCopyrightText: year[-year] Espressif Systems + # or + # SPDX-FileContributor: year[-year] Espressif Systems + # are replaced with this template prefixed with the correct comment notation (# or // or *) and SPDX- notation + espressif_copyright: '{years} Espressif Systems (Shanghai) CO LTD' + +# You can create your own rules for files or group of files +examples_and_unit_tests: + include: + - 'test_apps/' + allowed_licenses: + - Apache-2.0 + - Unlicense + - CC0-1.0 + license_for_new_files: CC0-1.0 + +ignore: # You can also select ignoring files here + perform_check: no # Don't check files from that block + include: + - 'examples/' \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/examples/GettingStartUAC/GettingStartUAC.ino b/lib/ESP32_USB_STREAM/examples/GettingStartUAC/GettingStartUAC.ino new file mode 100644 index 0000000..f49825d --- /dev/null +++ b/lib/ESP32_USB_STREAM/examples/GettingStartUAC/GettingStartUAC.ino @@ -0,0 +1,45 @@ +#include +#include "USB_STREAM.h" + +/* Define the Mic frame callback function implementation */ +static void onMicFrameCallback(mic_frame_t *frame, void *ptr) +{ + // We should using higher baudrate here, to reduce the blocking time here + Serial.printf("mic callback! bit_resolution = %u, samples_frequence = %"PRIu32", data_bytes = %"PRIu32"\n", frame->bit_resolution, frame->samples_frequence, frame->data_bytes); +} + +void setup() +{ + Serial.begin(115200); + // Instantiate a Ustream object + USB_STREAM *usb = new USB_STREAM(); + + // Config the parameter + usb->uacConfiguration(UAC_CH_ANY, UAC_BITS_ANY, UAC_FREQUENCY_ANY, 6400, UAC_CH_ANY, UAC_BITS_ANY, UAC_FREQUENCY_ANY, 6400); + + //Register the camera frame callback function + usb->uacMicRegisterCb(&onMicFrameCallback, NULL); + + usb->start(); + + usb->connectWait(1000); + delay(5000); + + usb->uacMicMute((void *)0); + delay(5000); + + usb->uacMicVolume((void *)60); + + usb->uacMicSuspend(NULL); + delay(5000); + + usb->uacMicResume(NULL); + +} + +// The loop function runs repeatedly +void loop() +{ + // Delay the task for 100ms + vTaskDelay(5000); +} diff --git a/lib/ESP32_USB_STREAM/examples/GettingStartUVC/GettingStartUVC.ino b/lib/ESP32_USB_STREAM/examples/GettingStartUVC/GettingStartUVC.ino new file mode 100644 index 0000000..54c3374 --- /dev/null +++ b/lib/ESP32_USB_STREAM/examples/GettingStartUVC/GettingStartUVC.ino @@ -0,0 +1,50 @@ +#include +#include "USB_STREAM.h" + +/* Define the camera frame callback function implementation */ +static void onCameraFrameCallback(uvc_frame *frame, void *user_ptr) +{ + Serial.printf("uvc callback! frame_format = %d, seq = %" PRIu32 ", width = %" PRIu32", height = %" PRIu32 ", length = %u, ptr = %d\n", + frame->frame_format, frame->sequence, frame->width, frame->height, frame->data_bytes, (int)user_ptr); +} + +void setup() +{ + Serial.begin(115200); + // Instantiate an object + USB_STREAM *usb = new USB_STREAM(); + + // allocate memory + uint8_t *_xferBufferA = (uint8_t *)malloc(55 * 1024); + assert(_xferBufferA != NULL); + uint8_t *_xferBufferB = (uint8_t *)malloc(55 * 1024); + assert(_xferBufferB != NULL); + uint8_t *_frameBuffer = (uint8_t *)malloc(55 * 1024); + assert(_frameBuffer != NULL); + + // Config the parameter + usb->uvcConfiguration(FRAME_RESOLUTION_ANY, FRAME_RESOLUTION_ANY, FRAME_INTERVAL_FPS_15, 55 * 1024, _xferBufferA, _xferBufferB, 55 * 1024, _frameBuffer); + + //Register the camera frame callback function + usb->uvcCamRegisterCb(&onCameraFrameCallback, NULL); + + usb->start(); + + usb->connectWait(1000); + delay(5000); + + usb->uvcCamSuspend(NULL); + delay(5000); + + usb->uvcCamResume(NULL); + + /*Dont forget to free the allocated memory*/ + // free(_xferBufferA); + // free(_xferBufferB); + // free(_frameBuffer); +} + +void loop() +{ + vTaskDelay(100); +} diff --git a/lib/ESP32_USB_STREAM/library.properties b/lib/ESP32_USB_STREAM/library.properties new file mode 100644 index 0000000..30af6a2 --- /dev/null +++ b/lib/ESP32_USB_STREAM/library.properties @@ -0,0 +1,10 @@ +name=ESP32_USB_STREAM +version=0.0.1 +author=espressif +maintainer=alibukharai +sentence=ESP32_USB_STREAM is a specialized library created to facilitate the implementation of USB stream functionality on ESP SoCs. +paragraph=This means that it provides a convenient and efficient way to transmit audio and video data through USB connections, making it an invaluable tool for a wide range of applications such as audio and video streaming, data transfer, and more. Currently, it is only competible with ESP32-S2 and ESP32-S3. +category=Other +architectures=esp32 +url=https://github.com/esp-arduino-libs/ESP32_USB_Stream +includes=USB_STREAM.h \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/license.txt b/lib/ESP32_USB_STREAM/license.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/lib/ESP32_USB_STREAM/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/ESP32_USB_STREAM/src/USB_STREAM.cpp b/lib/ESP32_USB_STREAM/src/USB_STREAM.cpp new file mode 100644 index 0000000..b29a89b --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/USB_STREAM.cpp @@ -0,0 +1,337 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "USB_STREAM.h" +#include "original/include/usb_stream.h" +#include "freertos/event_groups.h" +#include "esp_log.h" + +// Define the static variable TAG with a string "driver" +static const char *TAG = "arduino-usb"; + +#define CHECK_ESP_ERROR(result, message) \ + if (result != ESP_OK) { \ + ESP_LOGE(TAG, "%s(%d): %s, Error Code: %d", __FUNCTION__, __LINE__, message, result); \ + } + +// A constructor with default values +USB_STREAM::USB_STREAM() +{ + _frame_width = FRAME_RESOLUTION_ANY; + _frame_height = FRAME_RESOLUTION_ANY; + _frame_interval = FPS2INTERVAL(15); + _spk_ch_num = UAC_CH_ANY; + _spk_bit_resolution = UAC_BITS_ANY; + _spk_samples_frequency = UAC_FREQUENCY_ANY; + _spk_buf_size = 6400; + _mic_ch_num = UAC_CH_ANY; + _mic_bit_resolution = UAC_BITS_ANY; + _mic_samples_frequency = UAC_FREQUENCY_ANY; + _mic_buf_size = 6400; +} + +// A destructor with default values +USB_STREAM::~USB_STREAM() +{} + +// Method to register a user-defined callback function +void USB_STREAM::uvcCamRegisterCb(uvc_frame_callback_t *newFunction, void *cb_arg) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "registerCallBack function error\n"); + return; + } else { + this->_user_frame_cb = newFunction; + this->_user_frame_cb_arg = cb_arg; + } +} + +// A static function that serves as the callback function for the camera frame +static void _camera_frame_cb(uvc_frame_t *frame, void *ptr) +{ + USB_STREAM *my_instance = (USB_STREAM *)ptr; + if (my_instance->_user_frame_cb != NULL) { + my_instance->_user_frame_cb(frame, my_instance->_user_frame_cb_arg); + } +} + +// Method to register a user-defined callback function +void USB_STREAM::uacMicRegisterCb(mic_callback_t *newFunction, void *cb_arg) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "registerCallBack function error\n"); + return; + } else { + this->_user_mic_frame_cb = newFunction; + this->_user_mic_frame_cb_arg = cb_arg; + } +} + +// A static function that serves as the callback function for the camera frame +static void _mic_frame_cb(mic_frame_t *frame, void *ptr) +{ + USB_STREAM *my_instance = (USB_STREAM *)ptr; + if (my_instance->_user_mic_frame_cb != NULL) { + my_instance->_user_mic_frame_cb(frame, my_instance->_user_frame_cb_arg); + } +} + +// Method to configure the uvc camera stream +void USB_STREAM::uvcConfiguration(uint16_t width, uint16_t height, uint32_t frameInterval, uint32_t transferBufferSize, uint8_t *transferBufferA, uint8_t *transferBufferB, uint32_t frameBufferSize, uint8_t *frameBuffer) +{ + if (transferBufferA == nullptr || transferBufferB == nullptr || frameBuffer == nullptr) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + + _frame_width = width; + _frame_height = height; + _frame_interval = frameInterval; + + uvc_config_t uvc_config = { + .frame_width = _frame_width, + .frame_height = _frame_height, + .frame_interval = _frame_interval, + .xfer_buffer_size = transferBufferSize, + .xfer_buffer_a = transferBufferA, + .xfer_buffer_b = transferBufferB, + .frame_buffer_size = frameBufferSize, + .frame_buffer = frameBuffer, + .frame_cb = &_camera_frame_cb, + .frame_cb_arg = this, + }; + // Configure the UVC streaming with the provided configuration + CHECK_ESP_ERROR(uvc_streaming_config(&uvc_config), "UVC streaming config fail"); +} + +// Method to configure the uac mic stream +void USB_STREAM::uacConfiguration(uint8_t mic_ch_num, uint16_t mic_bit_resolution, uint32_t mic_samples_frequency, uint32_t mic_buf_size, uint8_t spk_ch_num, uint16_t spk_bit_resolution, uint32_t spk_samples_frequency, uint32_t spk_buf_size) +{ + + _mic_ch_num = mic_ch_num; + _mic_bit_resolution = mic_bit_resolution; + _mic_samples_frequency = mic_samples_frequency; + _mic_buf_size = mic_buf_size; + + _spk_ch_num = spk_ch_num; + _spk_bit_resolution = spk_bit_resolution; + _spk_samples_frequency = spk_samples_frequency; + _spk_buf_size = spk_buf_size; + + uac_config_t uac_config = { + .spk_ch_num = _spk_ch_num, + .mic_ch_num = _mic_ch_num, + .mic_bit_resolution = _mic_bit_resolution, + .mic_samples_frequence = _mic_samples_frequency, + .spk_bit_resolution = _spk_bit_resolution, + .spk_samples_frequence = _spk_samples_frequency, + .spk_buf_size = _spk_buf_size, + .mic_buf_size = _mic_buf_size, + .mic_cb = &_mic_frame_cb, + .mic_cb_arg = this, + }; + CHECK_ESP_ERROR(uac_streaming_config(&uac_config), "UAC streaming config fail"); +} + +// Method to start the USB streaming +void USB_STREAM::start() +{ + CHECK_ESP_ERROR(usb_streaming_start(), "USB streaming start fail"); +} + +// Method to stop the USB streaming +void USB_STREAM::stop() +{ + CHECK_ESP_ERROR(usb_streaming_stop(), "USB streaming stop fail"); +} + +// Method to wait for usb stream to connect +void USB_STREAM::connectWait(int timeoutMs) +{ + CHECK_ESP_ERROR(usb_streaming_connect_wait(timeoutMs), "USB streaming wait fail"); +} + +// Method to register state for usb stream +void USB_STREAM::registerState(StateChangeCallback newFunction, void *userData) +{ + if (!newFunction) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + // Register the provided callback function + CHECK_ESP_ERROR(usb_streaming_state_register(newFunction, userData), "state register fail"); +} + +// Method to suspend uvc camera stream +void USB_STREAM::uvcCamSuspend(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UVC, CTRL_SUSPEND, ctrl_value), "uvc camera suspend fail"); +} + +// Method to resume uvc camera stream +void USB_STREAM::uvcCamResume(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UVC, CTRL_RESUME, ctrl_value), "uvc camera resume fail"); +} + +// Method to suspend uac mic stream +void USB_STREAM::uacMicSuspend(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_SUSPEND, ctrl_value), "uac mic suspend fail"); +} + +// Method to resume uac mic stream +void USB_STREAM::uacMicResume(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_RESUME, ctrl_value), "uac mic resume fail"); +} + +// Method to mute uac mic +void USB_STREAM::uacMicMute(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_UAC_MUTE, ctrl_value), "uac mic mute fail"); +} + +// Method to adjust uac mic volume +void USB_STREAM::uacMicVolume(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_UAC_VOLUME, ctrl_value), "uac mic volume fail"); +} + +// Method to suspend uac spk stream +void USB_STREAM::uacSpkSuspend(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_SPK, CTRL_SUSPEND, ctrl_value), "uac spk suspend fail"); +} + +// Method to resume uac spk stream +void USB_STREAM::uacSpkResume(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_SPK, CTRL_RESUME, ctrl_value), "uac spk resume fail"); +} + +// Method to mute uac spk +void USB_STREAM::uacSpkMute(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_UAC_MUTE, ctrl_value), "uac spk mute fail"); +} + +// Method to adjust uac spk volume +void USB_STREAM::uacSpkVolume(void *ctrl_value) +{ + CHECK_ESP_ERROR(usb_streaming_control(STREAM_UAC_MIC, CTRL_UAC_VOLUME, ctrl_value), "uac spk volume fail"); +} + +// Method to get uvc frame size +uvc_frame_size_t *USB_STREAM::uvcCamGetFrameSize(uvc_frame_size_t *uvc_frame_list) +{ + if (uvc_frame_list == nullptr) { + return NULL; + } + CHECK_ESP_ERROR(uvc_frame_size_list_get(uvc_frame_list, NULL, NULL), "uvc cam get frame size fail"); + return uvc_frame_list; +} + +// Method to get uvc frame list size +void USB_STREAM::uvcCamGetFrameListSize(size_t *frame_size, size_t *frame_index) +{ + if (frame_size == nullptr || frame_index == nullptr) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uvc_frame_size_list_get(nullptr, frame_size, frame_index), "get frame list size fail"); +} + +// Method to reset uvc cam frame +void USB_STREAM::uvcCamFrameReset(uint16_t frame_width, uint16_t frame_height, uint32_t frame_interval) +{ + + if (frame_width == NULL || frame_height == NULL || frame_interval == NULL) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uvc_frame_size_reset(frame_width, frame_height, frame_interval), "reset camera frame size fail"); +} + +// Method to read mic +void USB_STREAM::uacReadMic(uint8_t *buffer, size_t buf_size, size_t *data_bytes, size_t timeout_ms) +{ + if (buffer == nullptr || data_bytes == nullptr || buf_size == 0) { + ESP_LOGE(TAG, "Invalid parameters for uacReadMic"); + return; + } + CHECK_ESP_ERROR(uac_mic_streaming_read(buffer, buf_size, data_bytes, timeout_ms), "read mic data error"); +} + +// Method to get uac spk frame size +uac_frame_size_t *USB_STREAM::uacSpkGetFrameSize(uac_frame_size_t *uac_frame_list) +{ + if (uac_frame_list == nullptr) { + return NULL; + } + CHECK_ESP_ERROR(uac_frame_size_list_get(STREAM_UAC_SPK, uac_frame_list, NULL, NULL), "uac spk get frame size fail"); + return uac_frame_list; +} + +// Method to get uac mic frame size +uac_frame_size_t *USB_STREAM::uacMicGetFrameSize(uac_frame_size_t *uac_frame_list) +{ + if (uac_frame_list == nullptr) { + return NULL; + } + CHECK_ESP_ERROR(uac_frame_size_list_get(STREAM_UAC_MIC, uac_frame_list, NULL, NULL), "uac mic get frame size fail"); + return uac_frame_list; +} + +// Method to get uac frame list size +void USB_STREAM::uacMicGetFrameListSize(size_t *frame_size, size_t *frame_index) +{ + if (frame_size == nullptr || frame_index == nullptr) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uac_frame_size_list_get(STREAM_UAC_MIC, nullptr, frame_size, frame_index), "get frame list size fail"); +} + +// Method to get uac spk frame list size +void USB_STREAM::uacSpkGetFrameListSize(size_t *frame_size, size_t *frame_index) +{ + if (frame_size == nullptr || frame_index == nullptr) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uac_frame_size_list_get(STREAM_UAC_MIC, nullptr, frame_size, frame_index), "get frame list size fail"); +} + +// Method to reset uac mic frame +void USB_STREAM::uacMicFrameReset(uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequency) +{ + if (ch_num == NULL || bit_resolution == NULL || samples_frequency == NULL) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uac_frame_size_reset(STREAM_UAC_MIC, ch_num, bit_resolution, samples_frequency), "reset Mic frame size fail"); +} + +// Method to reset uac spk frame +void USB_STREAM::uacSpkFrameReset(uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequency) +{ + if (ch_num == NULL || bit_resolution == NULL || samples_frequency == NULL) { + ESP_LOGE(TAG, "arguments cannot be null"); + return; + } + CHECK_ESP_ERROR(uac_frame_size_reset(STREAM_UAC_SPK, ch_num, bit_resolution, samples_frequency), "reset Spk frame size fail"); +} + +// Method to write uac frame +void USB_STREAM::uacWriteSpk(uint16_t *buffer, size_t data_bytes, size_t timeout_ms) +{ + if (buffer == nullptr || data_bytes == NULL) { + ESP_LOGE(TAG, "Invalid parameters for uacWriteSpk"); + return; + } + CHECK_ESP_ERROR(uac_spk_streaming_write(buffer, data_bytes, timeout_ms), "write spk data error"); +} diff --git a/lib/ESP32_USB_STREAM/src/USB_STREAM.h b/lib/ESP32_USB_STREAM/src/USB_STREAM.h new file mode 100644 index 0000000..1b7ec35 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/USB_STREAM.h @@ -0,0 +1,283 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "original/include/usb_stream.h" + +class USB_STREAM { + +public: + //Public member variables for storing user-defined callback function and arguments + void *_user_mic_frame_cb_arg = NULL; + void *_user_frame_cb_arg = NULL; + uvc_frame_callback_t *_user_frame_cb = NULL; + mic_callback_t *_user_mic_frame_cb = NULL; + typedef void (*StateChangeCallback)(usb_stream_state_t event, void *arg); + + /** + * @brief Construct a new USB_STREAM object + * + */ + USB_STREAM(); + + /** + * @brief Destroy the USB_STREAM object + * + */ + ~USB_STREAM(); + + /** + * @brief Start usb streaming with pre-configs, usb driver will create internal tasks to handle usb data from stream pipe, and run user's callback after new frame ready. + */ + void start(); + + /** + * @brief Stop current usb streaming, internal tasks will be delete, related resourse will be free + */ + void stop(); + + /* + * @brief Wait for USB device connection + */ + void connectWait(int timeoutMs); + + /** + * @brief This function registers a callback for USB streaming, please note that only one callback + * can be registered, the later registered callback will overwrite the previous one. + * + * @param newFunction A pointer to a function that will be called when the USB streaming state changes. + * @param usr_data usr_data is a void pointer. + */ + void registerState(StateChangeCallback newFunction, void *usr_data); + + /** + * @brief Register a callback fucntion to an object + * + * @param newFunction Callback function + * @param cb_arg callback args + */ + void uvcCamRegisterCb(uvc_frame_callback_t *newFunction, void *cb_arg); + + /** + * @brief Configuration for an object + * + * @param width width of a frame + * @param height height of a frame + * @param frameInterval frame interval + * @param transferBufferSize Transfer buffer size, using double buffer here, must larger than one frame size + * @param transferBufferA buffer a for usb payload + * @param transferBufferB Buffer b for usb payload + * @param frameBufferSize Frame buffer size, must larger than one frame size + * @param frameBuffer Buffer for one frame + */ + void uvcConfiguration(uint16_t width, uint16_t height, uint32_t frameInterval, uint32_t transferBufferSize, uint8_t *transferBufferA, uint8_t *transferBufferB, uint32_t frameBufferSize, uint8_t *frameBuffer); + + /** + * @brief Suspends USB Camera streaming + * + * @param ctrl_value control value + */ + void uvcCamSuspend(void *ctrl_value); + + /** + * @brief Resumes USB Camera streaming + * + * @param ctrl_value control value + */ + void uvcCamResume(void *ctrl_value); + + /** + * @brief Get the frame size list of current connected camera + * + * @param frame_size the frame size list + */ + uvc_frame_size_t *uvcCamGetFrameSize(uvc_frame_size_t *frame_size); + + /** + * @brief Get the frame list size of current connected Cam + * + * @param frame_size the frame list size + * @param frame_index current frame index + */ + void uvcCamGetFrameListSize(size_t *frame_size, size_t *frame_index); + + /** + * @brief Reset the expected frame size and frame interval, please reset when uvc streaming + * in suspend state.The new configs will be effective after streaming resume. + * + * Note: frame_width and frame_height can be set to 0 at the same time, which means + * no change on frame size. + * + * @param frame_width frame width, FRAME_RESOLUTION_ANY means any width + * @param frame_height frame height, FRAME_RESOLUTION_ANY means any height + * @param frame_interval frame interval, 0 means no change + */ + void uvcCamFrameReset(uint16_t frame_width, uint16_t frame_height, uint32_t frame_interval); + + /** + * @brief Suspends USB Mic streaming + * + * @param ctrl_value control value + */ + void uacMicSuspend(void *ctrl_value); + + /** + * @brief Resumes USB Mic streaming + * + * @param ctrl_value control value + */ + void uacMicResume(void *ctrl_value); + + /** + * @brief Mute USB Mic streaming + * + * @param ctrl_value control value + */ + void uacMicMute(void *ctrl_value); + + /** + * @brief Control Mic volume + * + * @param ctrl_value control value + */ + void uacMicVolume(void *ctrl_value); + + /** + * @brief Get the frame size list of current connected Mic + * + * @param frame_size the frame size list + */ + // uac_frame_size_t *uacMicGetFrameSize(size_t *frame_size); + uac_frame_size_t *uacMicGetFrameSize(uac_frame_size_t *frame_size); + + /** + * @brief Get the frame list size of current connected Mic + * + * @param frame_size the frame list size + * @param frame_index current frame index + */ + void uacMicGetFrameListSize(size_t *frame_size, size_t *frame_index); + + /** + * @brief Configuration for UAC object + * + * @param mic_ch_num microphone channel numbers + * @param mic_bit_resolution microphone resolution(bits) + * @param mic_samples_frequency microphone frequency(Hz) + * @param mic_buf_size mic receive buffer size, 0 if not use + */ + void uacConfiguration(uint8_t mic_ch_num, uint16_t mic_bit_resolution, uint32_t mic_samples_frequency, uint32_t mic_buf_size, uint8_t spk_ch_num, uint16_t spk_bit_resolution, uint32_t spk_samples_frequency, uint32_t spk_buf_size); + + /** + * @brief Register a callback fucntion to an UAC Mic object + * + * @param newFunction Callback function + * @param cb_arg callback args + */ + void uacMicRegisterCb(mic_callback_t *newFunction, void *cb_arg); + + /** + * @brief Read data from internal mic buffer, the actual size will be returned + * + * @param buffer pointer to the buffer to store the received data + * @param buf_size The size of the data buffer. + * @param data_bytes The actual size read from buffer + * @param timeout_ms The timeout value for the read operation. + */ + void uacReadMic(uint8_t *buffer, size_t buf_size, size_t *data_bytes, size_t timeout_ms); + + /** + * @brief Reset Mic audio channel number, bit resolution and samples frequency, please reset when the streaming + * in suspend state. The new configs will be effective after streaming resume. + * + * @param ch_num audio channel numbers + * @param bit_resolution audio bit resolution + * @param samples_frequency audio samples frequency + */ + void uacMicFrameReset(uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequency); + + /** + * @brief Control Mic volume + * + * @param ctrl_value control value + */ + void uacSpkSuspend(void *ctrl_value); + + /** + * @brief Resumes USB Spk streaming + * + * @param ctrl_value control value + */ + void uacSpkResume(void *ctrl_value); + + /** + * @brief Mute USB spk streaming + * @param ctrl_value control value + */ + void uacSpkMute(void *ctrl_value); + + /** + * @brief Control Spk volume + * + * @param ctrl_value control value + */ + void uacSpkVolume(void *ctrl_value); + + /** + * @brief Get the frame size list of current connected Spk + * + * @param frame_size the frame size list + */ + uac_frame_size_t *uacSpkGetFrameSize(uac_frame_size_t *frame_size); + + /** + * @brief Get the frame list size of current connected Spk + * + * @param frame_size the frame list size + * @param frame_index current frame index + */ + void uacSpkGetFrameListSize(size_t *frame_size, size_t *frame_index); + + /** + * @brief Write data to the speaker buffer, will be send out when USB device is ready + * + * @param buffer The data to be written. + * @param data_bytes The size of the data to be written. + * @param timeout_ms The timeout value for writing data to the buffer. + */ + void uacWriteSpk(uint16_t *buffer, size_t data_bytes, size_t timeout_ms); + + /** + * @brief Reset Spk audio channel number, bit resolution and samples frequency, please reset when the streaming + * in suspend state. The new configs will be effective after streaming resume. + * + * @param ch_num audio channel numbers + * @param bit_resolution audio bit resolution + * @param samples_frequency audio samples frequency + */ + void uacSpkFrameReset(uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequency); + +private: + uint16_t _frame_width; + uint16_t _frame_height; + uint32_t _frame_interval; + uint8_t _spk_ch_num; + uint16_t _spk_bit_resolution; + uint32_t _spk_samples_frequency; + uint32_t _spk_buf_size; + uint8_t _mic_ch_num; + uint16_t _mic_bit_resolution; + uint32_t _mic_samples_frequency; + uint32_t _mic_buf_size; +}; diff --git a/lib/ESP32_USB_STREAM/src/original/descriptor.c b/lib/ESP32_USB_STREAM/src/original/descriptor.c new file mode 100644 index 0000000..a63bd2a --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/descriptor.c @@ -0,0 +1,526 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "esp_log.h" +#include "usb/usb_host.h" +#include "usb/usb_types_ch9.h" +#include "usb_stream_descriptor.h" + +// esp32/tools/esp32-arduino-libs/idf-release_v5.1-6b1f40b9bf/esp32s3/include/usb/include/usb/ +void print_device_descriptor(const uint8_t *buff) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + const usb_device_desc_t *devc_desc = (const usb_device_desc_t *)buff; + printf("*** Device descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("bLength %d\n", devc_desc->bLength); + printf("bDescriptorType %d\n", devc_desc->bDescriptorType); +#endif + printf("bcdUSB %d.%d0\n", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF)); + printf("bDeviceClass 0x%x\n", devc_desc->bDeviceClass); + printf("bDeviceSubClass 0x%x\n", devc_desc->bDeviceSubClass); + printf("bDeviceProtocol 0x%x\n", devc_desc->bDeviceProtocol); + printf("bMaxPacketSize0 %d\n", devc_desc->bMaxPacketSize0); + printf("idVendor 0x%x\n", devc_desc->idVendor); + printf("idProduct 0x%x\n", devc_desc->idProduct); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("bcdDevice %d.%d0\n", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF)); + printf("iManufacturer %d\n", devc_desc->iManufacturer); + printf("iProduct %d\n", devc_desc->iProduct); + printf("iSerialNumber %d\n", devc_desc->iSerialNumber); +#endif + printf("bNumConfigurations %d\n", devc_desc->bNumConfigurations); +#endif +} + +void print_uvc_header_desc(const uint8_t *buff, uint8_t sub_class) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + if (sub_class == VIDEO_SUBCLASS_CONTROL) { + const vc_interface_desc_t *desc = (const vc_interface_desc_t *) buff; + printf("\t*** Class-specific VC Interface Descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength 0x%x\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubType %u\n", desc->bDescriptorSubType); +#endif + printf("\tbcdUVC %x\n", desc->bcdUVC); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\twTotalLength %u\n", desc->wTotalLength); + printf("\tdwClockFrequency %"PRIu32"\n", desc->dwClockFrequency); + printf("\tbFunctionProtocol %u\n", desc->bFunctionProtocol); + printf("\tbInCollection %u\n", desc->bInCollection); + printf("\tbaInterfaceNr %u\n", desc->baInterfaceNr); +#endif + } else if (sub_class == VIDEO_SUBCLASS_STREAMING) { + const vs_interface_desc_t *desc = (const vs_interface_desc_t *) buff; + printf("\t*** Class-specific VS Interface Descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength 0x%x\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubType %u\n", desc->bDescriptorSubType); +#endif + printf("\tbNumFormats %x\n", desc->bNumFormats); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\twTotalLength %u\n", desc->wTotalLength); + printf("\tbEndpointAddress %u\n", desc->bEndpointAddress); + printf("\tbFunctionProtocol %u\n", desc->bFunctionProtocol); + printf("\tbmInfo 0x%x\n", desc->bmInfo); + printf("\tbTerminalLink %u\n", desc->bTerminalLink); + printf("\tbStillCaptureMethod %u\n", desc->bStillCaptureMethod); + printf("\tbTriggerSupport %u\n", desc->bTriggerSupport); + printf("\tbTriggerUsage %u\n", desc->bTriggerUsage); + printf("\tbControlSize %u\n", desc->bControlSize); + printf("\tbmaControls 0x%x\n", desc->bmaControls); +#endif + } +#endif +} + +void parse_vs_format_mjpeg_desc(const uint8_t *buff, uint8_t *format_idx, uint8_t *frame_num) +{ + if (buff == NULL) { + return; + } + const vs_format_desc_t *desc = (const vs_format_desc_t *) buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** VS Format MJPEG Descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength 0x%x\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubType 0x%x\n", desc->bDescriptorSubType); +#endif + printf("\tbFormatIndex 0x%x\n", desc->bFormatIndex); + printf("\tbNumFrameDescriptors %u\n", desc->bNumFrameDescriptors); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbmFlags 0x%x\n", desc->bmFlags); +#endif + printf("\tbDefaultFrameIndex %u\n", desc->bDefaultFrameIndex); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbAspectRatioX %u\n", desc->bAspectRatioX); + printf("\tbAspectRatioY %u\n", desc->bAspectRatioY); + printf("\tbmInterlaceFlags 0x%x\n", desc->bmInterlaceFlags); + printf("\tbCopyProtect %u\n", desc->bCopyProtect); +#endif +#endif + if (format_idx) { + *format_idx = desc->bFormatIndex; + } + if (frame_num) { + *frame_num = desc->bNumFrameDescriptors; + } +} + +void parse_vs_frame_mjpeg_desc(const uint8_t *buff, uint8_t *frame_idx, uint16_t *width, uint16_t *heigh, uint8_t *interval_type, const uint32_t **pp_interval, uint32_t *dflt_interval) +{ + if (buff == NULL) { + return; + } + const vs_frame_desc_t *desc = (const vs_frame_desc_t *) buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** VS MJPEG Frame Descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength 0x%x\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubType 0x%x\n", desc->bDescriptorSubType); +#endif + printf("\tbFrameIndex 0x%x\n", desc->bFrameIndex); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbmCapabilities 0x%x\n", desc->bmCapabilities); +#endif + printf("\twWidth %u\n", desc->wWidth); + printf("\twHeigh %u\n", desc->wHeigh); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tdwMinBitRate %"PRIu32"\n", desc->dwMinBitRate); + printf("\tdwMaxBitRate %"PRIu32"\n", desc->dwMaxBitRate); + printf("\tdwMaxVideoFrameBufSize %"PRIu32"\n", desc->dwMaxVideoFrameBufSize); + printf("\tdwDefaultFrameInterval %"PRIu32"\n", desc->dwDefaultFrameInterval); + printf("\tbFrameIntervalType %u\n", desc->bFrameIntervalType); +#endif + + if (desc->bFrameIntervalType == 0) { + // Continuous Frame Intervals + printf("\tdwMinFrameInterval %"PRIu32"\n", desc->dwMinFrameInterval); + printf("\tdwMaxFrameInterval %"PRIu32"\n", desc->dwMaxFrameInterval); + printf("\tdwFrameIntervalStep %"PRIu32"\n", desc->dwFrameIntervalStep); + } else { + // Discrete Frame Intervals + size_t num_of_intervals = (desc->bLength - 26) / 4; + uint32_t *interval = (uint32_t *)&desc->dwFrameInterval; + for (int i = 0; i < num_of_intervals; ++i) { + printf("\tFrameInterval[%d] %"PRIu32"\n", i, interval[i]); + } + } +#endif + if (width) { + *width = desc->wWidth; + } + if (heigh) { + *heigh = desc->wHeigh; + } + if (frame_idx) { + *frame_idx = desc->bFrameIndex; + } + if (interval_type) { + *interval_type = desc->bFrameIntervalType; + } + if (pp_interval) { + *pp_interval = &(desc->dwFrameInterval); + } + if (dflt_interval) { + *dflt_interval = desc->dwDefaultFrameInterval; + } +} + +void print_ep_desc(const uint8_t *buff) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + const usb_ep_desc_t *ep_desc = (const usb_ep_desc_t *)buff; + const char *ep_type_str; + int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK; + switch (type) { + case USB_BM_ATTRIBUTES_XFER_CONTROL: + ep_type_str = "CTRL"; + break; + case USB_BM_ATTRIBUTES_XFER_ISOC: + ep_type_str = "ISOC"; + break; + case USB_BM_ATTRIBUTES_XFER_BULK: + ep_type_str = "BULK"; + break; + case USB_BM_ATTRIBUTES_XFER_INT: + ep_type_str = "INT"; + break; + default: + ep_type_str = NULL; + break; + } + + printf("\t\t*** Endpoint descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\t\tbLength %d\n", ep_desc->bLength); + printf("\t\tbDescriptorType 0x%x\n", ep_desc->bDescriptorType); +#endif + printf("\t\tbEndpointAddress 0x%x\tEP %d %s\n", ep_desc->bEndpointAddress, + USB_EP_DESC_GET_EP_NUM(ep_desc), + USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT"); + printf("\t\tbmAttributes 0x%x\t%s\n", ep_desc->bmAttributes, ep_type_str); + printf("\t\twMaxPacketSize %d\n", ep_desc->wMaxPacketSize); + printf("\t\tbInterval %d\n", ep_desc->bInterval); +#endif +} + +void parse_ep_desc(const uint8_t *buff, uint16_t *ep_mps, uint8_t *ep_addr, uint8_t *ep_attr) +{ + if (buff == NULL) { + return; + } + const usb_ep_desc_t *ep_desc = (const usb_ep_desc_t *)buff; + if (ep_addr) { + *ep_addr = ep_desc->bEndpointAddress; + } + if (ep_mps) { + *ep_mps = ep_desc->wMaxPacketSize; + } + if (ep_attr) { + *ep_attr = ep_desc->bmAttributes; + } +} + +void print_intf_desc(const uint8_t *buff) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + const usb_intf_desc_t *intf_desc = (const usb_intf_desc_t *)buff; + printf("\t*** Interface descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", intf_desc->bLength); + printf("\tbDescriptorType 0x%x\n", intf_desc->bDescriptorType); +#endif + printf("\tbInterfaceNumber %d\n", intf_desc->bInterfaceNumber); + printf("\tbAlternateSetting %d\n", intf_desc->bAlternateSetting); + printf("\tbNumEndpoints %d\n", intf_desc->bNumEndpoints); + printf("\tbInterfaceClass 0x%x (%s)\n", intf_desc->bInterfaceClass, + intf_desc->bInterfaceClass==USB_CLASS_VIDEO?"Video": + (intf_desc->bInterfaceClass==USB_CLASS_AUDIO?"Audio":"Unknown")); + printf("\tbInterfaceSubClass 0x%x\n", intf_desc->bInterfaceSubClass); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbInterfaceProtocol 0x%x\n", intf_desc->bInterfaceProtocol); + printf("\tiInterface %d\n", intf_desc->iInterface); +#endif +#endif +} + +void print_assoc_desc(const uint8_t *buff) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + const ifc_assoc_desc_t *assoc_desc = (const ifc_assoc_desc_t *)buff; + printf("*** Interface Association Descriptor: "); + if (assoc_desc->bFunctionClass == USB_CLASS_VIDEO) { + printf("Video"); + } else if (assoc_desc->bFunctionClass == USB_CLASS_AUDIO) { + printf("Audio"); + } else { + printf("Unknown"); + } + printf(" ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("bLength %d\n", assoc_desc->bLength); + printf("bDescriptorType 0x%x\n", assoc_desc->bDescriptorType); + printf("bFirstInterface %d\n", assoc_desc->bFirstInterface); + printf("bInterfaceCount %d\n", assoc_desc->bInterfaceCount); + printf("bFunctionClass 0x%x\n", assoc_desc->bFunctionClass); + printf("bFunctionSubClass 0x%x\n", assoc_desc->bFunctionSubClass); + printf("bFunctionProtocol 0x%x\n", assoc_desc->bFunctionProtocol); + printf("iFunction %d\n", assoc_desc->iFunction); +#endif +#endif +} + +void print_cfg_desc(const uint8_t *buff) +{ + if (buff == NULL) { + return; + } +#ifdef CONFIG_UVC_PRINT_DESC + const usb_config_desc_t *cfg_desc = (const usb_config_desc_t *)buff; + printf("*** Configuration descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("bLength %d\n", cfg_desc->bLength); + printf("bDescriptorType 0x%x\n", cfg_desc->bDescriptorType); +#endif + printf("wTotalLength %d\n", cfg_desc->wTotalLength); + printf("bNumInterfaces %d\n", cfg_desc->bNumInterfaces); + printf("bConfigurationValue %d\n", cfg_desc->bConfigurationValue); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("iConfiguration %d\n", cfg_desc->iConfiguration); + printf("bmAttributes 0x%x\n", cfg_desc->bmAttributes); + printf("bMaxPower %dmA\n", cfg_desc->bMaxPower * 2); +#endif +#endif +} + +void parse_ac_header_desc(const uint8_t *buff, uint16_t *bcdADC, uint8_t *intf_num) +{ + if (buff == NULL) { + return; + } + const ac_interface_header_desc_t *desc = (const ac_interface_header_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio control header descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbcdADC 0x%x\n", desc->bcdADC); + printf("\twTotalLength %d\n", desc->wTotalLength); + printf("\tbInCollection %d\n", desc->bInCollection); + if (desc->bInCollection) { + const uint8_t *p_intf = desc->baInterfaceNr; + for (int i = 0; i < desc->bInCollection; ++i) { + printf("\t\tInterface number[%d] = %d\n", i, p_intf[i]); + } + } +#endif + if (bcdADC) { + *bcdADC = desc->bcdADC; + } + if (intf_num) { + *intf_num = desc->bInCollection; + } +} + +void parse_ac_input_desc(const uint8_t *buff, uint8_t *terminal_idx, uint16_t *terminal_type) +{ + if (buff == NULL) { + return; + } + const ac_interface_input_terminal_desc_t *desc = (const ac_interface_input_terminal_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio control input terminal descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbTerminalID %d\n", desc->bTerminalID); + printf("\twTerminalType 0x%x\n", desc->wTerminalType); + printf("\tbAssocTerminal %d\n", desc->bAssocTerminal); + printf("\tbNrChannels %d\n", desc->bNrChannels); + printf("\twChannelConfig 0x%04x\n", desc->wChannelConfig); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tiChannelNames %d\n", desc->iChannelNames); + printf("\tiTerminal %d\n", desc->iTerminal); +#endif +#endif + if (terminal_idx) { + *terminal_idx = desc->bTerminalID; + } + if (terminal_type) { + *terminal_type = desc->wTerminalType; + } +} + +void parse_ac_output_desc(const uint8_t *buff, uint8_t *terminal_idx, uint16_t *terminal_type) +{ + if (buff == NULL) { + return; + } + const ac_interface_output_terminal_desc_t *desc = (const ac_interface_output_terminal_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio control output terminal descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbTerminalID %d\n", desc->bTerminalID); + printf("\twTerminalType 0x%x\n", desc->wTerminalType); + printf("\tbAssocTerminal %d\n", desc->bAssocTerminal); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbSourceID %d\n", desc->bSourceID); + printf("\tiTerminal %d\n", desc->iTerminal); +#endif +#endif + if (terminal_idx) { + *terminal_idx = desc->bTerminalID; + } + if (terminal_type) { + *terminal_type = desc->wTerminalType; + } +} + +void parse_ac_feature_desc(const uint8_t *buff, uint8_t *source_idx, uint8_t *feature_unit_idx, uint8_t *volume_ch, uint8_t *mute_ch) +{ + if (buff == NULL) { + return; + } + const ac_interface_feature_unit_desc_t *desc = (const ac_interface_feature_unit_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio control feature unit descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbUnitID %d\n", desc->bUnitID); + printf("\tbSourceID %d\n", desc->bSourceID); + printf("\tbControlSize %d\n", desc->bControlSize); + for (size_t i = 0; i < (desc->bLength-7)/desc->bControlSize; i += desc->bControlSize) { + printf("\tbmaControls[ch%d] 0x%x\n", i, desc->bmaControls[i]); + } +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tiFeature %d\n", desc->iFeature); +#endif +#endif + if (feature_unit_idx) { + *feature_unit_idx = desc->bUnitID; + } + if (source_idx) { + *source_idx = desc->bSourceID; + } + uint8_t ch_num = 0; + for (size_t i = 0; i < (desc->bLength-7)/desc->bControlSize; i += desc->bControlSize) { + if ((desc->bmaControls[i] & AUDIO_FEATURE_CONTROL_VOLUME) && volume_ch) { + *volume_ch = *volume_ch | (1 << ch_num); + } + if ((desc->bmaControls[i] & AUDIO_FEATURE_CONTROL_MUTE) && mute_ch) { + *mute_ch = *mute_ch | (1 << ch_num); + } + ch_num++; + } +} + +void parse_as_general_desc(const uint8_t *buff, uint8_t *source_idx, uint16_t *format_tag) +{ + if (buff == NULL) { + return; + } + const as_interface_header_desc_t *desc = (const as_interface_header_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio stream general descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbTerminalLink %d\n", desc->bTerminalLink); + printf("\tbDelay %d\n", desc->bDelay); + printf("\twFormatTag %d\n", desc->wFormatTag); +#endif + if (source_idx) { + *source_idx = desc->bTerminalLink; + } + if (format_tag) { + *format_tag = desc->wFormatTag; + } +} + +void parse_as_type_desc(const uint8_t *buff, uint8_t *channel_num, uint8_t *bit_resolution, uint8_t *freq_type, const uint8_t **pp_samfreq) +{ + if (buff == NULL) { + return; + } + const as_interface_type_I_format_desc_t *desc = (const as_interface_type_I_format_desc_t *)buff; +#ifdef CONFIG_UVC_PRINT_DESC + printf("\t*** Audio control header descriptor ***\n"); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + printf("\tbLength %d\n", desc->bLength); + printf("\tbDescriptorType 0x%x\n", desc->bDescriptorType); + printf("\tbDescriptorSubtype 0x%x\n", desc->bDescriptorSubtype); +#endif + printf("\tbFormatType %d\n", desc->bFormatType); + printf("\tbNrChannels %d\n", desc->bNrChannels); + printf("\tbSubframeSize %d\n", desc->bSubframeSize); + printf("\tbBitResolution %d\n", desc->bBitResolution); + printf("\tbSamFreqType %d\n", desc->bSamFreqType); + if (desc->bSamFreqType == 0) { + // Continuous Frame Intervals + const uint8_t *p_samfreq = desc->tSamFreq; + uint32_t min_samfreq = (p_samfreq[2] << 16) + (p_samfreq[1] << 8) + p_samfreq[0]; + uint32_t max_samfreq = (p_samfreq[5] << 16) + (p_samfreq[4] << 8) + p_samfreq[3]; + printf("\ttLowerSamFreq %"PRIu32"\n", min_samfreq); + printf("\ttUpperSamFreq %"PRIu32"\n", max_samfreq); + } else { + const uint8_t *p_samfreq = desc->tSamFreq; + for (int i = 0; i < desc->bSamFreqType; ++i) { + printf("\ttSamFreq[%d] %"PRIu32"\n", i, (uint32_t)((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i])); + } + } +#endif + if (pp_samfreq) { + *pp_samfreq = desc->tSamFreq; + } + if (channel_num) { + *channel_num = desc->bNrChannels; + } + if (bit_resolution) { + *bit_resolution = desc->bBitResolution; + } + if (freq_type) { + *freq_type = desc->bSamFreqType; + } +} diff --git a/lib/ESP32_USB_STREAM/src/original/hcd.h b/lib/ESP32_USB_STREAM/src/original/hcd.h new file mode 100644 index 0000000..d2de4aa --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/hcd.h @@ -0,0 +1,539 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_err.h" +#include "usb_private.h" +#include "usb/usb_types_ch9.h" + +// ------------------------------------------------- Macros & Types ---------------------------------------------------- + +// ----------------------- States -------------------------- + +/** + * @brief States of the HCD port + * + * @note The port can be thought of as an abstraction of the Root Hub that contains + * a single port. + * @note These states roughly match the port states outlined in 11.5.1 of the + * USB2.0 specification. + */ +typedef enum { + HCD_PORT_STATE_NOT_POWERED, /**< The port is not powered */ + HCD_PORT_STATE_DISCONNECTED, /**< The port is powered but no device is connected */ + HCD_PORT_STATE_DISABLED, /**< A device has connected to the port but has not been reset. SOF/keep alive are not being sent */ + HCD_PORT_STATE_RESETTING, /**< The port is issuing a reset condition */ + HCD_PORT_STATE_SUSPENDED, /**< The port has been suspended. */ + HCD_PORT_STATE_RESUMING, /**< The port is issuing a resume condition */ + HCD_PORT_STATE_ENABLED, /**< The port has been enabled. SOF/keep alive are being sent */ + HCD_PORT_STATE_RECOVERY, /**< Port needs to be recovered from a fatal error (port error, overcurrent, or sudden disconnection) */ +} hcd_port_state_t; + +/** + * @brief States of an HCD pipe + * + * Active: + * - Pipe is able to transmit data. URBs can be enqueued. + * - Even if pipe has no URBs enqueued, it can still be in the active state. + * Halted: + * - An error has occurred on the pipe. URBs will no longer be executed. + * - Halt should be cleared using the HCD_PIPE_CMD_CLEAR command + */ +typedef enum { + HCD_PIPE_STATE_ACTIVE, /**< The pipe is active */ + HCD_PIPE_STATE_HALTED, /**< The pipe is halted */ +} hcd_pipe_state_t; + +// ----------------------- Events -------------------------- + +/** + * @brief HCD port events + * + * On receiving a port event, hcd_port_handle_event() should be called to handle that event + */ +typedef enum { + HCD_PORT_EVENT_NONE, /**< No event has occurred. Or the previous event is no longer valid */ + HCD_PORT_EVENT_CONNECTION, /**< A device has been connected to the port */ + HCD_PORT_EVENT_DISCONNECTION, /**< A device disconnection has been detected */ + HCD_PORT_EVENT_ERROR, /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY */ + HCD_PORT_EVENT_OVERCURRENT, /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */ +} hcd_port_event_t; + +/** + * @brief HCD pipe events + * + * @note Pipe error events will put the pipe into the HCD_PIPE_STATE_HALTED state + */ +typedef enum { + HCD_PIPE_EVENT_NONE, /**< The pipe has no events (used to indicate no events when polling) */ + HCD_PIPE_EVENT_URB_DONE, /**< The pipe has completed an URB. The URB can be dequeued */ + HCD_PIPE_EVENT_ERROR_XFER, /**< Excessive (three consecutive) transaction errors (e.g., no ACK, bad CRC etc) */ + HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL, /**< URB was not available */ + HCD_PIPE_EVENT_ERROR_OVERFLOW, /**< Received more data than requested. Usually a Packet babble error + (i.e., an IN packet has exceeded the endpoint's MPS) */ + HCD_PIPE_EVENT_ERROR_STALL, /**< Pipe received a STALL response received */ +} hcd_pipe_event_t; + +// ---------------------- Commands ------------------------- + +/** + * @brief HCD port commands + */ +typedef enum { + HCD_PORT_CMD_POWER_ON, /**< Power ON the port */ + HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port. If the port is enabled, this will cause a HCD_PORT_EVENT_SUDDEN_DISCONN event. + If the port is disabled, this will cause a HCD_PORT_EVENT_DISCONNECTION event. */ + HCD_PORT_CMD_RESET, /**< Issue a reset on the port */ + HCD_PORT_CMD_SUSPEND, /**< Suspend the port. All pipes must be halted */ + HCD_PORT_CMD_RESUME, /**< Resume the port */ + HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive). All pipes must be halted. */ +} hcd_port_cmd_t; + +/** + * @brief HCD pipe commands + * + * The pipe commands represent the list of pipe manipulations outlined in 10.5.2.2. of USB2.0 specification. + */ +typedef enum { + HCD_PIPE_CMD_HALT, /**< Halt an active pipe. The currently executing URB will be canceled. Enqueued URBs are left untouched */ + HCD_PIPE_CMD_FLUSH, /**< Can only be called when halted. Will cause all enqueued URBs to be canceled */ + HCD_PIPE_CMD_CLEAR, /**< Causes a halted pipe to become active again. Any enqueued URBs will being executing.*/ +} hcd_pipe_cmd_t; + +// -------------------- Object Types ----------------------- + +/** + * @brief Port handle type + */ +typedef void *hcd_port_handle_t; + +/** + * @brief Pipe handle type + */ +typedef void *hcd_pipe_handle_t; + +/** + * @brief Port event callback type + * + * This callback is run when a port event occurs + */ +typedef bool (*hcd_port_callback_t)(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); + +/** + * @brief Pipe event callback + * + * This callback is run when a pipe event occurs + */ +typedef bool (*hcd_pipe_callback_t)(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); + +typedef enum { + HCD_PORT_FIFO_BIAS_BALANCED, /**< Balanced FIFO sizing for RX, Non-periodic TX, and periodic TX */ + HCD_PORT_FIFO_BIAS_RX, /**< Bias towards a large RX FIFO */ + HCD_PORT_FIFO_BIAS_PTX, /**< Bias towards periodic TX FIFO */ +} hcd_port_fifo_bias_t; + +/** + * @brief HCD configuration structure + */ +typedef struct { + int intr_flags; /**< Interrupt flags for HCD interrupt */ +} hcd_config_t; + +/** + * @brief Port configuration structure + */ +typedef struct { + hcd_port_fifo_bias_t fifo_bias; /**< HCD port internal FIFO biasing */ + hcd_port_callback_t callback; /**< HCD port event callback */ + void *callback_arg; /**< User argument for HCD port callback */ + void *context; /**< Context variable used to associate the port with upper layer object */ +} hcd_port_config_t; + +/** + * @brief Pipe configuration structure + * + * @note The callback can be set to NULL if no callback is required (e.g., using HCD in a polling manner). + */ +typedef struct { + hcd_pipe_callback_t callback; /**< HCD pipe event ISR callback */ + void *callback_arg; /**< User argument for HCD pipe callback */ + void *context; /**< Context variable used to associate the pipe with upper layer object */ + const usb_ep_desc_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */ + usb_speed_t dev_speed; /**< Speed of the device */ + uint8_t dev_addr; /**< Device address of the pipe */ +} hcd_pipe_config_t; + +// --------------------------------------------- Host Controller Driver ------------------------------------------------ + +/** + * @brief Installs the Host Controller Driver + * + * - Allocates memory and interrupt resources for the HCD and underlying ports + * + * @note This function must be called before any other HCD function is called + * @note Before calling this function, the Host Controller must already be un-clock gated and reset. The USB PHY + * (internal or external, and associated GPIOs) must already be configured. + * + * @param config HCD configuration + * @retval ESP_OK: HCD successfully installed + * @retval ESP_ERR_NO_MEM: Insufficient memory + * @retval ESP_ERR_INVALID_STATE: HCD is already installed + * @retval ESP_ERR_NOT_FOUND: HCD could not allocate interrupt + * @retval ESP_ERR_INVALID_ARG: Arguments are invalid + */ +esp_err_t hcd_install(const hcd_config_t *config); + +/** + * @brief Uninstalls the HCD + * + * Before uninstalling the HCD, the following conditions should be met: + * - All ports must be uninitialized, all pipes freed + * + * @note This function will simply free the resources used by the HCD. The underlying Host Controller and USB PHY will + * not be disabled. + * + * @retval ESP_OK: HCD successfully uninstalled + * @retval ESP_ERR_INVALID_STATE: HCD is not in the right condition to be uninstalled + */ +esp_err_t hcd_uninstall(void); + +// ---------------------------------------------------- HCD Port ------------------------------------------------------- + +/** + * @brief Initialize a particular port of the HCD + * + * After a port is initialized, it will be put into the HCD_PORT_STATE_NOT_POWERED state. + * + * @note The host controller only has one port, thus the only valid port_number is 1 + * + * @param[in] port_number Port number + * @param[in] port_config Port configuration + * @param[out] port_hdl Port handle + * @retval ESP_OK: Port enabled + * @retval ESP_ERR_NO_MEM: Insufficient memory + * @retval ESP_ERR_INVALID_STATE: The port is already enabled + * @retval ESP_ERR_NOT_FOUND: Port number not found + * @retval ESP_ERR_INVALID_ARG: Arguments are invalid + */ +esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl); + +/** + * @brief Deinitialize a particular port + * + * The port must be placed in the HCD_PORT_STATE_NOT_POWERED or HCD_PORT_STATE_RECOVERY state before it can be + * deinitialized. + * + * @param port_hdl Port handle + * @retval ESP_OK: Port disabled + * @retval ESP_ERR_INVALID_STATE: The port is not in a condition to be disabled (not unpowered) + */ +esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl); + +/** + * @brief Execute a port command + * + * Call this function to manipulate a port (e.g., powering it ON, sending a reset etc). The following conditions + * must be met when calling this function: + * - The port is in the correct state for the command (e.g., port must be suspended in order to use the resume command) + * - The port does not have any pending events + * + * @note This function is internally protected by a mutex. If multiple threads call this function, this function will + * can block. + * @note The function can block + * @note For some of the commands that involve a blocking delay (e.g., reset and resume), if the port's state changes + * unexpectedly (e.g., a disconnect during a resume), this function will return ESP_ERR_INVALID_RESPONSE. + * + * @param port_hdl Port handle + * @param command Command for the HCD port + * @retval ESP_OK: Command executed successfully + * @retval ESP_ERR_INVALID_STATE: Conditions have not been met to call this function + * @retval ESP_ERR_INVALID_RESPONSE: The command is no longer valid due to a change in the port's state + */ +esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command); + +/** + * @brief Get the port's current state + * + * @param port_hdl Port handle + * @return hcd_port_state_t Current port state + */ +hcd_port_state_t hcd_port_get_state(hcd_port_handle_t port_hdl); + +/** + * @brief Get the speed of the port + * + * The speed of the port is determined by the speed of the device connected to it. + * + * @note This function is only valid after a device directly to the port and has been reset + * + * @param[in port_hdl Port handle + * @param[out] speed Speed of the port + * @retval ESP_OK Device speed obtained + * @retval ESP_ERR_INVALID_STATE: No valid device connected to the port + * @retval ESP_ERR_INVALID_ARG: Invalid arguments + */ +esp_err_t hcd_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *speed); + +/** + * @brief Handle a ports event + * + * When an port event occurs (as indicated by a callback), this function should be called the handle this event. A + * port's event should always be handled before attempting to execute a port command. Note that is actually handled + * may be different than the event reflected in the callback. + * + * If the port has no events, this function will return HCD_PORT_EVENT_NONE. + * + * @note If callbacks are not used, this function can also be used in a polling manner to repeatedly check for and + * handle a port's events. + * @note This function is internally protected by a mutex. If multiple threads call this function, this function will + * can block. + * + * @param port_hdl Port handle + * @return hcd_port_event_t The port event that was handled + */ +hcd_port_event_t hcd_port_handle_event(hcd_port_handle_t port_hdl); + +/** + * @brief Recover a port after a fatal error has occurred on it + * + * The port must be in the HCD_PORT_STATE_RECOVERY state to be called. Recovering the port will involve issuing a soft + * reset on the underlying USB controller. The port will be returned to the HCD_PORT_STATE_NOT_POWERED state. + * + * @param port_hdl Port handle + * @retval ESP_OK Port recovered successfully + * @retval ESP_ERR_INVALID_STATE Port is not in the HCD_PORT_STATE_RECOVERY state + */ +esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl); + +/** + * @brief Get the context variable of a port + * + * @param port_hdl Port handle + * @return void* Context variable + */ +void *hcd_port_get_context(hcd_port_handle_t port_hdl); + +/** + * @brief Set the bias of the HCD port's internal FIFO + * + * @note This function can only be called when the following conditions are met: + * - Port is initialized + * - Port does not have any pending events + * - Port does not have any allocated pipes + * + * @param port_hdl Port handle + * @param bias Fifo bias + * @retval ESP_OK FIFO sizing successfully set + * @retval ESP_ERR_INVALID_STATE Incorrect state for FIFO sizes to be set + */ +esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_t bias); + +// --------------------------------------------------- HCD Pipes ------------------------------------------------------- + +/** + * @brief Allocate a pipe + * + * When allocating a pipe, the HCD will assess whether there are sufficient resources (i.e., bus time, and controller + * channels). If sufficient, the pipe will be allocated. + * + * @note The host port must be in the enabled state before a pipe can be allocated + * + * @param[in] port_hdl Handle of the port this pipe will be routed through + * @param[in] pipe_config Pipe configuration + * @param[out] pipe_hdl Pipe handle + * + * @retval ESP_OK: Pipe successfully allocated + * @retval ESP_ERR_NO_MEM: Insufficient memory + * @retval ESP_ERR_INVALID_ARG: Arguments are invalid + * @retval ESP_ERR_INVALID_STATE: Host port is not in the correct state to allocate a pipe + * @retval ESP_ERR_NOT_SUPPORTED: The pipe's configuration cannot be supported + */ +esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pipe_config, hcd_pipe_handle_t *pipe_hdl); + +/** + * @brief Free a pipe + * + * Frees the resources used by an HCD pipe. The pipe's handle should be discarded after calling this function. The pipe + * must be in following condition before it can be freed: + * - All URBs have been dequeued + * + * @param pipe_hdl Pipe handle + * + * @retval ESP_OK: Pipe successfully freed + * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be freed + */ +esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Update a pipe's maximum packet size + * + * This function is intended to be called on default pipes during enumeration in order to update the pipe's maximum + * packet size. This function can only be called on a pipe that has met the following conditions: + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting + * + * @param pipe_hdl Pipe handle + * @param mps New Maximum Packet Size + * + * @retval ESP_OK: Pipe successfully updated + * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated + */ +esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); + +/** + * @brief Update a pipe's device address + * + * This function is intended to be called on default pipes during enumeration in order to update the pipe's device + * address. This function can only be called on a pipe that has met the following conditions: + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting + * + * @param pipe_hdl Pipe handle + * @param dev_addr New device address + * + * @retval ESP_OK: Pipe successfully updated + * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated + */ +esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr); + +/** + * @brief Update a pipe's callback + * + * This function is intended to be called on default pipes at the end of enumeration to switch to a callback that + * handles the completion of regular control transfer. + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting + * + * @param pipe_hdl Pipe handle + * @param callback Callback + * @param user_arg Callback argument + * @return esp_err_t + */ +esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg); + +/** + * @brief Make a pipe persist through a run time reset + * + * Normally when a HCD_PORT_CMD_RESET is called, all pipes should already have been freed. However There may be cases + * (such as during enumeration) when a pipe must persist through a reset. This function will mark a pipe as + * persistent allowing it to survive a reset. When HCD_PORT_CMD_RESET is called, the pipe can continue to be used after + * the reset. + * + * @param pipe_hdl Pipe handle + * @retval ESP_OK: Pipe successfully marked as persistent + * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be made persistent + */ +esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Get the context variable of a pipe from its handle + * + * @param pipe_hdl Pipe handle + * @return void* Context variable + */ +void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Get the current state of the pipe + * + * @param pipe_hdl Pipe handle + * @return hcd_pipe_state_t Current state of the pipe + */ +hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Get the number of in-flight URBs in the pipe + * + * Returns the current number of URBs that have been enqueued (via hcd_urb_enqueue()) and have yet to be dequeued (via + * hcd_urb_dequeue()). + * + * @param pipe_hdl Pipe handle + * @return Number of in-flight URBs + */ +unsigned int hcd_pipe_get_num_urbs(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Execute a command on a particular pipe + * + * Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs etc) + * + * @note This function can block + * + * @param pipe_hdl Pipe handle + * @param command Pipe command + * @retval ESP_OK: Command executed successfully + * @retval ESP_ERR_INVALID_STATE: The pipe is not in the correct state/condition too execute the command + */ +esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command); + +/** + * @brief Get the last event that occurred on a pipe + * + * This function allows a pipe to be polled for events (i.e., when callbacks are not used). Once an event has been + * obtained, this function reset the last event of the pipe to HCD_PIPE_EVENT_NONE. + * + * @param pipe_hdl Pipe handle + * @return hcd_pipe_event_t Last pipe event to occur + */ +hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl); + +// ---------------------------------------------------- HCD URBs ------------------------------------------------------- + +/** + * @brief Enqueue an URB to a particular pipe + * + * The following conditions must be met before an URB can be enqueued: + * - The URB is properly initialized (data buffer and transfer length are set) + * - The URB must not already be enqueued + * - The pipe must be in the HCD_PIPE_STATE_ACTIVE state + * - The pipe cannot be executing a command + * + * @param pipe_hdl Pipe handle + * @param urb URB to enqueue + * @retval ESP_OK: URB enqueued successfully + * @retval ESP_ERR_INVALID_STATE: Conditions not met to enqueue URB + */ +esp_err_t hcd_urb_enqueue(hcd_pipe_handle_t pipe_hdl, urb_t *urb); + +/** + * @brief Dequeue an URB from a particular pipe + * + * This function should be called on a pipe after a pipe receives a HCD_PIPE_EVENT_URB_DONE event. If a pipe has + * multiple URBs that can be dequeued, this function should be called repeatedly until all URBs are dequeued. If a pipe + * has no more URBs to dequeue, this function will return NULL. + * + * @param pipe_hdl Pipe handle + * @return urb_t* Dequeued URB, or NULL if no more URBs to dequeue + */ +urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Abort an enqueued URB + * + * This function will attempt to abort an URB that is already enqueued. If the URB has yet to be executed, it will be + * "canceled" and can then be dequeued. If the URB is currently in-flight or has already completed, the URB will not be + * affected by this function. + * + * @param urb URB to abort + * @retval ESP_OK: URB successfully aborted, or was not affected by this function + * @retval ESP_ERR_INVALID_STATE: URB was never enqueued + */ +esp_err_t hcd_urb_abort(urb_t *urb); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/hub.h b/lib/ESP32_USB_STREAM/src/original/hub.h new file mode 100644 index 0000000..1a8807b --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/hub.h @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "usb_private.h" +#include "usbh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ Types -------------------------------------------------------- + +/** + * @brief Hub driver configuration + */ +typedef struct { + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ +} hub_config_t; + +// ---------------------------------------------- Hub Driver Functions ------------------------------------------------- + +/** + * @brief Install Hub driver + * + * Entry: + * - USBH must already be installed + * Exit: + * - Install Hub driver memory resources + * - Initializes the HCD root port + * + * @param[in] hub_config Hub driver configuration + * @return esp_err_t + */ +esp_err_t hub_install(hub_config_t *hub_config); + +/** + * @brief Uninstall Hub driver + * + * This must be called before uninstalling the USBH + * Entry: + * - Must have stopped the root port + * Exit: + * - HCD root port deinitialized + * + * @return esp_err_t + */ +esp_err_t hub_uninstall(void); + +/** + * @brief Start the Hub driver's root port + * + * This will power the root port ON + * + * @return esp_err_t + */ +esp_err_t hub_root_start(void); + +/** + * @brief Stops the Hub driver's root port + * + * This will power OFF the root port + * + * @return esp_err_t + */ +esp_err_t hub_root_stop(void); + +/** + * @brief Hub driver's processing function + * + * Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the + * caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB to run this function. + * + * @return esp_err_t + */ +esp_err_t hub_process(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/include/arduino_config.h b/lib/ESP32_USB_STREAM/src/original/include/arduino_config.h new file mode 100644 index 0000000..28f3ca1 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/include/arduino_config.h @@ -0,0 +1,80 @@ +/* + * Automatically generated file. DO NOT EDIT. + * Espressif IoT Development Framework (ESP-IDF) #define CONFIGuration Header + */ +#pragma once +#define CONFIG_SAMPLE_PROC_TASK_STACK_SIZE 3072 +#define CONFIG_SAMPLE_PROC_TASK_PRIORITY 2 +#define CONFIG_SAMPLE_PROC_TASK_CORE 0 +#define CONFIG_USB_PROC_TASK_STACK_SIZE 3072 +#define CONFIG_USB_PROC_TASK_PRIORITY 5 +#define CONFIG_USB_PROC_TASK_CORE 1 +#define CONFIG_NUM_ISOC_STREAM_URBS 3 +#define CONFIG_NUM_PACKETS_PER_URB_URB 4 +#define CONFIG_UVC_GET_CONFIG_DESC 1 +#define USB_STREAM_VER_MAJOR (1) //version of USB Stream check .yml +#define USB_STREAM_VER_MINOR (0) +#define USB_STREAM_VER_PATCH (4) +#define CONFIG_CTRL_TRANSFER_DATA_MAX_BYTES 1024 //Max data length assumed in control transfer +#define CONFIG_NUM_BULK_STREAM_URBS 2 //Number of bulk stream URBS created for continuous enqueue +#define CONFIG_NUM_BULK_BYTES_PER_URB 2048 //Required transfer bytes of each URB, check +#define CONFIG_NUM_ISOC_UVC_URBS 3 //Number of isochronous stream URBS created for continuous enqueue +#define CONFIG_NUM_PACKETS_PER_URB 4 //Number of packets in each isochronous stream URB +#define CONFIG_USB_WAITING_AFTER_CONN_MS 50 //Waiting n ms for usb device ready after connection +#define CONFIG_NUM_ISOC_SPK_URBS 6 //Number of isochronous stream URBS created for continuous enqueue +#define CONFIG_NUM_ISOC_MIC_URBS 3 //Number of isochronous stream URBS created for continuous enqueue +#define CONFIG_UAC_MIC_CB_MIN_MS_DEFAULT 16 //Default min ms for mic callback +#define CONFIG_UAC_SPK_ST_MAX_MS_DEFAULT 16 //Default max ms for speaker stream +#define CONFIG_UAC_SPK_VOLUME_LEVEL_DEFAULT 80 //Default volume level for speaker +#define CONFIG_UAC_MIC_VOLUME_LEVEL_DEFAULT 80 //Default volume level for mic +#define CONFIG_UAC_MIC_PACKET_COMPENSATION 1 //padding data if mic packet loss +#define CONFIG_UAC_SPK_PACKET_COMPENSATION 1 //padding zero if speaker buffer empty +#define CONFIG_UAC_SPK_PACKET_COMPENSATION_SIZE_MS 10 //padding n MS zero if speaker buffer empty +#define CONFIG_UAC_SPK_PACKET_COMPENSATION_TIMEOUT_MS 1000 //padding n MS zero if speaker buffer empty +#define CONFIG_USB_PRE_ALLOC_CTRL_TRANSFER_URB 1 //Pre-allocate URB for control transfer +#define CONFIG_UVC_GET_DEVICE_DESC 1 +#define CONFIG_UVC_PRINT_DESC 1 +#define CONFIG_USB_ENUM_FAILED_RETRY 1 +#define CONFIG_USB_ENUM_FAILED_RETRY_COUNT 10 +#define CONFIG_USB_ENUM_FAILED_RETRY_DELAY_MS 200 +#define CONFIG_UVC_DROP_OVERFLOW_FRAME 1 +#define CONFIG_UVC_PRINT_PROBE_RESULT 1 +#define CONFIG_UVC_CHECK_BULK_JPEG_HEADER 1 + + +#define CONFIG_CTRL_TRANSFER_DATA_MAX_BYTES 1024 +#define CONFIG_UVC_GET_DEVICE_DESC 1 +#define CONFIG_UVC_GET_CONFIG_DESC 1 +#define CONFIG_UVC_PRINT_DESC 1 +#define CONFIG_USB_PRE_ALLOC_CTRL_TRANSFER_URB 1 +#define CONFIG_USB_PROC_TASK_PRIORITY 5 +#define CONFIG_USB_PROC_TASK_CORE 1 +#define CONFIG_USB_PROC_TASK_STACK_SIZE 3072 +#define CONFIG_USB_WAITING_AFTER_CONN_MS 50 +#define CONFIG_USB_ENUM_FAILED_RETRY 1 +#define CONFIG_USB_ENUM_FAILED_RETRY_COUNT 10 +#define CONFIG_USB_ENUM_FAILED_RETRY_DELAY_MS 200 + +#define CONFIG_SAMPLE_PROC_TASK_PRIORITY 2 +#define CONFIG_SAMPLE_PROC_TASK_CORE 0 +#define CONFIG_SAMPLE_PROC_TASK_STACK_SIZE 3072 +#define CONFIG_UVC_PRINT_PROBE_RESULT 1 +#define CONFIG_UVC_CHECK_BULK_JPEG_HEADER 1 +#define CONFIG_UVC_DROP_OVERFLOW_FRAME 1 +#define CONFIG_NUM_BULK_STREAM_URBS 2 +#define CONFIG_NUM_BULK_BYTES_PER_URB 2048 +#define CONFIG_NUM_ISOC_UVC_URBS 3 +#define CONFIG_NUM_PACKETS_PER_URB 4 + +#define CONFIG_NUM_ISOC_SPK_URBS 6 +#define CONFIG_NUM_ISOC_MIC_URBS 3 +#define CONFIG_UAC_MIC_CB_MIN_MS_DEFAULT 16 +#define CONFIG_UAC_SPK_ST_MAX_MS_DEFAULT 16 +#define CONFIG_UAC_MIC_PACKET_COMPENSATION 1 +#define CONFIG_UAC_SPK_PACKET_COMPENSATION 1 +#define CONFIG_UAC_SPK_PACKET_COMPENSATION_TIMEOUT_MS 1000 +#define CONFIG_UAC_SPK_PACKET_COMPENSATION_SIZE_MS 10 +#define CONFIG_UAC_SPK_VOLUME_LEVEL_DEFAULT 80 +#define CONFIG_UAC_MIC_VOLUME_LEVEL_DEFAULT 80 + +#define CONFIG_USB_CTRL_XFER_TIMEOUT_MS 1000 \ No newline at end of file diff --git a/lib/ESP32_USB_STREAM/src/original/include/libuvc_def.h b/lib/ESP32_USB_STREAM/src/original/include/libuvc_def.h new file mode 100644 index 0000000..31d53ba --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/include/libuvc_def.h @@ -0,0 +1,195 @@ +#pragma once + +#include +#include +#include +#include + +/** Handle on an open UVC device. + * only one device supported + */ +typedef void* uvc_device_handle_t; + +/** UVC error types, based on libusb errors + * @ingroup diag + */ +typedef enum uvc_error { + /** Success (no error) */ + UVC_SUCCESS = 0, + /** Input/output error */ + UVC_ERROR_IO = -1, + /** Invalid parameter */ + UVC_ERROR_INVALID_PARAM = -2, + /** Access denied */ + UVC_ERROR_ACCESS = -3, + /** No such device */ + UVC_ERROR_NO_DEVICE = -4, + /** Entity not found */ + UVC_ERROR_NOT_FOUND = -5, + /** Resource busy */ + UVC_ERROR_BUSY = -6, + /** Operation timed out */ + UVC_ERROR_TIMEOUT = -7, + /** Overflow */ + UVC_ERROR_OVERFLOW = -8, + /** Pipe error */ + UVC_ERROR_PIPE = -9, + /** System call interrupted */ + UVC_ERROR_INTERRUPTED = -10, + /** Insufficient memory */ + UVC_ERROR_NO_MEM = -11, + /** Operation not supported */ + UVC_ERROR_NOT_SUPPORTED = -12, + /** Device is not UVC-compliant */ + UVC_ERROR_INVALID_DEVICE = -50, + /** Mode not supported */ + UVC_ERROR_INVALID_MODE = -51, + /** Resource has a callback (can't use polling and async) */ + UVC_ERROR_CALLBACK_EXISTS = -52, + /** Undefined error */ + UVC_ERROR_OTHER = -99 +} uvc_error_t; + +/** Color coding of stream, transport-independent + * @ingroup streaming + */ +enum uvc_frame_format { + UVC_FRAME_FORMAT_UNKNOWN = 0, + /** Any supported format */ + UVC_FRAME_FORMAT_ANY = 0, + UVC_FRAME_FORMAT_UNCOMPRESSED, + UVC_FRAME_FORMAT_COMPRESSED, + /** YUYV/YUV2/YUV422: YUV encoding with one luminance value per pixel and + * one UV (chrominance) pair for every two pixels. + */ + UVC_FRAME_FORMAT_YUYV, + UVC_FRAME_FORMAT_UYVY, + /** 24-bit RGB */ + UVC_FRAME_FORMAT_RGB, + UVC_FRAME_FORMAT_BGR, + /** Motion-JPEG (or JPEG) encoded images */ + UVC_FRAME_FORMAT_MJPEG, + UVC_FRAME_FORMAT_H264, + /** Greyscale images */ + UVC_FRAME_FORMAT_GRAY8, + UVC_FRAME_FORMAT_GRAY16, + /* Raw colour mosaic images */ + UVC_FRAME_FORMAT_BY8, + UVC_FRAME_FORMAT_BA81, + UVC_FRAME_FORMAT_SGRBG8, + UVC_FRAME_FORMAT_SGBRG8, + UVC_FRAME_FORMAT_SRGGB8, + UVC_FRAME_FORMAT_SBGGR8, + /** YUV420: NV12 */ + UVC_FRAME_FORMAT_NV12, + /** Number of formats understood */ + UVC_FRAME_FORMAT_COUNT, +}; + +/** Converts an unaligned four-byte little-endian integer into an int32 */ +#define DW_TO_INT(p) ((p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24)) +/** Converts an unaligned two-byte little-endian integer into an int16 */ +#define SW_TO_SHORT(p) ((p)[0] | ((p)[1] << 8)) +/** Converts an int16 into an unaligned two-byte little-endian integer */ +#define SHORT_TO_SW(s, p) \ + (p)[0] = (s); \ + (p)[1] = (s) >> 8; +/** Converts an int32 into an unaligned four-byte little-endian integer */ +#define INT_TO_DW(i, p) \ + (p)[0] = (i); \ + (p)[1] = (i) >> 8; \ + (p)[2] = (i) >> 16; \ + (p)[3] = (i) >> 24; + +/** Streaming mode, includes all information needed to select stream + * @ingroup streaming + */ +typedef struct uvc_stream_ctrl { + uint16_t bmHint; + uint8_t bFormatIndex; + uint8_t bFrameIndex; + uint32_t dwFrameInterval; + uint16_t wKeyFrameRate; + uint16_t wPFrameRate; + uint16_t wCompQuality; + uint16_t wCompWindowSize; + uint16_t wDelay; + uint32_t dwMaxVideoFrameSize; + uint32_t dwMaxPayloadTransferSize; + uint32_t dwClockFrequency; + uint8_t bmFramingInfo; + uint8_t bPreferredVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bInterfaceNumber; +} uvc_stream_ctrl_t; + +/** UVC request code (A.8) */ +enum uvc_req_code { + UVC_RC_UNDEFINED = 0x00, + UVC_SET_CUR = 0x01, + UVC_GET_CUR = 0x81, + UVC_GET_MIN = 0x82, + UVC_GET_MAX = 0x83, + UVC_GET_RES = 0x84, + UVC_GET_LEN = 0x85, + UVC_GET_INFO = 0x86, + UVC_GET_DEF = 0x87 +}; + +/** VideoStreaming interface control selector (A.9.7) */ +enum uvc_vs_ctrl_selector { + UVC_VS_CONTROL_UNDEFINED = 0x00, + UVC_VS_PROBE_CONTROL = 0x01, + UVC_VS_COMMIT_CONTROL = 0x02, + UVC_VS_STILL_PROBE_CONTROL = 0x03, + UVC_VS_STILL_COMMIT_CONTROL = 0x04, + UVC_VS_STILL_IMAGE_TRIGGER_CONTROL = 0x05, + UVC_VS_STREAM_ERROR_CODE_CONTROL = 0x06, + UVC_VS_GENERATE_KEY_FRAME_CONTROL = 0x07, + UVC_VS_UPDATE_FRAME_SEGMENT_CONTROL = 0x08, + UVC_VS_SYNC_DELAY_CONTROL = 0x09 +}; + +/** An image frame received from the UVC device + * @ingroup streaming + */ +typedef struct uvc_frame { + /** Image data for this frame */ + void *data; + /** Size of image data buffer */ + size_t data_bytes; + /** Width of image in pixels */ + uint32_t width; + /** Height of image in pixels */ + uint32_t height; + /** Pixel data format */ + enum uvc_frame_format frame_format; + /** Number of bytes per horizontal line (undefined for compressed format) */ + size_t step; + /** Frame number (may skip, but is strictly monotonically increasing) */ + uint32_t sequence; + /** Estimate of system time when the device started capturing the image */ + struct timeval capture_time; + /** Estimate of system time when the device finished receiving the image */ + struct timespec capture_time_finished; + /** Handle on the device that produced the image. + * @warning You must not call any uvc_* functions during a callback. */ + uvc_device_handle_t *source; + /** Is the data buffer owned by the library? + * If 1, the data buffer can be arbitrarily reallocated by frame conversion + * functions. + * If 0, the data buffer will not be reallocated or freed by the library. + * Set this field to zero if you are supplying the buffer. + */ + uint8_t library_owns_data; + /** Metadata for this frame if available */ + void *metadata; + /** Size of metadata buffer */ + size_t metadata_bytes; +} uvc_frame_t; + +/** A callback function to handle incoming assembled UVC frames + * @ingroup streaming + */ +typedef void(uvc_frame_callback_t)(struct uvc_frame *frame, void *user_ptr); diff --git a/lib/ESP32_USB_STREAM/src/original/include/usb_stream.h b/lib/ESP32_USB_STREAM/src/original/include/usb_stream.h new file mode 100644 index 0000000..fa7d64a --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/include/usb_stream.h @@ -0,0 +1,360 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include "esp_err.h" +#include "usb/usb_types_stack.h" +#include "libuvc_def.h" +#include "arduino_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FRAME_RESOLUTION_ANY __UINT16_MAX__ /*!< any uvc frame resolution */ +#define UAC_FREQUENCY_ANY __UINT32_MAX__ /*!< any uac sample frequency */ +#define UAC_BITS_ANY __UINT16_MAX__ /*!< any uac bit resolution */ +#define UAC_CH_ANY 0 /*!< any uac channel number */ +#define FPS2INTERVAL(fps) (10000000ul / fps) /*!< convert fps to uvc frame interval */ +#define FRAME_INTERVAL_FPS_5 FPS2INTERVAL(5) /*!< 5 fps */ +#define FRAME_INTERVAL_FPS_10 FPS2INTERVAL(10) /*!< 10 fps */ +#define FRAME_INTERVAL_FPS_15 FPS2INTERVAL(15) /*!< 15 fps */ +#define FRAME_INTERVAL_FPS_20 FPS2INTERVAL(20) /*!< 20 fps */ +#define FRAME_INTERVAL_FPS_30 FPS2INTERVAL(25) /*!< 25 fps */ +#define FLAG_UVC_SUSPEND_AFTER_START (1 << 0) /*!< suspend uvc after usb_streaming_start */ +#define FLAG_UAC_SPK_SUSPEND_AFTER_START (1 << 1) /*!< suspend uac speaker after usb_streaming_start */ +#define FLAG_UAC_MIC_SUSPEND_AFTER_START (1 << 2) /*!< suspend uac microphone after usb_streaming_start */ + +/** + * @brief UVC stream usb transfer type, most camera using isochronous mode, + * bulk mode can also be support for higher bandwidth + */ +typedef enum { + UVC_XFER_ISOC = 0, /*!< Isochronous Transfer Mode */ + UVC_XFER_BULK, /*!< Bulk Transfer Mode */ + UVC_XFER_UNKNOWN, /*!< Unknown Mode */ +} uvc_xfer_t; + +/** + * @brief Stream id, used for control + * + */ +typedef enum { + STREAM_UVC = 0, /*!< usb video stream */ + STREAM_UAC_SPK, /*!< usb audio speaker stream */ + STREAM_UAC_MIC, /*!< usb audio microphone stream */ + STREAM_MAX, /*!< max stream id */ +} usb_stream_t; + +/** + * @brief USB device connection status + * + */ +typedef enum { + STREAM_CONNECTED = 0, + STREAM_DISCONNECTED, +} usb_stream_state_t; + +/** + * @brief Stream control type, which also depends on if device support + * + */ +typedef enum { + CTRL_NONE = 0, /*!< None */ + CTRL_SUSPEND, /*!< streaming suspend control. ctrl_data NULL */ + CTRL_RESUME, /*!< streaming resume control. ctrl_data NULL */ + CTRL_UAC_MUTE, /*!< mute control. ctrl_data (false/true) */ + CTRL_UAC_VOLUME, /*!< volume control. ctrl_data (0~100) */ + CTRL_MAX, /*!< max type value */ +} stream_ctrl_t; + +/** + * @brief UVC configurations, for params with (optional) label, users do not need to specify manually, + * unless there is a problem with descriptors, or users want to skip the get and process descriptors steps + */ +typedef struct uvc_config { + uint16_t frame_width; /*!< Picture width, set FRAME_RESOLUTION_ANY for any resolution */ + uint16_t frame_height; /*!< Picture height, set FRAME_RESOLUTION_ANY for any resolution */ + uint32_t frame_interval; /*!< Frame interval in 100-ns units, 666666 ~ 15 Fps*/ + uint32_t xfer_buffer_size; /*!< Transfer buffer size, using double buffer here, must larger than one frame size */ + uint8_t *xfer_buffer_a; /*!< Buffer a for usb payload */ + uint8_t *xfer_buffer_b; /*!< Buffer b for usb payload */ + uint32_t frame_buffer_size; /*!< Frame buffer size, must larger than one frame size */ + uint8_t *frame_buffer; /*!< Buffer for one frame */ + uvc_frame_callback_t *frame_cb; /*!< callback function to handle incoming frame */ + void *frame_cb_arg; /*!< callback function arg */ + /*!< Optional configs, Users need to specify parameters manually when they want to + skip the get and process descriptors steps (used to speed up startup)*/ + uvc_xfer_t xfer_type; /*!< (optional) UVC stream transfer type, UVC_XFER_ISOC or UVC_XFER_BULK */ + uint8_t format_index; /*!< (optional) Format index of MJPEG */ + uint8_t frame_index; /*!< (optional) Frame index, to choose resolution */ + uint16_t interface; /*!< (optional) UVC stream interface number */ + uint16_t interface_alt; /*!< (optional) UVC stream alternate interface, to choose MPS (Max Packet Size), bulk fix to 0*/ + uint8_t ep_addr; /*!< (optional) endpoint address of selected alternate interface*/ + uint32_t ep_mps; /*!< (optional) MPS of selected interface_alt */ + uint32_t flags; /*!< (optional) flags to control the driver behavers */ +} uvc_config_t; + +/** + * @brief mic frame type + * */ +typedef struct { + void *data; /*!< mic data */ + uint32_t data_bytes; /*!< mic data size */ + uint16_t bit_resolution; /*!< bit resolution in buffer */ + uint32_t samples_frequence; /*!< mic sample frequency */ +} mic_frame_t; + +/** + * @brief uvc frame type + * */ +typedef struct { + uint16_t width; /*!< frame width */ + uint16_t height; /*!< frame height */ + uint32_t interval; /*!< frame interval */ + uint32_t interval_min; /*!< frame min interval */ + uint32_t interval_max; /*!< frame max interval */ + uint32_t interval_step; /*!< frame interval step */ +} uvc_frame_size_t; + +/** + * @brief uac frame type + * */ +typedef struct { + uint8_t ch_num; /*!< channel numbers */ + uint16_t bit_resolution; /*!< bit resolution in buffer */ + uint32_t samples_frequence; /*!< sample frequency */ + uint32_t samples_frequence_min; /*!< min sample frequency */ + uint32_t samples_frequence_max; /*!< max sample frequency */ +} uac_frame_size_t; + +/** + * @brief user callback function to handle incoming mic frames + * + */ +typedef void(mic_callback_t)(mic_frame_t *frame, void *user_ptr); + +/** + * @brief user callback function to handle usb device connection status + * + */ +typedef void(state_callback_t)(usb_stream_state_t state, void *user_ptr); + +/** + * @brief UAC configurations, for params with (optional) label, users do not need to specify manually, + * unless there is a problem with descriptor parse, or a problem with the device descriptor + */ +typedef struct { + uint8_t spk_ch_num; /*!< speaker channel numbers, UAC_CH_ANY for any channel number */ + uint8_t mic_ch_num; /*!< microphone channel numbers, UAC_CH_ANY for any channel number */ + uint16_t mic_bit_resolution; /*!< microphone resolution(bits), UAC_BITS_ANY for any bit resolution */ + uint32_t mic_samples_frequence; /*!< microphone frequence(Hz), UAC_FREQUENCY_ANY for any frequency */ + uint16_t spk_bit_resolution; /*!< speaker resolution(bits), UAC_BITS_ANY for any */ + uint32_t spk_samples_frequence; /*!< speaker frequence(Hz), UAC_FREQUENCY_ANY for any frequency */ + uint32_t spk_buf_size; /*!< size of speaker send buffer, should be a multiple of spk_ep_mps */ + uint32_t mic_buf_size; /*!< mic receive buffer size, 0 if not use */ + mic_callback_t *mic_cb; /*!< mic callback, can not block in here!, NULL if not use */ + void *mic_cb_arg; /*!< mic callback args, NULL if not use */ + /*!< Optional configs, Users need to specify parameters manually when they want to + skip the get and process descriptors steps (used to speed up startup)*/ + uint16_t mic_interface; /*!< (optional) microphone stream interface number, set 0 if not use */ + uint8_t mic_ep_addr; /*!< (optional) microphone interface endpoint address */ + uint32_t mic_ep_mps; /*!< (optional) microphone interface endpoint mps */ + uint16_t spk_interface; /*!< (optional) speaker stream interface number, set 0 if not use */ + uint8_t spk_ep_addr; /*!< (optional) speaker interface endpoint address */ + uint32_t spk_ep_mps; /*!< (optional) speaker interface endpoint mps */ + uint16_t ac_interface; /*!< (optional) audio control interface number, set 0 if not use */ + uint8_t mic_fu_id; /*!< (optional) microphone feature unit id, set 0 if not use */ + uint8_t spk_fu_id; /*!< (optional) speaker feature unit id, set 0 if not use */ + uint32_t flags; /*!< (optional) flags to control the driver behavers */ +} uac_config_t; + +/** + * @brief Config UVC streaming with user defined parameters.For normal use, user only need to specify + * no-optional parameters, and set optional parameters to 0 (the driver will find the correct value from the device descriptors). + * For quick start mode, user should specify all parameters manually to skip get and process descriptors steps. + * + * @param config parameters defined in uvc_config_t + * @return esp_err_t + * ESP_ERR_INVALID_STATE USB streaming is running, user need to stop streaming first + * ESP_ERR_INVALID_ARG Invalid argument + * ESP_OK Success + */ +esp_err_t uvc_streaming_config(const uvc_config_t *config); + +/** + * @brief Config UAC streaming with user defined parameters.For normal use, user only need to specify + * no-optional parameters, and set optional parameters to 0 (the driver will find the correct value from the device descriptors). + * For quick start mode, user should specify all parameters manually to skip get and process descriptors steps. + * + * @param config parameters defined in uvc_config_t + * @return esp_err_t + * ESP_ERR_INVALID_STATE USB streaming is running, user need to stop streaming first + * ESP_ERR_INVALID_ARG Invalid argument + * ESP_OK Success + */ +esp_err_t uac_streaming_config(const uac_config_t *config); + +/** + * @brief Start usb streaming with pre-configs, usb driver will create internal tasks + * to handle usb data from stream pipe, and run user's callback after new frame ready. + * + * @return + * ESP_ERR_INVALID_STATE streaming not configured, or streaming running already + * ESP_FAIL start failed + * ESP_OK start succeed + */ +esp_err_t usb_streaming_start(void); + +/** + * @brief Stop current usb streaming, internal tasks will be delete, related resourse will be free + * + * @return + * ESP_ERR_INVALID_STATE streaming not started + * ESP_ERR_TIMEOUT stop wait timeout + * ESP_OK stop succeed + */ +esp_err_t usb_streaming_stop(void); + +/** + * @brief Wait for USB device connection + * + * @param timeout_ms timeout in ms + * @return esp_err_t + * ESP_ERR_INVALID_STATE: usb streaming not started + * ESP_ERR_TIMEOUT: timeout + * ESP_OK: device connected + */ +esp_err_t usb_streaming_connect_wait(size_t timeout_ms); + +/** + * @brief This function registers a callback for USB streaming, please note that only one callback + * can be registered, the later registered callback will overwrite the previous one. + * + * @param cb A pointer to a function that will be called when the USB streaming state changes. + * @param user_ptr user_ptr is a void pointer. + * + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE USB streaming is running, callback need register before start + */ +esp_err_t usb_streaming_state_register(state_callback_t *cb, void *user_ptr); + +/** + * @brief Control USB streaming with specific stream and control type + * @param stream stream type defined in usb_stream_t + * @param ctrl_type control type defined in stream_ctrl_t + * @param ctrl_value control value + * @return + * ESP_ERR_INVALID_ARG invalid arg + * ESP_ERR_INVALID_STATE driver not configured or not started + * ESP_ERR_NOT_SUPPORTED current device not support this control type + * ESP_FAIL control failed + * ESP_OK succeed + */ +esp_err_t usb_streaming_control(usb_stream_t stream, stream_ctrl_t ctrl_type, void *ctrl_value); + +/** + * @brief Write data to the speaker buffer, will be send out when USB device is ready + * + * @param data The data to be written. + * @param data_bytes The size of the data to be written. + * @param timeout_ms The timeout value for writing data to the buffer. + * + * @return + * ESP_ERR_INVALID_STATE spk stream not config + * ESP_ERR_NOT_FOUND spk interface not found + * ESP_ERR_TIMEOUT spk ringbuf full + * ESP_OK succeed + */ +esp_err_t uac_spk_streaming_write(void *data, size_t data_bytes, size_t timeout_ms); + +/** + * @brief Read data from internal mic buffer, the actual size will be returned + * + * @param buf pointer to the buffer to store the received data + * @param buf_size The size of the data buffer. + * @param data_bytes The actual size read from buffer + * @param timeout_ms The timeout value for the read operation. + * + * @return + * ESP_ERR_INVALID_ARG parameter error + * ESP_ERR_INVALID_STATE mic stream not config + * ESP_ERR_NOT_FOUND mic interface not found + * ESP_TIMEOUT timeout + * ESP_OK succeed + */ +esp_err_t uac_mic_streaming_read(void *buf, size_t buf_size, size_t *data_bytes, size_t timeout_ms); + +/** + * @brief Get the audio frame size list of current stream, the list contains audio channel number, bit resolution and samples frequence. + * IF list_size equals 1 and the samples_frequence equals 0, which means the frequency can be set to any value between samples_frequence_min + * and samples_frequence_max. + * + * + * @param stream the stream type + * @param frame_list the output frame list, NULL to only get the list size + * @param list_size frame list size + * @param cur_index current frame index + * @return esp_err_t + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE USB device not active + * - ESP_OK Success + */ +esp_err_t uac_frame_size_list_get(usb_stream_t stream, uac_frame_size_t *frame_list, size_t *list_size, size_t *cur_index); + +/** + * @brief Reset audio channel number, bit resolution and samples frequence, please reset when the streaming + * in suspend state. The new configs will be effective after streaming resume. + * + * @param stream stream type + * @param ch_num audio channel numbers + * @param bit_resolution audio bit resolution + * @param samples_frequence audio samples frequence + * @return esp_err_t + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE USB device not active + * - ESP_ERR_NOT_FOUND frequency not found + * - ESP_OK Success + * - ESP_FAIL Reset failed + */ +esp_err_t uac_frame_size_reset(usb_stream_t stream, uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequence); + +/** + * @brief Get the frame size list of current connected camera + * + * @param frame_list the frame size list, can be NULL if only want to get list size + * @param list_size the list size + * @param cur_index current frame index + * @return esp_err_t + * ESP_ERR_INVALID_ARG parameter error + * ESP_ERR_INVALID_STATE uvc stream not config or not active + * ESP_OK succeed + */ +esp_err_t uvc_frame_size_list_get(uvc_frame_size_t *frame_list, size_t *list_size, size_t *cur_index); + +/** + * @brief Reset the expected frame size and frame interval, please reset when uvc streaming + * in suspend state.The new configs will be effective after streaming resume. + * + * Note: frame_width and frame_height can be set to 0 at the same time, which means + * no change on frame size. + * + * @param frame_width frame width, FRAME_RESOLUTION_ANY means any width + * @param frame_height frame height, FRAME_RESOLUTION_ANY means any height + * @param frame_interval frame interval, 0 means no change + * @return esp_err_t + */ +esp_err_t uvc_frame_size_reset(uint16_t frame_width, uint16_t frame_height, uint32_t frame_interval); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.c b/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.c new file mode 100644 index 0000000..770ed60 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.c @@ -0,0 +1,367 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_idf_version.h" +#include "esp_attr.h" +#include "esp_private/usb_phy.h" +#include "usb_host_helpers.h" +#include "esp_intr_alloc.h" + +#define USB_PORT_NUM 1 //Default port number +static const char *TAG = "USB_STREAM"; +/*------------------------------------------------ USB URB Code ----------------------------------------------------*/ +void _usb_urb_clear(urb_t *urb) +{ + UVC_CHECK_RETURN_VOID(NULL != urb, "urb = NULL"); + usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&urb->transfer; + uint8_t *data_buffer = transfer_dummy->data_buffer; + size_t data_buffer_size = transfer_dummy->data_buffer_size; + int num_isoc_packets = transfer_dummy->num_isoc_packets; + void *context = transfer_dummy->context; + memset(urb, 0, sizeof(urb_t) + (num_isoc_packets * sizeof(usb_isoc_packet_desc_t))); + transfer_dummy->data_buffer = data_buffer; + transfer_dummy->data_buffer_size = data_buffer_size; + transfer_dummy->num_isoc_packets = num_isoc_packets; + transfer_dummy->context = context; +} + +urb_t *_usb_urb_alloc(int num_isoc_packets, size_t packet_data_buffer_size, void *context) +{ + uint8_t *data_buffer = NULL; + urb_t *urb = heap_caps_calloc(1, sizeof(urb_t) + (num_isoc_packets * sizeof(usb_isoc_packet_desc_t)), MALLOC_CAP_DMA); + UVC_CHECK_GOTO(NULL != urb, "urb alloc failed", _alloc_failed); + //Allocate data buffer for each URB and assign them + if (num_isoc_packets) { + /* ISOC urb: num_isoc_packets must be 0 for isoc urb */ + data_buffer = heap_caps_calloc(num_isoc_packets, packet_data_buffer_size, MALLOC_CAP_DMA); + } else { + /* no ISOC urb */ + data_buffer = heap_caps_calloc(1, packet_data_buffer_size, MALLOC_CAP_DMA); + } + UVC_CHECK_GOTO(NULL != data_buffer, "urb data_buffer alloc failed", _alloc_failed); + //Initialize URB and underlying transfer structure. Need to cast to dummy due to const fields + size_t data_buffer_size = packet_data_buffer_size * (num_isoc_packets == 0 ? 1 : num_isoc_packets); + usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&urb->transfer; + transfer_dummy->data_buffer = data_buffer; + transfer_dummy->data_buffer_size = data_buffer_size; + transfer_dummy->num_isoc_packets = num_isoc_packets; + transfer_dummy->context = context; + ESP_LOGV(TAG, "urb(%p), alloc size %d = %d * %d", urb, data_buffer_size, packet_data_buffer_size, num_isoc_packets == 0 ? 1 : num_isoc_packets); + return urb; +_alloc_failed: + free(urb); + free(data_buffer); + return NULL; +} + +void _usb_urb_free(urb_t *urb) +{ + UVC_CHECK_RETURN_VOID(NULL != urb, "urb = NULL"); + //Free data buffers of URB + if (urb->transfer.data_buffer) { + heap_caps_free(urb->transfer.data_buffer); + } + //Free the URB + heap_caps_free(urb); + ESP_LOGD(TAG, "urb free(%p)", urb); +} + + +urb_t **_usb_urb_list_alloc(uint32_t urb_num, uint32_t num_isoc_packets, uint32_t bytes_per_packet) +{ + ESP_LOGD(TAG, "urb list alloc urb_num = %"PRId32", isoc packets = %"PRId32 ", bytes_per_packet = %"PRId32, urb_num, num_isoc_packets, bytes_per_packet); + urb_t **urb_list = heap_caps_calloc(urb_num, sizeof(urb_t *), MALLOC_CAP_DMA); + UVC_CHECK(urb_list != NULL, "p_urb alloc failed", NULL); + for (int i = 0; i < urb_num; i++) { + urb_list[i] = _usb_urb_alloc(num_isoc_packets, bytes_per_packet, NULL); + UVC_CHECK_GOTO(urb_list[i] != NULL, "stream urb alloc failed", failed_); + urb_list[i]->transfer.num_bytes = (num_isoc_packets == 0 ? 1 : num_isoc_packets) * bytes_per_packet; + for (size_t j = 0; j < num_isoc_packets; j++) { + //We need to initialize each individual isoc packet descriptor of the URB + urb_list[i]->transfer.isoc_packet_desc[j].num_bytes = bytes_per_packet; + } + } + return urb_list; +failed_: + for (size_t i = 0; i < urb_num; i++) { + _usb_urb_free(urb_list[i]); + urb_list[i] = NULL; + } + free(urb_list); + return NULL; +} + +void _usb_urb_list_clear(urb_t **urb_list, uint32_t urb_num, uint32_t packets_per_urb, uint32_t bytes_per_packet) +{ + if (urb_list) { + for (size_t i = 0; i < urb_num; i++) { + _usb_urb_clear(urb_list[i]); + usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&urb_list[i]->transfer; + transfer_dummy->num_isoc_packets = packets_per_urb; + transfer_dummy->num_bytes = (packets_per_urb == 0 ? 1 : packets_per_urb) * bytes_per_packet; + for (size_t j = 0; j < packets_per_urb; j++) { + //We need to initialize each individual isoc packet descriptor of the URB + urb_list[i]->transfer.isoc_packet_desc[j].num_bytes = bytes_per_packet; + } + } + } +} + +esp_err_t _usb_urb_list_enqueue(hcd_pipe_handle_t pipe_handle, urb_t **urb_list, uint32_t urb_num) +{ + UVC_CHECK(pipe_handle != NULL, "stream pipe not init", ESP_ERR_INVALID_ARG); + UVC_CHECK(urb_list != NULL, "stream urb list not init", ESP_ERR_INVALID_ARG); + UVC_CHECK(urb_num > 0, "stream urb num invalid", ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_OK; + for (int i = 0; i < urb_num; i++) { + UVC_CHECK(urb_list[i] != NULL, "urb not init", ESP_FAIL); + ret = hcd_urb_enqueue(pipe_handle, urb_list[i]); + UVC_CHECK(ESP_OK == ret, "pipe enqueue failed", ESP_FAIL); + } + return ESP_OK; +} + +void _usb_urb_list_free(urb_t **urb_list, uint32_t urb_num) +{ + if (urb_list) { + for (size_t i = 0; i < urb_num; i++) { + _usb_urb_free(urb_list[i]); + } + free(urb_list); + } +} + +/*------------------------------------------------ USB Port Code ----------------------------------------------------*/ +hcd_port_event_t _usb_port_event_dflt_process(hcd_port_handle_t port_hdl, hcd_port_event_t event) +{ + (void)event; + UVC_CHECK(port_hdl != NULL, "port handle can not be NULL", HCD_PORT_EVENT_NONE); + hcd_port_event_t actual_evt = hcd_port_handle_event(port_hdl); + + switch (actual_evt) { + case HCD_PORT_EVENT_CONNECTION: + //Reset newly connected device + ESP_LOGI(TAG, "line %u HCD_PORT_EVENT_CONNECTION", __LINE__); + break; + + case HCD_PORT_EVENT_DISCONNECTION: + ESP_LOGW(TAG, "line %u HCD_PORT_EVENT_DISCONNECTION", __LINE__); + break; + + case HCD_PORT_EVENT_ERROR: + ESP_LOGW(TAG, "line %u HCD_PORT_EVENT_ERROR", __LINE__); + break; + + case HCD_PORT_EVENT_OVERCURRENT: + ESP_LOGW(TAG, "line %u HCD_PORT_EVENT_OVERCURRENT", __LINE__); + break; + + case HCD_PORT_EVENT_NONE: + ESP_LOGD(TAG, "line %u HCD_PORT_EVENT_NONE", __LINE__); + break; + + default: + ESP_LOGE(TAG, "line %u invalid HCD_PORT_EVENT%d", __LINE__, actual_evt); + break; + } + return actual_evt; +} + +hcd_port_handle_t _usb_port_init(hcd_port_callback_t callback, void *callback_arg) +{ + UVC_CHECK( callback != NULL && callback_arg != NULL, "invalid args", NULL); + esp_err_t ret = ESP_OK; + hcd_port_handle_t port_hdl = NULL; + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) + .otg_io_conf = NULL, +#else + .gpio_conf = NULL, +#endif + }; + usb_phy_handle_t phy_handle = NULL; + ret = usb_new_phy(&phy_config, &phy_handle); + UVC_CHECK(ESP_OK == ret, "USB PHY init failed", NULL); + hcd_config_t hcd_config = { + .intr_flags = ESP_INTR_FLAG_LEVEL2, + }; + ret = hcd_install(&hcd_config); + UVC_CHECK_GOTO(ESP_OK == ret, "HCD Install failed", hcd_init_err); + hcd_port_config_t port_cfg = { + .fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED, + .callback = callback, + .callback_arg = callback_arg, + .context = phy_handle, + }; + ret = hcd_port_init(USB_PORT_NUM, &port_cfg, &port_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "HCD Port init failed", port_init_err); + ret = hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_ON); + UVC_CHECK_GOTO(ESP_OK == ret, "HCD Port Power on failed", port_power_err); + ESP_LOGD(TAG, "Port=%d init succeed, context = %p", USB_PORT_NUM, phy_handle); + return port_hdl; +port_power_err: + hcd_port_deinit(port_hdl); +port_init_err: + hcd_uninstall(); +hcd_init_err: + usb_del_phy(phy_handle); + return NULL; +} + +esp_err_t _usb_port_deinit(hcd_port_handle_t port_hdl) +{ + esp_err_t ret; + ret = hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE); + UVC_CHECK_CONTINUE(ESP_OK == ret, "port disable failed"); + ret = hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF); + UVC_CHECK_CONTINUE(ESP_OK == ret, "Port power off failed"); + usb_phy_handle_t phy_handle = hcd_port_get_context(port_hdl); + ret = usb_del_phy(phy_handle); + UVC_CHECK_CONTINUE(ESP_OK == ret, "phy delete failed"); + ret = hcd_port_deinit(port_hdl); + UVC_CHECK_CONTINUE(ESP_OK == ret, "port deinit failed"); + ret = hcd_uninstall(); + UVC_CHECK_CONTINUE(ESP_OK == ret, "hcd uninstall failed"); + ESP_LOGD(TAG, "Port=%d deinit succeed, context = %p", USB_PORT_NUM, phy_handle); + return ret; +} + +esp_err_t _usb_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *port_speed) +{ + UVC_CHECK(port_hdl != NULL && port_speed != NULL, "invalid args", ESP_ERR_INVALID_ARG); + esp_err_t ret = hcd_port_get_speed(port_hdl, port_speed); + UVC_CHECK(ESP_OK == ret, "port speed get failed", ESP_FAIL); + return ret; +} + +#ifdef RANDOM_ERROR_TEST +#include "esp_random.h" +#endif +/*------------------------------------------------ USB Pipe Code ----------------------------------------------------*/ +IRAM_ATTR hcd_pipe_event_t _pipe_event_dflt_process(hcd_pipe_handle_t pipe_handle, const char *pipe_name, hcd_pipe_event_t pipe_event) +{ + UVC_CHECK(pipe_handle != NULL, "pipe handle can not be NULL", pipe_event); + hcd_pipe_event_t actual_evt = pipe_event; + +#ifdef RANDOM_ERROR_TEST + if (HCD_PIPE_EVENT_URB_DONE == pipe_event) + actual_evt = (esp_random() % 10 > 8) ? HCD_PIPE_EVENT_ERROR_XFER : HCD_PIPE_EVENT_URB_DONE; +#endif + + switch (pipe_event) { + case HCD_PIPE_EVENT_NONE: + break; + case HCD_PIPE_EVENT_URB_DONE: + ESP_LOGV(TAG, "Pipe(%s): XFER_DONE", pipe_name); + break; + + case HCD_PIPE_EVENT_ERROR_XFER: + ESP_LOGW(TAG, "Pipe(%s): ERROR_XFER", pipe_name); + break; + + case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: + ESP_LOGW(TAG, "Pipe(%s): ERROR_URB_NOT_AVAIL", pipe_name); + break; + + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + ESP_LOGW(TAG, "Pipe(%s): ERROR_OVERFLOW", pipe_name); + break; + case HCD_PIPE_EVENT_ERROR_STALL: + ESP_LOGW(TAG, "Pipe(%s): ERROR_STALL", pipe_name); + break; + default: + ESP_LOGW(TAG, "Pipe(%s): invalid EVENT = %d", pipe_name, pipe_event); + break; + } + return actual_evt; +} + +hcd_pipe_handle_t _usb_pipe_init(hcd_port_handle_t port_hdl, usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed, void *context, hcd_pipe_callback_t callback, void *callback_arg) +{ + UVC_CHECK(port_hdl != NULL, "invalid args", NULL); + hcd_pipe_config_t pipe_cfg = { + .callback = callback, + .callback_arg = callback_arg, + .context = context, + .ep_desc = ep_desc, //NULL EP descriptor to create a default pipe + .dev_addr = dev_addr, + .dev_speed = dev_speed, + }; + + esp_err_t ret = ESP_OK; + hcd_pipe_handle_t pipe_handle = NULL; + ret = hcd_pipe_alloc(port_hdl, &pipe_cfg, &pipe_handle); + UVC_CHECK(ESP_OK == ret, "pipe alloc failed", NULL); + ESP_LOGD(TAG, "pipe(%p) init succeed", pipe_handle); + return pipe_handle; +} + +esp_err_t _usb_pipe_flush(hcd_pipe_handle_t pipe_hdl, size_t urb_num) +{ + UVC_CHECK(pipe_hdl != NULL, "invalid args", ESP_ERR_INVALID_ARG); + ESP_LOGD(TAG, "pipe flushing: state = %d", hcd_pipe_get_state(pipe_hdl)); + esp_err_t ret = hcd_pipe_command(pipe_hdl, HCD_PIPE_CMD_HALT); + if (ESP_OK != ret) { + ESP_LOGW(TAG, "pipe=%p halt failed", pipe_hdl); + } + ret = hcd_pipe_command(pipe_hdl, HCD_PIPE_CMD_FLUSH); + if (ESP_OK != ret) { + ESP_LOGW(TAG, "pipe=%p flush failed", pipe_hdl); + } + for (size_t i = 0; i < urb_num; i++) { + urb_t *urb = hcd_urb_dequeue(pipe_hdl); + ESP_LOGV(TAG, "urb dequeue handle = %p", urb); + } + ESP_LOGD(TAG, "urb dequeued num = %d", urb_num); + ESP_LOGD(TAG, "pipe(%p) flush succeed", pipe_hdl); + return ESP_OK; +} + +esp_err_t _usb_pipe_clear(hcd_pipe_handle_t pipe_hdl, size_t urb_num) +{ + UVC_CHECK(pipe_hdl != NULL, "invalid args", ESP_ERR_INVALID_ARG); + ESP_LOGD(TAG, "pipe clearing: state = %d", hcd_pipe_get_state(pipe_hdl)); + for (size_t i = 0; i < urb_num; i++) { + urb_t *urb = hcd_urb_dequeue(pipe_hdl); + ESP_LOGV(TAG, "urb dequeue handle = %p", urb); + } + ESP_LOGD(TAG, "urb dequeued num = %d", urb_num); + if (hcd_pipe_get_state(pipe_hdl) == HCD_PIPE_STATE_ACTIVE) { + return ESP_OK; + } + esp_err_t ret = hcd_pipe_command(pipe_hdl, HCD_PIPE_CMD_CLEAR); + if (ESP_OK != ret) { + ESP_LOGD(TAG, "pipe=%p clear failed", pipe_hdl); + return ESP_FAIL; + } + ESP_LOGD(TAG, "pipe(%p) clear succeed", pipe_hdl); + return ESP_OK; +} + +esp_err_t _usb_pipe_deinit(hcd_pipe_handle_t pipe_hdl, size_t urb_num) +{ + UVC_CHECK(pipe_hdl != NULL, "invalid args", ESP_ERR_INVALID_ARG); + esp_err_t ret = _usb_pipe_flush(pipe_hdl, urb_num); + if (ESP_OK != ret) { + ESP_LOGW(TAG, "pipe=%p flush failed", pipe_hdl); + } + ret = hcd_pipe_free(pipe_hdl); + if (ESP_OK != ret) { + ESP_LOGW(TAG, "pipe=%p free failed", pipe_hdl); + return ret; + } + ESP_LOGD(TAG, "pipe(%p) deinit succeed", pipe_hdl); + return ESP_OK; +} diff --git a/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.h b/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.h new file mode 100644 index 0000000..4c1615f --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_host_helpers.h @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "hcd.h" +#include "usb/usb_types_stack.h" +#include "usb_private.h" + +#define UVC_CHECK(a, str, ret) if(!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } + +#define UVC_CHECK_ABORT(a, str) if(!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + abort(); \ + } + +#define UVC_CHECK_RETURN_VOID(a, str) if(!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return; \ + } + +#define UVC_CHECK_CONTINUE(a, str) if(!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + } + +#define UVC_CHECK_GOTO(a, str, label) if(!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + goto label; \ + } + +/* The usb host helper functions, only for usb_stream driver */ +void _usb_urb_clear(urb_t *urb); +urb_t *_usb_urb_alloc(int num_isoc_packets, size_t packet_data_buffer_size, void *context); +void _usb_urb_free(urb_t *urb); +urb_t **_usb_urb_list_alloc(uint32_t urb_num, uint32_t num_isoc_packets, uint32_t bytes_per_packet); +void _usb_urb_list_clear(urb_t **urb_list, uint32_t urb_num, uint32_t packets_per_urb, uint32_t bytes_per_packet); +esp_err_t _usb_urb_list_enqueue(hcd_pipe_handle_t pipe_handle, urb_t **urb_list, uint32_t urb_num); +void _usb_urb_list_free(urb_t **urb_list, uint32_t urb_num); +hcd_port_event_t _usb_port_event_dflt_process(hcd_port_handle_t port_hdl, hcd_port_event_t event); +hcd_pipe_event_t _pipe_event_dflt_process(hcd_pipe_handle_t pipe_handle, const char *pipe_name, hcd_pipe_event_t pipe_event);; +hcd_port_handle_t _usb_port_init(hcd_port_callback_t callback, void *callback_arg); +esp_err_t _usb_port_deinit(hcd_port_handle_t port_hdl); +esp_err_t _usb_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *port_speed); +hcd_pipe_handle_t _usb_pipe_init(hcd_port_handle_t port_hdl, usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed, void *context, hcd_pipe_callback_t callback, void *callback_arg); +esp_err_t _usb_pipe_flush(hcd_pipe_handle_t pipe_hdl, size_t urb_num); +esp_err_t _usb_pipe_clear(hcd_pipe_handle_t pipe_hdl, size_t urb_num); +esp_err_t _usb_pipe_deinit(hcd_pipe_handle_t pipe_hdl, size_t urb_num); diff --git a/lib/ESP32_USB_STREAM/src/original/usb_private.h b/lib/ESP32_USB_STREAM/src/original/usb_private.h new file mode 100644 index 0000000..ccdcd44 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_private.h @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ Types -------------------------------------------------------- + +typedef struct { + uint8_t *data_buffer; + size_t data_buffer_size; + int num_bytes; + int actual_num_bytes; + uint32_t flags; + usb_device_handle_t device_handle; + uint8_t bEndpointAddress; + usb_transfer_status_t status; + uint32_t timeout; + usb_transfer_cb_t callback; + void *context; + int num_isoc_packets; + usb_isoc_packet_desc_t isoc_packet_desc[0]; +} usb_transfer_dummy_t; +_Static_assert(sizeof(usb_transfer_dummy_t) == sizeof(usb_transfer_t), "usb_transfer_dummy_t does not match usb_transfer_t"); + +struct urb_s { + TAILQ_ENTRY(urb_s) tailq_entry; + // HCD Layer: Handler pointer and variables. Must be initialized to NULL and 0 respectively + void *hcd_ptr; + uint32_t hcd_var; + // Host Lib Layer: + void *usb_host_client; // Currently only used when submitted to shared pipes (i.e., Device default pipes) + size_t usb_host_header_size; // USB Host may need the data buffer to have a transparent header + bool usb_host_inflight; // Debugging variable, used to prevent re-submitting URBs already inflight + // Public transfer structure. Must be last due to variable length array + usb_transfer_t transfer; +}; +typedef struct urb_s urb_t; + +/** + * @brief Processing request source + * + * Enum to indicate which layer of the USB Host stack requires processing. The main handling loop should then call that + * layer's processing function (i.e., xxx_process()). + */ +typedef enum { + USB_PROC_REQ_SOURCE_USBH = 0x01, + USB_PROC_REQ_SOURCE_HUB = 0x02, +} usb_proc_req_source_t; + +/** + * @brief Processing request callback + * + * Callback function provided to each layer of the USB Host stack so that each layer can request calls to their + * processing function. + */ +typedef bool (*usb_proc_req_cb_t)(usb_proc_req_source_t source, bool in_isr, void *context); + +// --------------------------------------------------- Allocation ------------------------------------------------------ + +/** + * @brief Allocate a URB + * + * - Data buffer is allocated in DMA capable memory + * - The constant fields of the URB are also set + * - The data_buffer field of the URB is set to point to start of the allocated data buffer AFTER the header. To access + * the header, users need a negative offset from data_buffer. + * + * @param data_buffer_size Size of the URB's data buffer + * @param header_size Size of header to put in front of URB's data buffer + * @param num_isoc_packets Number of isochronous packet descriptors + * @return urb_t* URB object + */ +urb_t *urb_alloc(size_t data_buffer_size, size_t header_size, int num_isoc_packets); + +/** + * @brief Free a URB + * + * @param urb URB object + */ +void urb_free(urb_t *urb); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/usb_stream.c b/lib/ESP32_USB_STREAM/src/original/usb_stream.c new file mode 100644 index 0000000..f3f0eca --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_stream.c @@ -0,0 +1,4095 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" +#include "freertos/ringbuf.h" +#include "esp_bit_defs.h" +#include "esp_heap_caps.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "hcd.h" +#include "usb/usb_types_stack.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_helpers.h" +#include "esp_private/usb_phy.h" +#include "usb_private.h" +#include "usb_stream_descriptor.h" +#include "usb_host_helpers.h" +#include "include/usb_stream.h" +#include "usb_stream_sysview.h" +#include "original/include/arduino_config.h" + +static const char *TAG = "USB_STREAM"; + +#define USB_CONFIG_NUM 1 //Default configuration number +#define USB_DEVICE_ADDR 1 //Default UVC device address +#define USB_ENUM_SHORT_DESC_REQ_LEN 8 //Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) +#define USB_EP0_FS_DEFAULT_MPS 64 //Default MPS(max payload size) of Endpoint 0 for full speed device +#define USB_EP0_LS_DEFAULT_MPS 8 //Default MPS(max payload size) of Endpoint 0 for low speed device +#define USB_SHORT_DESC_REQ_LEN 8 //Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) +#define USB_EP_ISOC_IN_MAX_MPS 512 //Max MPS ESP32-S2/S3 can handle +#define USB_EP_BULK_FS_MPS 64 //Default MPS of full speed bulk transfer +#define USB_EP_BULK_HS_MPS 512 //Default MPS of high speed bulk transfer +#define USB_EP_DIR_MASK 0x80 //Mask for endpoint direction +#define USB_EVENT_QUEUE_LEN 8 //USB event queue length +#define USB_STREAM_EVENT_QUEUE_LEN 32 //Stream event queue length +#define FRAME_MAX_INTERVAL 2000000 //Specified in 100 ns units, General max frame interval (5 FPS) +#define FRAME_MIN_INTERVAL 166666 //General min frame interval (60 FPS) +#define TIMEOUT_USB_CTRL_XFER_MS CONFIG_USB_CTRL_XFER_TIMEOUT_MS //Timeout for USB control transfer +#define WAITING_CTRL_XFER_MUTEX_MS (TIMEOUT_USB_CTRL_XFER_MS + 100) //Timeout for USER COMMAND control transfer +#define WAITING_TASK_RESOURCE_RELEASE_MS 50 //Additional waiting time for task resource release +#define WAITING_DEVICE_CONTROL_APPLY_MS 50 //Delay to avoid device control too frequently +#define TIMEOUT_USB_STREAM_DEINIT_MS (TIMEOUT_USB_CTRL_XFER_MS + 100) //Timeout for usb stream deinit +#define TIMEOUT_USB_STREAM_DISCONNECT_MS (TIMEOUT_USB_CTRL_XFER_MS + 100) //Timeout for usb stream disconnect +#define TIMEOUT_USER_COMMAND_MS (TIMEOUT_USB_CTRL_XFER_MS + 200) //Timeout for USER COMMAND control transfer +#define ACTIVE_DEBOUNCE_TIME_MS 100 //Debounce time for active state +#define CTRL_TRANSFER_DATA_MAX_BYTES CONFIG_CTRL_TRANSFER_DATA_MAX_BYTES //Max data length assumed in control transfer +#define NUM_BULK_STREAM_URBS CONFIG_NUM_BULK_STREAM_URBS //Number of bulk stream URBS created for continuous enqueue +#define NUM_BULK_BYTES_PER_URB CONFIG_NUM_BULK_BYTES_PER_URB //Required transfer bytes of each URB, check +#define NUM_ISOC_UVC_URBS CONFIG_NUM_ISOC_UVC_URBS //Number of isochronous stream URBS created for continuous enqueue +#define NUM_PACKETS_PER_URB CONFIG_NUM_PACKETS_PER_URB //Number of packets in each isochronous stream URB +#define WAITING_USB_AFTER_CONNECTION_MS CONFIG_USB_WAITING_AFTER_CONN_MS //Waiting n ms for usb device ready after connection +#define NUM_ISOC_SPK_URBS CONFIG_NUM_ISOC_SPK_URBS //Number of isochronous stream URBS created for continuous enqueue +#define NUM_ISOC_MIC_URBS CONFIG_NUM_ISOC_MIC_URBS //Number of isochronous stream URBS created for continuous enqueue +#define UAC_MIC_CB_MIN_MS_DEFAULT CONFIG_UAC_MIC_CB_MIN_MS_DEFAULT //Default min ms for mic callback +#define UAC_SPK_ST_MAX_MS_DEFAULT CONFIG_UAC_SPK_ST_MAX_MS_DEFAULT //Default max ms for speaker stream +#define UAC_MIC_PACKET_COMPENSATION CONFIG_UAC_MIC_PACKET_COMPENSATION //padding data if mic packet loss +#define UAC_SPK_PACKET_COMPENSATION CONFIG_UAC_SPK_PACKET_COMPENSATION //padding zero if speaker buffer empty +#define UAC_SPK_PACKET_COMPENSATION_SIZE_MS CONFIG_UAC_SPK_PACKET_COMPENSATION_SIZE_MS //padding n MS zero if speaker buffer empty +#define UAC_SPK_PACKET_COMPENSATION_TIMEOUT_MS CONFIG_UAC_SPK_PACKET_COMPENSATION_TIMEOUT_MS //padding n MS after wait timeout +#define UAC_SPK_PACKET_COMPENSATION_CONTINUOUS CONFIG_UAC_SPK_PACKET_COMPENSATION_CONTINUOUS //continuous padding zero at timeout interval +#define USB_PRE_ALLOC_CTRL_TRANSFER_URB CONFIG_USB_PRE_ALLOC_CTRL_TRANSFER_URB //Pre-allocate URB for control transfer + +/** + * @brief Task for USB I/O request and control transfer processing, + * can not be blocked, higher task priority is suggested. + * + */ +#define USB_PROC_TASK_NAME "usb_proc" +#define USB_PROC_TASK_PRIORITY (CONFIG_USB_PROC_TASK_PRIORITY+1) +#define USB_PROC_TASK_STACK_SIZE CONFIG_USB_PROC_TASK_STACK_SIZE +#define USB_PROC_TASK_CORE CONFIG_USB_PROC_TASK_CORE + +/** + * @brief Task for video/audio usb stream payload processing, polling and send data from ring buffer + * + */ +#define USB_STREAM_NAME "usb_stream_proc" +#define USB_STREAM_PRIORITY CONFIG_USB_PROC_TASK_PRIORITY +#define USB_STREAM_STACK_SIZE CONFIG_USB_PROC_TASK_STACK_SIZE +#define USB_STREAM_CORE CONFIG_USB_PROC_TASK_CORE + +/** + * @brief Task for uvc sample processing, run user callback when new frame ready. + * + */ +#define SAMPLE_PROC_TASK_NAME "sample_proc" +#define SAMPLE_PROC_TASK_PRIORITY CONFIG_SAMPLE_PROC_TASK_PRIORITY +#define SAMPLE_PROC_TASK_STACK_SIZE CONFIG_SAMPLE_PROC_TASK_STACK_SIZE +#if (CONFIG_SAMPLE_PROC_TASK_CORE == -1) +#define SAMPLE_PROC_TASK_CORE tskNO_AFFINITY +#else +#define SAMPLE_PROC_TASK_CORE CONFIG_SAMPLE_PROC_TASK_CORE +#endif + +/** + * @brief Events bit map + * + */ +#define USB_HOST_INIT_DONE BIT1 //set if uvc stream initted +#define USB_HOST_TASK_KILL_BIT BIT2 //event bit to kill usb task +#define USB_UVC_STREAM_RUNNING BIT3 //set if uvc streaming +#define UAC_SPK_STREAM_RUNNING BIT4 //set if speaker streaming +#define UAC_MIC_STREAM_RUNNING BIT5 //set if mic streaming +#define USB_CTRL_PROC_SUCCEED BIT6 //set if control successfully operate +#define USB_CTRL_PROC_FAILED BIT7 //set if control failed operate +#define UVC_SAMPLE_PROC_STOP_DONE BIT8 //set if sample processing successfully stop +#define USB_STREAM_TASK_KILL_BIT BIT9 //event bit to kill stream task +#define USB_STREAM_TASK_RECOVER_BIT BIT10 //event bit to recover stream task +#define USB_STREAM_TASK_PROC_SUCCEED BIT11 //set if stream task successfully operate +#define USB_STREAM_TASK_PROC_FAILED BIT12 //set if stream task failed operate +#define USB_STREAM_DEVICE_READY_BIT BIT13 //set if uvc device ready + +/** + * @brief Actions bit map + * + */ +#define ACTION_PORT_RECOVER BIT1 //recover port from error state +#define ACTION_PORT_DISABLE BIT2 //disable port if user stop streaming +#define ACTION_DEVICE_CONNECT BIT3 //find the connected device +#define ACTION_DEVICE_DISCONNECT BIT4 //lost the connected device +#define ACTION_DEVICE_ENUM_RECOVER BIT9 //recover enum the connected device +#define ACTION_DEVICE_ENUM BIT10 //enum the connected device +#define ACTION_PIPE_DFLT_RECOVER BIT11 //recover pipe from error state +#define ACTION_PIPE_DFLT_CLEAR BIT12 //Clear pipe from error state +#define ACTION_PIPE_DFLT_DISABLE BIT13 //disable pipe from error state +#define ACTION_PIPE_XFER_DONE BIT14 //handle pipe transfer event +#define ACTION_PIPE_XFER_FAIL BIT15 //handle pipe transfer event + +/********************************************** Helper Functions **********************************************/ +#define USB_CTRL_UVC_COMMIT_REQ(ctrl_req_ptr) ({ \ + (ctrl_req_ptr)->bmRequestType = 0x21; \ + (ctrl_req_ptr)->bRequest = UVC_SET_CUR; \ + (ctrl_req_ptr)->wValue = (UVC_VS_COMMIT_CONTROL << 8); \ + (ctrl_req_ptr)->wIndex = 1; \ + (ctrl_req_ptr)->wLength = 26; \ + }) + +#define USB_CTRL_UVC_PROBE_SET_REQ(ctrl_req_ptr) ({ \ + (ctrl_req_ptr)->bmRequestType = 0x21; \ + (ctrl_req_ptr)->bRequest = UVC_SET_CUR; \ + (ctrl_req_ptr)->wValue = (UVC_VS_PROBE_CONTROL << 8); \ + (ctrl_req_ptr)->wIndex = 1; \ + (ctrl_req_ptr)->wLength = 26; \ + }) + +#define USB_CTRL_UVC_PROBE_GET_REQ(ctrl_req_ptr) ({ \ + (ctrl_req_ptr)->bmRequestType = 0xA1; \ + (ctrl_req_ptr)->bRequest = UVC_GET_CUR; \ + (ctrl_req_ptr)->wValue = (UVC_VS_PROBE_CONTROL << 8); \ + (ctrl_req_ptr)->wIndex = 1; \ + (ctrl_req_ptr)->wLength = 26; \ + }) + +#define USB_CTRL_UVC_PROBE_GET_GENERAL_REQ(ctrl_req_ptr, req) ({ \ + (ctrl_req_ptr)->bmRequestType = 0xA1; \ + (ctrl_req_ptr)->bRequest = req; \ + (ctrl_req_ptr)->wValue = (UVC_VS_PROBE_CONTROL << 8); \ + (ctrl_req_ptr)->wIndex = 1; \ + (ctrl_req_ptr)->wLength = 26; \ + }) + +enum uac_ep_ctrl_cs { + UAC_EP_CONTROL_UNDEFINED = 0x00, + UAC_SAMPLING_FREQ_CONTROL = 0x01, + UAC_PITCH_CONTROL = 0x02, +}; + +enum uac_fu_ctrl_cs { + UAC_FU_CONTROL_UNDEFINED = 0x00, + UAC_FU_MUTE_CONTROL = 0x01, + UAC_FU_VOLUME_CONTROL = 0x02, +}; + +#define UAC_SPK_VOLUME_MAX 0xfff0 +#define UAC_SPK_VOLUME_MIN 0xe3a0 +#define UAC_SPK_VOLUME_STEP ((UAC_SPK_VOLUME_MAX - UAC_SPK_VOLUME_MIN)/100) + +#define USB_CTRL_UAC_SET_EP_FREQ(ctrl_req_ptr, ep_addr) ({ \ + (ctrl_req_ptr)->bmRequestType = 0x22; \ + (ctrl_req_ptr)->bRequest = 1; \ + (ctrl_req_ptr)->wValue = (UAC_SAMPLING_FREQ_CONTROL << 8); \ + (ctrl_req_ptr)->wIndex = (0x00ff & ep_addr); \ + (ctrl_req_ptr)->wLength = 3; \ + }) + +#define USB_CTRL_UAC_SET_FU_VOLUME(ctrl_req_ptr, logic_ch, uint_id, ac_itf) ({ \ + (ctrl_req_ptr)->bmRequestType = 0x21; \ + (ctrl_req_ptr)->bRequest = 1; \ + (ctrl_req_ptr)->wValue = ((UAC_FU_VOLUME_CONTROL << 8) | logic_ch); \ + (ctrl_req_ptr)->wIndex = ((uint_id << 8) | (0x00ff & ac_itf)); \ + (ctrl_req_ptr)->wLength = 2; \ + }) + +#define USB_CTRL_UAC_SET_FU_MUTE(ctrl_req_ptr, logic_ch, uint_id, ac_itf) ({ \ + (ctrl_req_ptr)->bmRequestType = 0x21; \ + (ctrl_req_ptr)->bRequest = 1; \ + (ctrl_req_ptr)->wValue = ((UAC_FU_MUTE_CONTROL << 8) | logic_ch); \ + (ctrl_req_ptr)->wIndex = ((uint_id << 8) | (0x00ff & ac_itf)); \ + (ctrl_req_ptr)->wLength = 1; \ + }) + +/*************************************** UVC Probe Helpers ********************************************/ + +#define DEFAULT_UVC_STREAM_CTRL() {\ + .bmHint = 1,\ + .bFormatIndex = 2,\ + .bFrameIndex = 3,\ + .dwFrameInterval = 666666,\ + .wKeyFrameRate = 0,\ + .wPFrameRate = 0,\ + .wCompQuality = 0,\ + .wCompWindowSize = 0,\ + .wDelay = 0,\ + .dwMaxVideoFrameSize = 35000,\ + .dwMaxPayloadTransferSize = 512,\ +} + +/** + * @brief fill buffer with user ctrl configs + * + * @param buf user buffer + * @param len buffer size + * @param ctrl user ctrl configs + * @return ** void + */ +void _uvc_stream_ctrl_to_buf(uint8_t *buf, size_t len, uvc_stream_ctrl_t *ctrl) +{ + UVC_CHECK_RETURN_VOID(len >= 26, "len must >= 26"); + memset(buf, 0, len); + + /* prepare for a SET transfer */ + SHORT_TO_SW(ctrl->bmHint, buf); + buf[2] = ctrl->bFormatIndex; + buf[3] = ctrl->bFrameIndex; + INT_TO_DW(ctrl->dwFrameInterval, buf + 4); + SHORT_TO_SW(ctrl->wKeyFrameRate, buf + 8); + SHORT_TO_SW(ctrl->wPFrameRate, buf + 10); + SHORT_TO_SW(ctrl->wCompQuality, buf + 12); + SHORT_TO_SW(ctrl->wCompWindowSize, buf + 14); + SHORT_TO_SW(ctrl->wDelay, buf + 16); + INT_TO_DW(ctrl->dwMaxVideoFrameSize, buf + 18); + INT_TO_DW(ctrl->dwMaxPayloadTransferSize, buf + 22); +} + +/** + * @brief parse buffer data to ctrl configs + * + * @param buf buffer for parse + * @param len buffer size + * @param ctrl ctrl struct to save configs + * @return ** void + */ +void _buf_to_uvc_stream_ctrl(uint8_t *buf, size_t len, uvc_stream_ctrl_t *ctrl) +{ + UVC_CHECK_RETURN_VOID(len >= 26, "len must >= 26"); + + /* prepare for a SET transfer */ + ctrl->bmHint = SW_TO_SHORT(buf); + ctrl->bFormatIndex = buf[2]; + ctrl->bFrameIndex = buf[3]; + ctrl->dwFrameInterval = DW_TO_INT(buf + 4); + ctrl->wKeyFrameRate = SW_TO_SHORT(buf + 8); + ctrl->wPFrameRate = SW_TO_SHORT(buf + 10); + ctrl->wCompQuality = SW_TO_SHORT(buf + 12); + ctrl->wCompWindowSize = SW_TO_SHORT(buf + 14); + ctrl->wDelay = SW_TO_SHORT(buf + 16); + ctrl->dwMaxVideoFrameSize = DW_TO_INT(buf + 18); + ctrl->dwMaxPayloadTransferSize = DW_TO_INT(buf + 22); +} + +/** @brief Print the values in a stream control block + * @ingroup diag + * + * @param devh UVC device + * @param stream Output stream (stderr if NULL) + */ +void _uvc_stream_ctrl_printf(FILE *stream, uvc_stream_ctrl_t *ctrl) +{ + if (stream == NULL) { + stream = stderr; + } +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + fprintf(stream, "bmHint: 0x%04x\n", ctrl->bmHint); +#endif + fprintf(stream, "bFormatIndex: %d\n", ctrl->bFormatIndex); + fprintf(stream, "bFrameIndex: %d\n", ctrl->bFrameIndex); + fprintf(stream, "dwFrameInterval: %"PRIu32"\n", ctrl->dwFrameInterval); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + fprintf(stream, "wKeyFrameRate: %d\n", ctrl->wKeyFrameRate); + fprintf(stream, "wPFrameRate: %d\n", ctrl->wPFrameRate); + fprintf(stream, "wCompQuality: %d\n", ctrl->wCompQuality); + fprintf(stream, "wCompWindowSize: %d\n", ctrl->wCompWindowSize); + fprintf(stream, "wDelay: %d\n", ctrl->wDelay); + fprintf(stream, "dwMaxVideoFrameSize: %"PRIu32"\n", ctrl->dwMaxVideoFrameSize); +#endif + fprintf(stream, "dwMaxPayloadTransferSize: %"PRIu32"\n", ctrl->dwMaxPayloadTransferSize); +#ifdef CONFIG_UVC_PRINT_DESC_VERBOSE + fprintf(stream, "dwClockFrequency: %"PRIu32"\n", ctrl->dwClockFrequency); + fprintf(stream, "bmFramingInfo: %u\n", ctrl->bmFramingInfo); + fprintf(stream, "bPreferredVersion: %u\n", ctrl->bPreferredVersion); + fprintf(stream, "bMinVersion: %u\n", ctrl->bMinVersion); + fprintf(stream, "bMaxVersion: %u\n", ctrl->bMaxVersion); +#endif + fprintf(stream, "bInterfaceNumber: %d\n", ctrl->bInterfaceNumber); +} + +/*************************************** Internal Types ********************************************/ +typedef enum { + STATE_NONE, + STATE_DEVICE_INSTALLED, + STATE_DEVICE_RECOVER, + STATE_DEVICE_CONNECTED, + STATE_DEVICE_ENUM, + STATE_DEVICE_ENUM_FAILED, + STATE_DEVICE_ACTIVE, +} _device_state_t; + +typedef enum { + ENUM_STAGE_NONE = 0, /*!< There is no device awaiting enumeration. Start requires device connection and first reset. */ + ENUM_STAGE_START = 1, /*!< A device has connected and has already been reset once. Allocate a device object in USBH */ + //Basic device enumeration + ENUM_STAGE_GET_SHORT_DEV_DESC = 2, /*!< Getting short dev desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ + ENUM_STAGE_CHECK_SHORT_DEV_DESC = 3, /*!< Save bMaxPacketSize0 from the short dev desc. Update the MPS of the enum pipe */ + ENUM_STAGE_SET_ADDR = 4, /*!< Send SET_ADDRESS request */ + ENUM_STAGE_CHECK_ADDR = 5, /*!< Update the enum pipe's target address */ + ENUM_STAGE_GET_FULL_DEV_DESC = 6, /*!< Get the full dev desc */ + ENUM_STAGE_CHECK_FULL_DEV_DESC = 7, /*!< Check the full dev desc, fill it into the device object in USBH. Save the string descriptor indexes*/ + ENUM_STAGE_GET_SHORT_CONFIG_DESC = 8, /*!< Getting a short config desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ + ENUM_STAGE_CHECK_SHORT_CONFIG_DESC = 9, /*!< Save wTotalLength of the short config desc */ + ENUM_STAGE_GET_FULL_CONFIG_DESC = 10, /*!< Get the full config desc (wLength is the saved wTotalLength) */ + ENUM_STAGE_CHECK_FULL_CONFIG_DESC = 11, /*!< Check the full config desc, fill it into the device object in USBH */ + ENUM_STAGE_SET_CONFIG = 12, /*!< Send SET_CONFIGURATION request */ + ENUM_STAGE_CHECK_CONFIG = 13, /*!< Check that SET_CONFIGURATION request was successful */ + ENUM_STAGE_FAILED = 14, /*!< Failed enum */ +} _enum_stage_t; + +const char *const STAGE_STR[] = { + "NONE", + "START", + "GET_SHORT_DEV_DESC", + "CHECK_SHORT_DEV_DESC", + "SET_ADDR", + "CHECK_ADDR", + "GET_FULL_DEV_DESC", + "CHECK_FULL_DEV_DESC", + "GET_SHORT_CONFIG_DESC", + "CHECK_SHORT_CONFIG_DESC", + "GET_FULL_CONFIG_DESC", + "CHECK_FULL_CONFIG_DESC", + "SET_CONFIG", + "CHECK_CONFIG", + "FAILED", +}; + +typedef struct { + // dynamic values should be protect + bool suspended; + uint16_t interface; + uint16_t interface_alt; + uint8_t ep_addr; + // dynamic values, but using in single thread + bool not_found; + uvc_xfer_t xfer_type; + usb_stream_t type; + uint32_t ep_mps; + const char *name; + uint32_t evt_bit; + urb_t **urb_list; + uint32_t urb_num; + uint32_t packets_per_urb; + uint32_t bytes_per_packet; + hcd_pipe_handle_t pipe_handle; +} _stream_ifc_t; + +typedef struct { + /** if true, stream is running (streaming video to host) */ + uvc_device_handle_t devh; + uint8_t running; + /** Current control block */ + struct uvc_stream_ctrl cur_ctrl; + uint8_t fid; + uint8_t reassemble_flag; + uint8_t reassembling; + uint32_t seq, hold_seq; + uint32_t pts, hold_pts; + uint32_t last_scr, hold_last_scr; + size_t got_bytes, hold_bytes; + uint8_t *outbuf, *holdbuf; + uvc_frame_callback_t *user_cb; + void *user_ptr; + SemaphoreHandle_t cb_mutex; + TaskHandle_t taskh; + struct uvc_frame frame; + enum uvc_frame_format frame_format; + struct timespec capture_time_finished; +} _uvc_stream_handle_t; + +typedef struct { + _stream_ifc_t *vs_ifc; + _uvc_stream_handle_t *uvc_stream_hdl; + uint8_t format_index; + // dynamic values should be protect + uvc_frame_size_t *frame_size; + uint8_t frame_num; + uint8_t frame_index; + uint16_t frame_width; + uint16_t frame_height; + uint32_t frame_interval; +} _uvc_device_t; + +typedef enum { + UAC_SPK, + UAC_MIC, + UAC_MAX, +} _uac_internal_stream_t; + +typedef struct { + // dynamic values, but using in single thread + _stream_ifc_t *as_ifc[UAC_MAX]; + RingbufHandle_t ringbuf_hdl[UAC_MAX]; + // dynamic values should be protect + // index 0: speaker, index 1: mic + uint16_t ac_interface; + uint8_t *mic_frame_buf; + uint32_t mic_frame_buf_size; + uint32_t mic_ms_bytes; + uint32_t spk_ms_bytes; + uint32_t spk_max_xfer_size; + uac_frame_size_t *frame_size[UAC_MAX]; + uint8_t frame_num[UAC_MAX]; + uint8_t frame_index[UAC_MAX]; + uint8_t ch_num[UAC_MAX]; + uint16_t bit_resolution[UAC_MAX]; + uint32_t samples_frequence[UAC_MAX]; + bool freq_ctrl_support[UAC_MAX]; + uint8_t fu_id[UAC_MAX]; + uint8_t mute_ch[UAC_MAX]; + uint8_t volume_ch[UAC_MAX]; + bool mute[UAC_MAX]; + uint32_t volume[UAC_MAX]; +} _uac_device_t; + +typedef struct { + int in_mps; + int non_periodic_out_mps; + int periodic_out_mps; +} fifo_mps_limits_t; + +extern const fifo_mps_limits_t mps_limits_default; +extern const fifo_mps_limits_t mps_limits_bias_rx; + +typedef struct { + // const user config values + uac_config_t uac_cfg; + uvc_config_t uvc_cfg; + // const values after usb stream start + bool enabled[STREAM_MAX]; + hcd_port_handle_t port_hdl; + hcd_port_fifo_bias_t fifo_bias; + const fifo_mps_limits_t *mps_limits; + uint16_t configuration; + uint8_t dev_addr; + QueueHandle_t queue_hdl; + QueueHandle_t stream_queue_hdl; + TaskHandle_t stream_task_hdl; + EventGroupHandle_t event_group_hdl; + SemaphoreHandle_t xfer_mutex_hdl; + SemaphoreHandle_t ctrl_smp_hdl; + urb_t *ctrl_urb; + // const values after usb enum + hcd_pipe_handle_t dflt_pipe_hdl; + usb_speed_t dev_speed; + uint8_t ep_mps; + _uac_device_t *uac; + _uvc_device_t *uvc; + _stream_ifc_t *ifc[STREAM_MAX]; + // only operate in single thread + _enum_stage_t enum_stage; + // dynamic values should be protect + _device_state_t state; + state_callback_t *state_cb; + void *state_cb_arg; + uint32_t flags; +} _usb_device_t; + +/** + * @brief Singleton Pattern, Only one device supported + * + */ +static _usb_device_t s_usb_dev = {0}; +static portMUX_TYPE s_uvc_lock = portMUX_INITIALIZER_UNLOCKED; +#define UVC_ENTER_CRITICAL() portENTER_CRITICAL(&s_uvc_lock) +#define UVC_EXIT_CRITICAL() portEXIT_CRITICAL(&s_uvc_lock) + +typedef enum { + USER_EVENT, + PORT_EVENT, + PIPE_EVENT, +} _event_type_t; + +typedef enum { + //accept by usb stream task + STREAM_SUSPEND, + STREAM_RESUME, + //accept by usb task + USB_RECOVER, + PIPE_RECOVER, +} _user_cmd_t; + +typedef struct { + _event_type_t _type; + union { + void *user_hdl; + hcd_port_handle_t port_hdl; + hcd_pipe_handle_t pipe_handle; + } _handle; + union { + _user_cmd_t user_cmd; + hcd_port_event_t port_event; + hcd_pipe_event_t pipe_event; + } _event; + void *_event_data; +} _event_msg_t; + +IRAM_ATTR static _device_state_t _usb_device_get_state() +{ + UVC_ENTER_CRITICAL(); + _device_state_t state = s_usb_dev.state; + UVC_EXIT_CRITICAL(); + return state; +} + +IRAM_ATTR static bool _usb_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) +{ + QueueHandle_t usb_event_queue = (QueueHandle_t)user_arg; + assert(in_isr); //Current HCD implementation should never call a port callback in a task context + _event_msg_t event_msg = { + ._type = PORT_EVENT, + ._handle.port_hdl = port_hdl, + ._event.port_event = port_event, + }; + BaseType_t xTaskWoken = pdFALSE; + xQueueSendFromISR(usb_event_queue, &event_msg, &xTaskWoken); + return (xTaskWoken == pdTRUE); +} + +IRAM_ATTR static bool _usb_pipe_callback(hcd_pipe_handle_t pipe_handle, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +{ + QueueHandle_t usb_event_queue = (QueueHandle_t)user_arg; + _event_msg_t event_msg = { + ._type = PIPE_EVENT, + ._handle.pipe_handle = pipe_handle, + ._event.pipe_event = pipe_event, + }; + + if (in_isr) { + BaseType_t xTaskWoken = pdFALSE; + xQueueSendFromISR(usb_event_queue, &event_msg, &xTaskWoken); + return (xTaskWoken == pdTRUE); + } else { + xQueueSend(usb_event_queue, &event_msg, portMAX_DELAY); + return false; + } +} + +static void _uvc_process_payload(_uvc_stream_handle_t *strmh, size_t req_len, uint8_t *payload, size_t payload_len); + +IRAM_ATTR static void _processing_uvc_pipe(_uvc_stream_handle_t *strmh, hcd_pipe_handle_t pipe_handle, bool if_enqueue) +{ + UVC_CHECK_RETURN_VOID(pipe_handle != NULL, "pipe handle can not be NULL"); + + urb_t *urb_done = hcd_urb_dequeue(pipe_handle); + + if (urb_done == NULL) { + ESP_LOGV(TAG, "uvc pipe dequeue failed"); + return; + } + + if (urb_done->transfer.num_isoc_packets == 0) { // Bulk transfer + if (urb_done->transfer.actual_num_bytes > 0) { + _uvc_process_payload(strmh, urb_done->transfer.num_bytes, urb_done->transfer.data_buffer, urb_done->transfer.actual_num_bytes); + } + } else { // isoc transfer + for (size_t i = 0; i < urb_done->transfer.num_isoc_packets; i++) { + if (urb_done->transfer.isoc_packet_desc[i].status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGV(TAG, "line:%u bad iso transit status %d", __LINE__, urb_done->transfer.isoc_packet_desc[i].status); + continue; + } + + uint8_t *simplebuffer = urb_done->transfer.data_buffer + (i * s_usb_dev.uvc->vs_ifc->ep_mps); + ESP_LOGV(TAG, "process payload=%u, len = %d", i, urb_done->transfer.isoc_packet_desc[i].actual_num_bytes); + _uvc_process_payload(strmh, (urb_done->transfer.isoc_packet_desc[i].num_bytes), simplebuffer, (urb_done->transfer.isoc_packet_desc[i].actual_num_bytes)); + } + } + + if (if_enqueue) { + esp_err_t ret = hcd_urb_enqueue(pipe_handle, urb_done); + if (ret != ESP_OK) { + // We not handle the error because the usb stream task will handle it + ESP_LOGE(TAG, "UVC urb enqueue failed %s", esp_err_to_name(ret)); + } + + } + +} + +/*------------------------------------------------ USB Control Process Code ----------------------------------------------------*/ +static esp_err_t _apply_pipe_config(usb_stream_t stream) +{ + _usb_device_t *usb_dev = &s_usb_dev; + /* If users skiped the get descriptors process for quick start, use user-mandated configs */ + if (stream == STREAM_UVC && usb_dev->enabled[STREAM_UVC] && !usb_dev->ifc[STREAM_UVC]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + usb_dev->ifc[STREAM_UVC]->interface = usb_dev->uvc_cfg.interface; + usb_dev->ifc[STREAM_UVC]->interface_alt = usb_dev->uvc_cfg.interface_alt; + usb_dev->ifc[STREAM_UVC]->ep_addr = usb_dev->uvc_cfg.ep_addr; + usb_dev->ifc[STREAM_UVC]->ep_mps = usb_dev->uvc_cfg.ep_mps; + usb_dev->ifc[STREAM_UVC]->xfer_type = usb_dev->uvc_cfg.xfer_type; +#endif + if (usb_dev->ifc[STREAM_UVC]->ep_mps > USB_EP_ISOC_IN_MAX_MPS) { + usb_dev->ifc[STREAM_UVC]->ep_mps = USB_EP_ISOC_IN_MAX_MPS; + } + } + if (stream == STREAM_UAC_MIC && usb_dev->enabled[STREAM_UAC_MIC] && !usb_dev->ifc[STREAM_UAC_MIC]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + usb_dev->ifc[STREAM_UAC_MIC]->interface = usb_dev->uac_cfg.mic_interface; + usb_dev->ifc[STREAM_UAC_MIC]->interface_alt = 1; + usb_dev->ifc[STREAM_UAC_MIC]->ep_addr = usb_dev->uac_cfg.mic_ep_addr; + usb_dev->ifc[STREAM_UAC_MIC]->ep_mps = usb_dev->uac_cfg.mic_ep_mps; + usb_dev->uac->ac_interface = usb_dev->uac_cfg.ac_interface; + usb_dev->uac->fu_id[UAC_MIC] = usb_dev->uac_cfg.mic_fu_id; + usb_dev->uac->mute_ch[UAC_MIC] = 1 << 0; + usb_dev->uac->volume_ch[UAC_MIC] = 1 << 1; +#endif + usb_dev->ifc[STREAM_UAC_MIC]->xfer_type = UVC_XFER_ISOC; + usb_dev->uac->volume[UAC_MIC] = 80; + usb_dev->uac->mute[UAC_MIC] = 0; + if (usb_dev->ifc[STREAM_UAC_MIC]->ep_mps > usb_dev->mps_limits->in_mps) { + usb_dev->ifc[STREAM_UAC_MIC]->ep_mps = usb_dev->mps_limits->in_mps; + } + } + if (stream == STREAM_UAC_SPK && usb_dev->enabled[STREAM_UAC_SPK] && !usb_dev->ifc[STREAM_UAC_SPK]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + usb_dev->ifc[STREAM_UAC_SPK]->interface = usb_dev->uac_cfg.spk_interface; + usb_dev->ifc[STREAM_UAC_SPK]->interface_alt = 1; + usb_dev->ifc[STREAM_UAC_SPK]->ep_addr = usb_dev->uac_cfg.spk_ep_addr; + usb_dev->ifc[STREAM_UAC_SPK]->ep_mps = usb_dev->uac_cfg.spk_ep_mps; + usb_dev->uac->ac_interface = usb_dev->uac_cfg.ac_interface; + usb_dev->uac->fu_id[UAC_SPK] = usb_dev->uac_cfg.spk_fu_id; + usb_dev->uac->mute_ch[UAC_SPK] = 1 << 0; + usb_dev->uac->volume_ch[UAC_SPK] = 1 << 1; +#endif + usb_dev->ifc[STREAM_UAC_SPK]->xfer_type = UVC_XFER_ISOC; + usb_dev->uac->volume[UAC_SPK] = 80; + usb_dev->uac->mute[UAC_SPK] = 0; + if (usb_dev->ifc[STREAM_UAC_SPK]->ep_mps > usb_dev->mps_limits->periodic_out_mps) { + usb_dev->ifc[STREAM_UAC_SPK]->ep_mps = usb_dev->mps_limits->periodic_out_mps; + } + } + return ESP_OK; +} + +static esp_err_t _apply_stream_config(usb_stream_t stream) +{ + ESP_LOGD(TAG, "apply stream config"); + _usb_device_t *usb_dev = &s_usb_dev; + if (stream == STREAM_UVC && usb_dev->enabled[STREAM_UVC] && !usb_dev->ifc[STREAM_UVC]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + static uvc_frame_size_t frame_size = {0}; + frame_size.width = usb_dev->uvc_cfg.frame_width; + frame_size.height = usb_dev->uvc_cfg.frame_height; + frame_size.interval_max = usb_dev->uvc_cfg.frame_interval; + frame_size.interval_min = usb_dev->uvc_cfg.frame_interval; + frame_size.interval = usb_dev->uvc_cfg.frame_interval; + usb_dev->uvc->format_index = usb_dev->uvc_cfg.format_index; + usb_dev->uvc->frame_index = usb_dev->uvc_cfg.frame_index; + usb_dev->uvc->frame_height = usb_dev->uvc_cfg.frame_height; + usb_dev->uvc->frame_width = usb_dev->uvc_cfg.frame_width; + usb_dev->uvc->frame_num = 1; + usb_dev->uvc->frame_size = &frame_size; + usb_dev->uvc->frame_interval = usb_dev->uvc_cfg.frame_interval; +#else + UVC_CHECK((usb_dev->uvc->frame_index <= usb_dev->uvc->frame_num), "invalid frame index", ESP_ERR_INVALID_STATE); + if (usb_dev->uvc->frame_index == 0) { + ESP_LOGD(TAG, "Expected uvc resolution not found, frame_index=%d", usb_dev->uvc->frame_index); + return ESP_ERR_NOT_FOUND; + } + UVC_CHECK(usb_dev->uvc->format_index != 0, "invalid format index", ESP_ERR_INVALID_STATE); + usb_dev->uvc->frame_width = usb_dev->uvc->frame_size[usb_dev->uvc->frame_index - 1].width; + usb_dev->uvc->frame_height = usb_dev->uvc->frame_size[usb_dev->uvc->frame_index - 1].height; + usb_dev->uvc->frame_interval = usb_dev->uvc->frame_size[usb_dev->uvc->frame_index - 1].interval; +#endif + if (usb_dev->ifc[STREAM_UVC]->xfer_type == UVC_XFER_BULK) { + usb_dev->ifc[STREAM_UVC]->urb_num = NUM_BULK_STREAM_URBS; + usb_dev->ifc[STREAM_UVC]->packets_per_urb = 1; + usb_dev->ifc[STREAM_UVC]->bytes_per_packet = NUM_BULK_BYTES_PER_URB; + } else { + usb_dev->ifc[STREAM_UVC]->urb_num = NUM_ISOC_UVC_URBS; + usb_dev->ifc[STREAM_UVC]->packets_per_urb = NUM_PACKETS_PER_URB; + usb_dev->ifc[STREAM_UVC]->bytes_per_packet = usb_dev->ifc[STREAM_UVC]->ep_mps; + } + ESP_LOGD(TAG, "UVC format_index=%"PRIu8", frame_index=%"PRIu8", frame_width=%"PRIu16", frame_height=%"PRIu16", frame_interval=%"PRIu32, + usb_dev->uvc->format_index, usb_dev->uvc->frame_index, usb_dev->uvc->frame_width, usb_dev->uvc->frame_height, usb_dev->uvc->frame_interval); + } + if (stream == STREAM_UAC_MIC && usb_dev->enabled[STREAM_UAC_MIC] && !usb_dev->ifc[STREAM_UAC_MIC]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + static uac_frame_size_t frame_size = {0}; + frame_size.ch_num = (usb_dev->uac_cfg.mic_ch_num == 0)?1: usb_dev->uac_cfg.mic_ch_num; + frame_size.bit_resolution = usb_dev->uac_cfg.mic_bit_resolution; + frame_size.samples_frequence = usb_dev->uac_cfg.mic_samples_frequence; + usb_dev->uac->frame_index[UAC_MIC] = 1; + usb_dev->uac->frame_num[UAC_MIC] = 1; + usb_dev->uac->frame_size[UAC_MIC] = &frame_size; + usb_dev->uac->ch_num[UAC_MIC] = (usb_dev->uac_cfg.mic_ch_num == 0)?1: usb_dev->uac_cfg.mic_ch_num; + usb_dev->uac->bit_resolution[UAC_MIC] = usb_dev->uac_cfg.mic_bit_resolution; + usb_dev->uac->samples_frequence[UAC_MIC] = usb_dev->uac_cfg.mic_samples_frequence; + usb_dev->uac->freq_ctrl_support[UAC_MIC] = true; +#else + UVC_CHECK((usb_dev->uac->frame_index[UAC_MIC] <= usb_dev->uac->frame_num[UAC_MIC]), "invalid frame index", ESP_ERR_INVALID_STATE); + if (usb_dev->uac->frame_index[UAC_MIC] == 0) { + ESP_LOGD(TAG, "Expected uac channel/frequency/bits not found, frame_index=%d", usb_dev->uac->frame_index[UAC_MIC]); + return ESP_ERR_NOT_FOUND; + } + usb_dev->uac->ch_num[UAC_MIC] = usb_dev->uac->frame_size[UAC_MIC][usb_dev->uac->frame_index[UAC_MIC]-1].ch_num; + usb_dev->uac->bit_resolution[UAC_MIC] = usb_dev->uac->frame_size[UAC_MIC][usb_dev->uac->frame_index[UAC_MIC]-1].bit_resolution; + usb_dev->uac->samples_frequence[UAC_MIC] = usb_dev->uac->frame_size[UAC_MIC][usb_dev->uac->frame_index[UAC_MIC]-1].samples_frequence; +#endif + usb_dev->ifc[STREAM_UAC_MIC]->urb_num = NUM_ISOC_MIC_URBS; + usb_dev->ifc[STREAM_UAC_MIC]->packets_per_urb = UAC_MIC_CB_MIN_MS_DEFAULT; + usb_dev->ifc[STREAM_UAC_MIC]->bytes_per_packet = usb_dev->ifc[STREAM_UAC_MIC]->ep_mps; + usb_dev->uac->mic_ms_bytes = usb_dev->uac->ch_num[UAC_MIC] * usb_dev->uac->samples_frequence[UAC_MIC] / 1000 * usb_dev->uac->bit_resolution[UAC_MIC] / 8; + uint32_t mic_min_bytes = usb_dev->uac->mic_ms_bytes * UAC_MIC_CB_MIN_MS_DEFAULT; + ESP_LOGD(TAG, "min_bytes in mic callback = %"PRIu32, mic_min_bytes); + if (usb_dev->uac_cfg.mic_buf_size && (usb_dev->uac_cfg.mic_buf_size < mic_min_bytes)) { + ESP_LOGE(TAG, "mic_buf_size=%"PRIu32" must >= mic_min_bytes %"PRIu32, usb_dev->uac_cfg.mic_buf_size, mic_min_bytes); + assert(0); + } + usb_dev->uac->mic_frame_buf = heap_caps_realloc(usb_dev->uac->mic_frame_buf, mic_min_bytes, MALLOC_CAP_INTERNAL); + UVC_CHECK(usb_dev->uac->mic_frame_buf, "alloc mic frame buf failed", ESP_ERR_NO_MEM); + usb_dev->uac->mic_frame_buf_size = mic_min_bytes; + ESP_LOGD(TAG, "MIC ch_num=%"PRIu8", bit_resolution=%"PRIu16", samples_frequence=%"PRIu32", bytes_per_packet=%"PRIu32, + usb_dev->uac->ch_num[UAC_MIC], usb_dev->uac->bit_resolution[UAC_MIC], usb_dev->uac->samples_frequence[UAC_MIC], usb_dev->ifc[STREAM_UAC_MIC]->bytes_per_packet); + } + if (stream == STREAM_UAC_SPK && usb_dev->enabled[STREAM_UAC_SPK] && !usb_dev->ifc[STREAM_UAC_SPK]->not_found) { +#ifndef CONFIG_UVC_GET_CONFIG_DESC + static uac_frame_size_t frame_size = {0}; + frame_size.ch_num = (usb_dev->uac_cfg.spk_ch_num == 0)?1: usb_dev->uac_cfg.spk_ch_num; + frame_size.bit_resolution = usb_dev->uac_cfg.spk_bit_resolution; + frame_size.samples_frequence = usb_dev->uac_cfg.spk_samples_frequence; + usb_dev->uac->frame_index[UAC_SPK] = 1; + usb_dev->uac->frame_num[UAC_SPK] = 1; + usb_dev->uac->frame_size[UAC_SPK] = &frame_size; + usb_dev->uac->ch_num[UAC_SPK] = (usb_dev->uac_cfg.spk_ch_num == 0)?1: usb_dev->uac_cfg.spk_ch_num; + usb_dev->uac->bit_resolution[UAC_SPK] = usb_dev->uac_cfg.spk_bit_resolution; + usb_dev->uac->samples_frequence[UAC_SPK] = usb_dev->uac_cfg.spk_samples_frequence; + usb_dev->uac->freq_ctrl_support[UAC_SPK] = true; +#else + UVC_CHECK((usb_dev->uac->frame_index[UAC_SPK] <= usb_dev->uac->frame_num[UAC_SPK]), "invalid frame index", ESP_ERR_INVALID_STATE); + if (usb_dev->uac->frame_index[UAC_SPK] == 0) { + ESP_LOGD(TAG, "Expected uac channel/frequency/bits not found, frame_index=%d", usb_dev->uac->frame_index[UAC_SPK]); + return ESP_ERR_NOT_FOUND; + } + usb_dev->uac->ch_num[UAC_SPK] = usb_dev->uac->frame_size[UAC_SPK][usb_dev->uac->frame_index[UAC_SPK]-1].ch_num; + usb_dev->uac->bit_resolution[UAC_SPK] = usb_dev->uac->frame_size[UAC_SPK][usb_dev->uac->frame_index[UAC_SPK]-1].bit_resolution; + usb_dev->uac->samples_frequence[UAC_SPK] = usb_dev->uac->frame_size[UAC_SPK][usb_dev->uac->frame_index[UAC_SPK]-1].samples_frequence; +#endif + usb_dev->ifc[STREAM_UAC_SPK]->urb_num = NUM_ISOC_SPK_URBS; + usb_dev->ifc[STREAM_UAC_SPK]->packets_per_urb = UAC_SPK_ST_MAX_MS_DEFAULT; + usb_dev->ifc[STREAM_UAC_SPK]->bytes_per_packet = usb_dev->uac->ch_num[UAC_SPK] * usb_dev->uac->samples_frequence[UAC_SPK] / 1000 * usb_dev->uac->bit_resolution[UAC_SPK] / 8; + //TODO: Reset mps size to bytes_per_packet. to save tx fifo space + //usb_dev->ifc[STREAM_UAC_SPK]->ep_mps = usb_dev->ifc[STREAM_UAC_SPK]->bytes_per_packet; + usb_dev->uac->spk_max_xfer_size = usb_dev->ifc[STREAM_UAC_SPK]->packets_per_urb * usb_dev->ifc[STREAM_UAC_SPK]->bytes_per_packet; + ESP_LOGD(TAG, "SPK ch_num=%"PRIu8", bit_resolution=%"PRIu16", samples_frequence=%"PRIu32", bytes_per_packet=%"PRIu32, + usb_dev->uac->ch_num[UAC_SPK], usb_dev->uac->bit_resolution[UAC_SPK], usb_dev->uac->samples_frequence[UAC_SPK], usb_dev->ifc[STREAM_UAC_SPK]->bytes_per_packet); + } + return ESP_OK; +} + +static esp_err_t _update_config_from_descriptor(const usb_config_desc_t *cfg_desc) +{ + if (cfg_desc == NULL) { + return ESP_ERR_INVALID_ARG; + } + _uac_device_t *uac_dev = s_usb_dev.uac; + _uvc_device_t *uvc_dev = s_usb_dev.uvc; + _usb_device_t *usb_dev = &s_usb_dev; + int offset = 0; + bool already_next = false; + uint16_t wTotalLength = cfg_desc->wTotalLength; + /* flags indicate if required format and frame found */ + bool mjpeg_format_found = false; + uint8_t mjpeg_format_idx = 0; + uint8_t mjpeg_frame_num = 0; + bool user_frame_found = false; + uint8_t user_frame_idx = 0; + /* flags indicate if suitable audio stream interface found */ + uint16_t uac_ver_found = 0; + bool uac_format_others_found = 0; + bool ac_intf_found = false; + uint8_t ac_intf_idx = 0; + bool as_mic_intf_found = false; + uint8_t as_mic_feature_unit_idx = 0; + uint8_t as_mic_intf_idx = 0; + uint8_t as_mic_intf_ep_attr = 0; + uint16_t as_mic_intf_ep_mps = 0; + uint8_t as_mic_intf_ep_addr = 0; + uint8_t as_mic_volume_ch = __UINT8_MAX__; + uint8_t as_mic_mute_ch = __UINT8_MAX__; + uint8_t as_mic_next_unit_idx = 0; + uint8_t as_mic_output_terminal = 0; + bool as_spk_intf_found = false; + bool as_spk_freq_found = false; + bool as_mic_freq_found = false; + bool as_spk_bit_res_found = false; + bool as_mic_bit_res_found = false; + bool as_mic_freq_ctrl_found = false; + bool as_spk_freq_ctrl_found = false; + uint8_t as_spk_input_terminal = 0; + uint8_t as_mic_input_terminal = 0; + uint8_t as_spk_feature_unit_idx = 0; + uint8_t as_spk_next_unit_idx = 0; + uint8_t as_spk_volume_ch = __UINT8_MAX__; + uint8_t as_spk_mute_ch = __UINT8_MAX__; + uint8_t as_spk_intf_idx = 0; + uint8_t as_spk_intf_ep_attr = 0; + uint16_t as_spk_intf_ep_mps = 0; + uint8_t as_spk_intf_ep_addr = 0; + bool as_spk_ch_num_found = false; + bool as_mic_ch_num_found = false; + /* flags indicate if suitable video stream interface found */ + bool vs_intf_found = false; + uint8_t vs_intf_idx = 0; + uint8_t vs_intf_alt_idx = 0; + uint8_t vs_intf_ep_attr = 0; + uint16_t vs_intf_ep_mps = 0; + uint8_t vs_intf_ep_addr = 0; + uint8_t vs_intf1_idx = 0; + uint8_t vs_intf1_alt_idx = 0; + uint8_t vs_intf1_ep_attr = 0; + uint16_t vs_intf1_ep_mps = 0; + uint8_t vs_intf1_ep_addr = 0; + /* flags indicate if suitable video stream interface found */ + uint8_t context_class = 0; + uint8_t context_subclass = 0; + uint8_t context_intf = 0; + uint8_t context_intf_alt = 0; + uint8_t context_connected_terminal = 0; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)cfg_desc; + + do { + already_next = false; + switch (next_desc->bDescriptorType) { + case USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION: { + const ifc_assoc_desc_t *assoc_desc = (const ifc_assoc_desc_t *)next_desc; + if (assoc_desc->bFunctionClass == USB_CLASS_VIDEO) { + ESP_LOGD(TAG, "-------------------- Video Descriptor Start ----------------------"); + } else if (assoc_desc->bFunctionClass == USB_CLASS_AUDIO) { + ESP_LOGD(TAG, "-------------------- Audio Descriptor Start ----------------------"); + } else { + print_assoc_desc((const uint8_t *)assoc_desc); + break; + } + print_assoc_desc((const uint8_t *)assoc_desc); + } + break; + case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: + print_cfg_desc((const uint8_t *)next_desc); + break; + case USB_B_DESCRIPTOR_TYPE_INTERFACE: { + const usb_intf_desc_t *_intf_desc = (const usb_intf_desc_t *)next_desc; + context_intf = _intf_desc->bInterfaceNumber; + context_intf_alt = _intf_desc->bAlternateSetting; + context_class = _intf_desc->bInterfaceClass; + context_subclass = _intf_desc->bInterfaceSubClass; + if (context_class == USB_CLASS_VIDEO && context_subclass == VIDEO_SUBCLASS_CONTROL) { + ESP_LOGD(TAG, "Found Video Control interface"); + } + if (context_class == USB_CLASS_VIDEO && context_subclass == VIDEO_SUBCLASS_STREAMING) { + ESP_LOGD(TAG, "Found Video Stream interface, %d-%d", context_intf, context_intf_alt); + } + if (context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_CONTROL) { + ESP_LOGD(TAG, "Found Audio Control interface"); + } + if (context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_STREAMING) { + ESP_LOGD(TAG, "Found Audio Stream interface, %d-%d", context_intf, context_intf_alt); + } + print_intf_desc((const uint8_t *)_intf_desc); + } + break; + case USB_B_DESCRIPTOR_TYPE_ENDPOINT: + print_ep_desc((const uint8_t *)next_desc); + if ( context_class == USB_CLASS_VIDEO && context_subclass == VIDEO_SUBCLASS_STREAMING) { + uint16_t ep_mps = 0; + uint8_t ep_attr = 0; + uint8_t ep_addr = 0; + parse_ep_desc((const uint8_t *)next_desc, &ep_mps, &ep_addr, &ep_attr); + if (ep_mps <= USB_EP_ISOC_IN_MAX_MPS && ep_mps > vs_intf_ep_mps) { + vs_intf_found = true; + vs_intf_ep_mps = ep_mps; + vs_intf_ep_attr = ep_attr; + vs_intf_ep_addr = ep_addr; + vs_intf_idx = context_intf; + vs_intf_alt_idx = context_intf_alt; + } + if (context_intf_alt == 1) { + //workaround for some camera with error desc + vs_intf1_ep_mps = ep_mps; + vs_intf1_ep_attr = ep_attr; + vs_intf1_ep_addr = ep_addr; + vs_intf1_idx = context_intf; + vs_intf1_alt_idx = context_intf_alt; + } + } else if ( context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_STREAMING) { + if (context_intf_alt > 1) { + ESP_LOGD(TAG, "Found Audio Stream interface %d-%d, skip", context_intf, context_intf_alt); + break; + } + uint16_t ep_mps = 0; + uint8_t ep_attr = 0; + uint8_t ep_addr = 0; + parse_ep_desc((const uint8_t *)next_desc, &ep_mps, &ep_addr, &ep_attr); + if (ep_addr & 0x80) { + as_mic_intf_found = true; + as_mic_intf_ep_mps = ep_mps; + as_mic_intf_ep_attr = ep_attr; + as_mic_intf_ep_addr = ep_addr; + as_mic_intf_idx = context_intf; + } else { + as_spk_intf_found = true; + as_spk_intf_ep_mps = ep_mps; + as_spk_intf_ep_attr = ep_attr; + as_spk_intf_ep_addr = ep_addr; + as_spk_intf_idx = context_intf; + } + } + ESP_LOGV(TAG, "descriptor parsed %d/%d, vs interface %d-%d", offset, wTotalLength, context_intf, context_intf_alt); + break; + case CS_INTERFACE_DESC: + if ( context_class == USB_CLASS_VIDEO && context_subclass == VIDEO_SUBCLASS_CONTROL) { + const desc_header_t *header = (const desc_header_t *)next_desc; + switch (header->bDescriptorSubtype) { + default: + ESP_LOGD(TAG, "Found video control entity, skip"); + break; + } + } else if ( context_class == USB_CLASS_VIDEO && context_subclass == VIDEO_SUBCLASS_STREAMING) { + if (context_intf_alt == 0) { + //this is format related desc + uint16_t _frame_width = 0; + uint16_t _frame_heigh = 0; + uint8_t _frame_idx = 0; + const desc_header_t *header = (const desc_header_t *)next_desc; + switch (header->bDescriptorSubtype) { + case VIDEO_CS_ITF_VS_INPUT_HEADER: + print_uvc_header_desc((const uint8_t *)next_desc, VIDEO_SUBCLASS_STREAMING); + break; + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + parse_vs_format_mjpeg_desc((const uint8_t *)next_desc, &mjpeg_format_idx, &mjpeg_frame_num); + if (uvc_dev) { + uvc_frame_size_t *frame_size = uvc_dev->frame_size; + frame_size = (uvc_frame_size_t *)heap_caps_realloc(frame_size, mjpeg_frame_num * sizeof(uvc_frame_size_t), MALLOC_CAP_DEFAULT); + UVC_CHECK(frame_size, "alloc uvc frame size failed", ESP_ERR_NO_MEM); + UVC_ENTER_CRITICAL(); + uvc_dev->frame_num = mjpeg_frame_num; + uvc_dev->frame_size = frame_size; + UVC_EXIT_CRITICAL(); + } + mjpeg_format_found = true; + break; + case VIDEO_CS_ITF_VS_FRAME_MJPEG: { + uint8_t interval_type = 0; + const uint32_t *pp_interval = NULL; + uint32_t dflt_interval = 0; + uint32_t max_interval = 0; + uint32_t min_interval = 0; + uint32_t step_interval = 0; + uint32_t final_interval = 0; + parse_vs_frame_mjpeg_desc((const uint8_t *)next_desc, &_frame_idx, &_frame_width, &_frame_heigh, &interval_type, &pp_interval, &dflt_interval); + if (interval_type) { + min_interval = pp_interval[0]; + for (size_t i = 0; i uvc_cfg.frame_interval == pp_interval[i]) { + final_interval = pp_interval[i]; + } + if (pp_interval[i] > max_interval) { + max_interval = pp_interval[i]; + } + if (pp_interval[i] < min_interval) { + min_interval = pp_interval[i]; + } + } + } else { + min_interval = pp_interval[0]; + max_interval = pp_interval[1]; + step_interval = pp_interval[2]; + if (usb_dev->uvc_cfg.frame_interval >= min_interval && usb_dev->uvc_cfg.frame_interval <= max_interval) { + for (uint32_t i = min_interval; i < max_interval; i+=step_interval) { + if (usb_dev->uvc_cfg.frame_interval >= i && usb_dev->uvc_cfg.frame_interval < (i + step_interval)) { + final_interval = i; + } + } + } + } + if (final_interval == 0) { + final_interval = dflt_interval; + ESP_LOGD(TAG, "UVC frame interval %" PRIu32 " not found, using default = %" PRIu32, usb_dev->uvc_cfg.frame_interval, final_interval); + } else { + ESP_LOGD(TAG, "UVC frame interval %" PRIu32 " found = %" PRIu32, usb_dev->uvc_cfg.frame_interval, final_interval); + } + if (uvc_dev) { + assert((_frame_idx - 1) < uvc_dev->frame_num); //should not happen + UVC_ENTER_CRITICAL(); + uvc_dev->frame_size[_frame_idx - 1].width = _frame_width; + uvc_dev->frame_size[_frame_idx - 1].height = _frame_heigh; + uvc_dev->frame_size[_frame_idx - 1].interval = final_interval; + uvc_dev->frame_size[_frame_idx - 1].interval_min = min_interval; + uvc_dev->frame_size[_frame_idx - 1].interval_max = max_interval; + uvc_dev->frame_size[_frame_idx - 1].interval_step = step_interval; + UVC_EXIT_CRITICAL(); + } + if (user_frame_found == true) { + break; + } + if (((_frame_width == usb_dev->uvc_cfg.frame_width) || (FRAME_RESOLUTION_ANY == usb_dev->uvc_cfg.frame_width)) + && ((_frame_heigh == usb_dev->uvc_cfg.frame_height) || (FRAME_RESOLUTION_ANY == usb_dev->uvc_cfg.frame_height))) { + user_frame_found = true; + user_frame_idx = _frame_idx; + } else if ((_frame_width == usb_dev->uvc_cfg.frame_height) && (_frame_heigh == usb_dev->uvc_cfg.frame_width)) { + ESP_LOGW(TAG, "found width*heigh %u * %u , orientation swap?", _frame_heigh, _frame_width); + } + break; + } + default: + break; + } + } + } else if ( context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_CONTROL) { + const desc_header_t *header = (const desc_header_t *)next_desc; + switch (header->bDescriptorSubtype) { + case AUDIO_CS_AC_INTERFACE_HEADER: + parse_ac_header_desc((const uint8_t *)next_desc, &uac_ver_found, NULL); + if (uac_ver_found != UAC_VERSION_1) { + ESP_LOGW(TAG, "UAC version 0x%04x Not supported", uac_ver_found); + } + break; + case AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL: { + uint8_t terminal_idx = 0; + uint16_t terminal_type = 0; + parse_ac_input_desc((const uint8_t *)next_desc, &terminal_idx, &terminal_type); + if (terminal_type == AUDIO_TERM_TYPE_USB_STREAMING) { + as_spk_input_terminal = terminal_idx; + as_spk_next_unit_idx = terminal_idx; + } else if (terminal_type == AUDIO_TERM_TYPE_IN_GENERIC_MIC || terminal_type == AUDIO_TERM_TYPE_HEADSET) { + as_mic_input_terminal = terminal_idx; + as_mic_next_unit_idx = terminal_idx; + } else { + ESP_LOGW(TAG, "Input terminal type 0x%04x Not supported", terminal_type); + } + } + break; + case AUDIO_CS_AC_INTERFACE_FEATURE_UNIT: { + ac_intf_found = true; + ac_intf_idx = context_intf; + uint8_t source_idx = 0; + uint8_t volume_ch = 0; + uint8_t mute_ch = 0; + uint8_t feature_unit_idx = 0; + // Note: we prefer using master channel for feature control + parse_ac_feature_desc((const uint8_t *)next_desc, &source_idx, &feature_unit_idx, &volume_ch, &mute_ch); + // We just assume the next unit of input terminal is feature unit + if (source_idx == as_spk_next_unit_idx || source_idx == as_spk_input_terminal) { + as_spk_next_unit_idx = feature_unit_idx; + as_spk_feature_unit_idx = feature_unit_idx; + as_spk_mute_ch = mute_ch; + as_spk_volume_ch = volume_ch; + } else if (source_idx == as_mic_next_unit_idx || source_idx == as_mic_input_terminal) { + as_mic_next_unit_idx = feature_unit_idx; + as_mic_feature_unit_idx = feature_unit_idx; + as_mic_mute_ch = mute_ch; + as_mic_volume_ch = volume_ch; + } + } + break; + case AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL: { + uint8_t terminal_idx = 0; + uint16_t terminal_type = 0; + parse_ac_output_desc((const uint8_t *)next_desc, &terminal_idx, &terminal_type); + if (terminal_type == AUDIO_TERM_TYPE_USB_STREAMING) { + as_mic_output_terminal = terminal_idx; + as_spk_next_unit_idx = terminal_idx; + } else if (terminal_type == AUDIO_TERM_TYPE_OUT_GENERIC_SPEAKER || terminal_type == AUDIO_TERM_TYPE_HEADSET) { + as_mic_next_unit_idx = terminal_idx; + } else { + ESP_LOGW(TAG, "Output terminal type 0x%04x Not supported", terminal_type); + } + } + break; + default: + break; + } + } else if ( context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_STREAMING) { + const desc_header_t *header = (const desc_header_t *)next_desc; + if (context_intf_alt > 1) { + ESP_LOGD(TAG, "Found audio interface sub-desc:%d of %d-%d, skip", header->bDescriptorSubtype, context_intf, context_intf_alt); + break; + } + switch (header->bDescriptorSubtype) { + case AUDIO_CS_AS_INTERFACE_AS_GENERAL: { + uint16_t format_tag = 0; + uint8_t source_idx = 0; + parse_as_general_desc((const uint8_t *)next_desc, &source_idx, &format_tag); + context_connected_terminal = source_idx; + //we only support TYPEI + if (format_tag != UAC_FORMAT_TYPEI) { + uac_format_others_found = true; + ESP_LOGW(TAG, "Audio format type 0x%04x Not supported", format_tag); + } + } + break; + case AUDIO_CS_AS_INTERFACE_FORMAT_TYPE: { + uint8_t channel_num = 0; + uint8_t bit_resolution = 0; + uint8_t freq_type = 0; + const uint8_t *p_samfreq = NULL; + parse_as_type_desc((const uint8_t *)next_desc, &channel_num, &bit_resolution, &freq_type, &p_samfreq); + if (context_connected_terminal == as_mic_output_terminal) { + if (usb_dev->enabled[STREAM_UAC_MIC]) { + uint8_t frame_num = freq_type == 0 ? 1 : freq_type; + uac_frame_size_t *frame_size = uac_dev->frame_size[UAC_MIC]; + frame_size = (uac_frame_size_t *)heap_caps_realloc(frame_size, frame_num * sizeof(uac_frame_size_t), MALLOC_CAP_DEFAULT); + UVC_CHECK(frame_size, "alloc mic frame size failed", ESP_ERR_NO_MEM); + UVC_ENTER_CRITICAL(); + uac_dev->frame_num[UAC_MIC] = frame_num; + uac_dev->frame_size[UAC_MIC] = frame_size; + UVC_EXIT_CRITICAL(); + } + if (channel_num == usb_dev->uac_cfg.mic_ch_num || usb_dev->uac_cfg.mic_ch_num == UAC_CH_ANY) { + as_mic_ch_num_found = true; + } + + if (bit_resolution == usb_dev->uac_cfg.mic_bit_resolution || usb_dev->uac_cfg.mic_bit_resolution == UAC_BITS_ANY) { + as_mic_bit_res_found = true; + } + if (freq_type == 0) { + uint32_t min_samfreq = (p_samfreq[2] << 16) + (p_samfreq[1] << 8) + p_samfreq[0]; + uint32_t max_samfreq = (p_samfreq[5] << 16) + (p_samfreq[4] << 8) + p_samfreq[3]; + uint32_t mic_frame_frequence = 0; + uint8_t mic_frame_index = 0; + if (usb_dev->uac_cfg.mic_samples_frequence <= max_samfreq && usb_dev->uac_cfg.mic_samples_frequence >= min_samfreq) { + as_mic_freq_found = true; + mic_frame_index = 1; + mic_frame_frequence = usb_dev->uac_cfg.mic_samples_frequence; + } else if (usb_dev->uac_cfg.mic_samples_frequence == UAC_FREQUENCY_ANY) { + as_mic_freq_found = true; + mic_frame_index = 1; + mic_frame_frequence = min_samfreq; + } + if (usb_dev->enabled[STREAM_UAC_MIC]) { + UVC_ENTER_CRITICAL(); + uac_dev->frame_size[UAC_MIC][0].ch_num = channel_num; + uac_dev->frame_size[UAC_MIC][0].bit_resolution = bit_resolution; + uac_dev->frame_size[UAC_MIC][0].samples_frequence = mic_frame_frequence; + uac_dev->frame_size[UAC_MIC][0].samples_frequence_min = min_samfreq; + uac_dev->frame_size[UAC_MIC][0].samples_frequence_max = max_samfreq; + uac_dev->frame_index[UAC_MIC] = mic_frame_index; + UVC_EXIT_CRITICAL(); + } + } else { + uint8_t mic_frame_index = 0; + for (int i = 0; i < freq_type; ++i) { + if (usb_dev->uac_cfg.mic_samples_frequence == UAC_FREQUENCY_ANY) { + as_mic_freq_found = true; + mic_frame_index = 1; + }else if (((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i]) == usb_dev->uac_cfg.mic_samples_frequence) { + as_mic_freq_found = true; + mic_frame_index = i+1; + } + if (usb_dev->enabled[STREAM_UAC_MIC]) { + UVC_ENTER_CRITICAL(); + uac_dev->frame_size[UAC_MIC][i].ch_num = channel_num; + uac_dev->frame_size[UAC_MIC][i].bit_resolution = bit_resolution; + uac_dev->frame_size[UAC_MIC][i].samples_frequence = ((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i]); + uac_dev->frame_size[UAC_MIC][i].samples_frequence_min = 0; + uac_dev->frame_size[UAC_MIC][i].samples_frequence_max = 0; + uac_dev->frame_index[UAC_MIC] = mic_frame_index; + UVC_EXIT_CRITICAL(); + } + } + } + + } else if (context_connected_terminal == as_spk_input_terminal) { + if (usb_dev->enabled[STREAM_UAC_SPK]) { + uint8_t frame_num = freq_type == 0 ? 1 : freq_type; + uac_frame_size_t *frame_size = uac_dev->frame_size[UAC_SPK]; + frame_size = (uac_frame_size_t *)heap_caps_realloc(frame_size, frame_num * sizeof(uac_frame_size_t), MALLOC_CAP_DEFAULT); + UVC_CHECK(frame_size, "alloc spk frame size failed", ESP_ERR_NO_MEM); + UVC_ENTER_CRITICAL(); + uac_dev->frame_num[UAC_SPK] = frame_num; + uac_dev->frame_size[UAC_SPK] = frame_size; + UVC_EXIT_CRITICAL(); + } + if (channel_num == usb_dev->uac_cfg.spk_ch_num || usb_dev->uac_cfg.spk_ch_num == UAC_CH_ANY) { + as_spk_ch_num_found = true; + } + if (bit_resolution == usb_dev->uac_cfg.spk_bit_resolution || usb_dev->uac_cfg.spk_bit_resolution == UAC_BITS_ANY) { + as_spk_bit_res_found = true; + } + if (freq_type == 0) { + uint32_t min_samfreq = (p_samfreq[2] << 16) + (p_samfreq[1] << 8) + p_samfreq[0]; + uint32_t max_samfreq = (p_samfreq[5] << 16) + (p_samfreq[4] << 8) + p_samfreq[3]; + uint32_t spk_frame_frequence = 0; + uint8_t spk_frame_index = 0; + if (usb_dev->uac_cfg.spk_samples_frequence <= max_samfreq && usb_dev->uac_cfg.spk_samples_frequence >= min_samfreq) { + as_spk_freq_found = true; + spk_frame_index = 1; + spk_frame_frequence = usb_dev->uac_cfg.spk_samples_frequence; + } else if (usb_dev->uac_cfg.spk_samples_frequence == UAC_FREQUENCY_ANY) { + as_spk_freq_found = true; + spk_frame_index = 1; + spk_frame_frequence = min_samfreq; + } + if (usb_dev->enabled[STREAM_UAC_SPK]) { + UVC_ENTER_CRITICAL(); + uac_dev->frame_size[UAC_SPK][0].ch_num = channel_num; + uac_dev->frame_size[UAC_SPK][0].bit_resolution = bit_resolution; + uac_dev->frame_size[UAC_SPK][0].samples_frequence = spk_frame_frequence; + uac_dev->frame_size[UAC_SPK][0].samples_frequence_min = min_samfreq; + uac_dev->frame_size[UAC_SPK][0].samples_frequence_max = max_samfreq; + uac_dev->frame_index[UAC_SPK] = spk_frame_index; + UVC_EXIT_CRITICAL(); + } + } else { + uint8_t spk_frame_index = 0; + for (int i = 0; i < freq_type; ++i) { + if (usb_dev->uac_cfg.spk_samples_frequence == UAC_FREQUENCY_ANY) { + as_spk_freq_found = true; + spk_frame_index = 1; + } else if (((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i]) == usb_dev->uac_cfg.spk_samples_frequence) { + as_spk_freq_found = true; + spk_frame_index = i+1; + } + if (usb_dev->enabled[STREAM_UAC_SPK]) { + UVC_ENTER_CRITICAL(); + uac_dev->frame_size[UAC_SPK][i].ch_num = channel_num; + uac_dev->frame_size[UAC_SPK][i].bit_resolution = bit_resolution; + uac_dev->frame_size[UAC_SPK][i].samples_frequence = ((p_samfreq[3 * i + 2] << 16) + (p_samfreq[3 * i + 1] << 8) + p_samfreq[3 * i]); + uac_dev->frame_size[UAC_SPK][i].samples_frequence_min = 0; + uac_dev->frame_size[UAC_SPK][i].samples_frequence_max = 0; + uac_dev->frame_index[UAC_SPK] = spk_frame_index; + UVC_EXIT_CRITICAL(); + } + } + } + } + } + break; + default: + break; + } + } + ESP_LOGV(TAG, "descriptor parsed %d/%d, vs interface %d-%d", offset, wTotalLength, context_intf, context_intf_alt); + break; + case CS_ENDPOINT_DESC: + if ( context_class == USB_CLASS_AUDIO && context_subclass == AUDIO_SUBCLASS_STREAMING) { + if (context_intf_alt > 1) { + ESP_LOGD(TAG, "Found audio endpoint desc of interface %d-%d, skip", context_intf, context_intf_alt); + break; + } + as_cs_ep_desc_t *desc = (as_cs_ep_desc_t *)next_desc; + if (context_connected_terminal == as_spk_input_terminal) { + as_spk_freq_ctrl_found = AUDIO_EP_CONTROL_SAMPLING_FEQ & desc->bmAttributes; + } else if (context_connected_terminal == as_mic_output_terminal) { + as_mic_freq_ctrl_found = AUDIO_EP_CONTROL_SAMPLING_FEQ & desc->bmAttributes; + } + } + break; + default: + break; + } + if (!already_next && next_desc) { + next_desc = usb_parse_next_descriptor(next_desc, wTotalLength, &offset); + } + ESP_LOGV(TAG, "descriptor parsed %d/%d", offset, wTotalLength); + } while (next_desc != NULL); + + // check all params we get + if (usb_dev->enabled[STREAM_UVC]) { + if (vs_intf_found) { + //Re-config uvc device + UVC_ENTER_CRITICAL(); + uvc_dev->vs_ifc->interface = vs_intf_idx; + uvc_dev->vs_ifc->interface_alt = vs_intf_alt_idx; + uvc_dev->vs_ifc->ep_addr = vs_intf_ep_addr; + UVC_EXIT_CRITICAL(); + uvc_dev->vs_ifc->ep_mps = vs_intf_ep_mps; + uvc_dev->vs_ifc->xfer_type = (vs_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_ISOC ? UVC_XFER_ISOC + : ((vs_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_BULK ? UVC_XFER_BULK : UVC_XFER_UNKNOWN); + ESP_LOGI(TAG, "Actual VS Interface(MPS <= %d) found, interface = %u, alt = %u", USB_EP_ISOC_IN_MAX_MPS, vs_intf_idx, vs_intf_alt_idx); + ESP_LOGI(TAG, "\tEndpoint(%s) Addr = 0x%x, MPS = %u", uvc_dev->vs_ifc->xfer_type == UVC_XFER_ISOC ? "ISOC" + : (uvc_dev->vs_ifc->xfer_type == UVC_XFER_BULK ? "BULK" : "Unknown"), vs_intf_ep_addr, vs_intf_ep_mps); + } else if (usb_dev->uvc_cfg.interface) { + //Try with user's config + ESP_LOGW(TAG, "VS Interface(MPS <= %d) NOT found", USB_EP_ISOC_IN_MAX_MPS); + ESP_LOGW(TAG, "Try with user's config"); + UVC_ENTER_CRITICAL(); + uvc_dev->vs_ifc->interface = usb_dev->uvc_cfg.interface; + uvc_dev->vs_ifc->interface_alt = usb_dev->uvc_cfg.interface_alt; + uvc_dev->vs_ifc->ep_addr = usb_dev->uvc_cfg.ep_addr; + UVC_EXIT_CRITICAL(); + uvc_dev->vs_ifc->ep_mps = usb_dev->uvc_cfg.ep_mps; + uvc_dev->vs_ifc->xfer_type = usb_dev->uvc_cfg.xfer_type; + vs_intf_found = true; + } else { + //Try with first interface + UVC_ENTER_CRITICAL(); + uvc_dev->vs_ifc->interface = vs_intf1_idx; + uvc_dev->vs_ifc->interface_alt = vs_intf1_alt_idx; + uvc_dev->vs_ifc->ep_addr = vs_intf1_ep_addr; + UVC_EXIT_CRITICAL(); + uvc_dev->vs_ifc->ep_mps = vs_intf1_ep_mps; + uvc_dev->vs_ifc->xfer_type = (vs_intf1_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_ISOC ? UVC_XFER_ISOC + : ((vs_intf1_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_BULK ? UVC_XFER_BULK : UVC_XFER_UNKNOWN); + vs_intf_found = true; + ESP_LOGW(TAG, "VS Interface(MPS <= %d) NOT found", USB_EP_ISOC_IN_MAX_MPS); + ESP_LOGW(TAG, "Try with first alt-interface config"); + } + if (mjpeg_format_found) { + ESP_LOGI(TAG, "Actual MJPEG format index = %u, contains %u frames", mjpeg_format_idx, mjpeg_frame_num); + uvc_dev->format_index = mjpeg_format_idx; + } else if (usb_dev->uvc_cfg.format_index) { + ESP_LOGW(TAG, "MJPEG format NOT found"); + ESP_LOGW(TAG, "Try with user's config"); + uvc_dev->format_index = usb_dev->uvc_cfg.format_index; + } else { + ESP_LOGE(TAG, "MJPEG format NOT found"); + // We treat MJPEG format as mandatory + vs_intf_found = false; + } + if (user_frame_found) { + UVC_ENTER_CRITICAL(); + uvc_dev->frame_index = user_frame_idx; + UVC_EXIT_CRITICAL(); + ESP_LOGI(TAG, "Actual MJPEG width*heigh: %u*%u, frame index = %u", usb_dev->uvc_cfg.frame_width, usb_dev->uvc_cfg.frame_height, user_frame_idx); + } else if (usb_dev->uvc_cfg.frame_index) { + ESP_LOGW(TAG, "MJPEG width*heigh: %u*%u, NOT found", usb_dev->uvc_cfg.frame_width, usb_dev->uvc_cfg.frame_height); + ESP_LOGW(TAG, "Try with user's config"); + UVC_ENTER_CRITICAL(); + uvc_dev->frame_index = usb_dev->uvc_cfg.frame_index; + uvc_dev->frame_height = usb_dev->uvc_cfg.frame_height; + uvc_dev->frame_width = usb_dev->uvc_cfg.frame_width; + UVC_EXIT_CRITICAL(); + } else { + // No suitable frame found, we need suspend UVC interface during start + ESP_LOGW(TAG, "MJPEG width*heigh: %u*%u, NOT found", usb_dev->uvc_cfg.frame_width, usb_dev->uvc_cfg.frame_height); + vs_intf_found = false; + } + } + + if (usb_dev->enabled[STREAM_UAC_MIC] || usb_dev->enabled[STREAM_UAC_SPK]) { + if (ac_intf_found) { + ESP_LOGI(TAG, "Audio control interface = %d", ac_intf_idx); + if (as_spk_feature_unit_idx) { + ESP_LOGI(TAG, "Speaker feature unit = %d", as_spk_feature_unit_idx); + } else if (usb_dev->uac_cfg.spk_fu_id) { + as_spk_feature_unit_idx = usb_dev->uac_cfg.spk_fu_id; + ESP_LOGW(TAG, "Speaker feature unit NOT found, try with user's config"); + } + if (as_spk_volume_ch != __UINT8_MAX__) { + ESP_LOGI(TAG, "\tSupport volume control, ch = %d", as_spk_volume_ch); + } else { + as_spk_volume_ch = 0; + } + if (as_spk_mute_ch != __UINT8_MAX__) { + ESP_LOGI(TAG, "\tSupport mute control, ch = %d", as_spk_mute_ch); + } else { + as_spk_mute_ch = 0; + } + if (as_mic_feature_unit_idx) { + ESP_LOGI(TAG, "Mic feature unit = %d", as_mic_feature_unit_idx); + } else if (usb_dev->uac_cfg.mic_fu_id) { + as_mic_feature_unit_idx = usb_dev->uac_cfg.mic_fu_id; + ESP_LOGW(TAG, "Mic feature unit NOT found, try with user's config"); + } + if (as_mic_volume_ch != __UINT8_MAX__) { + ESP_LOGI(TAG, "\tSupport volume control, ch = %d", as_mic_volume_ch); + } else { + as_mic_volume_ch = 0; + } + if (as_mic_mute_ch != __UINT8_MAX__) { + ESP_LOGI(TAG, "\tSupport mute control, ch = %d", as_mic_mute_ch); + } else { + as_mic_mute_ch = 0; + } + } else if (usb_dev->uac_cfg.mic_fu_id || usb_dev->uac_cfg.spk_fu_id) { + ac_intf_idx = usb_dev->uac_cfg.ac_interface; + as_mic_feature_unit_idx = usb_dev->uac_cfg.mic_fu_id; + as_spk_feature_unit_idx = usb_dev->uac_cfg.spk_fu_id; + as_spk_volume_ch = 0; + as_spk_mute_ch = 0; + as_mic_volume_ch = 0; + as_mic_mute_ch = 0; + ESP_LOGW(TAG, "Audio control interface NOT found"); + ESP_LOGW(TAG, "Try with user's config"); + } else { + ESP_LOGW(TAG, "Audio control interface NOT found"); + } + UVC_ENTER_CRITICAL(); + uac_dev->ac_interface = ac_intf_idx; + uac_dev->fu_id[UAC_SPK] = as_spk_feature_unit_idx; + uac_dev->volume_ch[UAC_SPK] = as_spk_volume_ch; + uac_dev->mute_ch[UAC_SPK] = as_spk_mute_ch; + uac_dev->fu_id[UAC_MIC] = as_mic_feature_unit_idx; + uac_dev->volume_ch[UAC_MIC] = as_mic_volume_ch; + uac_dev->mute_ch[UAC_MIC] = as_mic_mute_ch; + UVC_EXIT_CRITICAL(); + } + + if (usb_dev->enabled[STREAM_UAC_SPK] && as_spk_intf_found && uac_ver_found == UAC_VERSION_1 && !uac_format_others_found) { + UVC_ENTER_CRITICAL(); + uac_dev->as_ifc[UAC_SPK]->interface = as_spk_intf_idx; + uac_dev->as_ifc[UAC_SPK]->interface_alt = 1; + uac_dev->as_ifc[UAC_SPK]->ep_addr = as_spk_intf_ep_addr; + UVC_EXIT_CRITICAL(); + uac_dev->as_ifc[UAC_SPK]->ep_mps = as_spk_intf_ep_mps; + ESP_LOGI(TAG, "Speaker Interface found, interface = %u", as_spk_intf_idx); + ESP_LOGI(TAG, "\tEndpoint(%s) Addr = 0x%x, MPS = %u", (as_spk_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_ISOC ? "ISOC" + : ((as_spk_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_BULK ? "BULK" : "Unknown"), as_spk_intf_ep_addr, as_spk_intf_ep_mps); + if (!as_spk_ch_num_found) { + ESP_LOGW(TAG, "\tSpeaker channel num %d Not supported", usb_dev->uac_cfg.spk_ch_num); + } + if (!as_spk_bit_res_found) { + ESP_LOGW(TAG, "\tSpeaker bit resolution %d Not supported", usb_dev->uac_cfg.spk_bit_resolution); + } + if (!as_spk_freq_found) { + ESP_LOGW(TAG, "\tSpeaker frequency %"PRIu32" Not supported", usb_dev->uac_cfg.spk_samples_frequence); + } else { + uac_dev->freq_ctrl_support[UAC_SPK] = as_spk_freq_ctrl_found; + ESP_LOGI(TAG, "\tSpeaker frequency control %s Support", as_spk_freq_ctrl_found ? "" : "Not"); + } + } else if (usb_dev->enabled[STREAM_UAC_SPK] && usb_dev->uac_cfg.spk_interface) { + UVC_ENTER_CRITICAL(); + uac_dev->as_ifc[UAC_SPK]->interface = usb_dev->uac_cfg.spk_interface; + uac_dev->as_ifc[UAC_SPK]->interface_alt = 1; + uac_dev->as_ifc[UAC_SPK]->ep_addr = usb_dev->uac_cfg.spk_ep_addr; + UVC_EXIT_CRITICAL(); + uac_dev->as_ifc[UAC_SPK]->ep_mps = usb_dev->uac_cfg.spk_ep_mps; + ESP_LOGW(TAG, "Speaker Interface NOT found"); + ESP_LOGW(TAG, "Try with user's config"); + as_spk_intf_found = true; + } else { + as_spk_intf_found = false; + } + + if (usb_dev->enabled[STREAM_UAC_MIC] && as_mic_intf_found && uac_ver_found == UAC_VERSION_1 && !uac_format_others_found) { + UVC_ENTER_CRITICAL(); + uac_dev->as_ifc[UAC_MIC]->interface = as_mic_intf_idx; + uac_dev->as_ifc[UAC_MIC]->interface_alt = 1; + uac_dev->as_ifc[UAC_MIC]->ep_addr = as_mic_intf_ep_addr; + UVC_EXIT_CRITICAL(); + uac_dev->as_ifc[UAC_MIC]->ep_mps = as_mic_intf_ep_mps; + ESP_LOGI(TAG, "Mic Interface found interface = %u", as_mic_intf_idx); + ESP_LOGI(TAG, "\tEndpoint(%s) Addr = 0x%x, MPS = %u", (as_mic_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_ISOC ? "ISOC" + : ((as_mic_intf_ep_attr & USB_BM_ATTRIBUTES_XFERTYPE_MASK) == USB_BM_ATTRIBUTES_XFER_BULK ? "BULK" : "Unknown"), as_mic_intf_ep_addr, as_mic_intf_ep_mps); + if (!as_mic_ch_num_found) { + ESP_LOGW(TAG, "\tMic channel num %d Not supported", usb_dev->uac_cfg.mic_ch_num); + } + if (!as_mic_bit_res_found) { + ESP_LOGW(TAG, "\tMic bit resolution %d Not supported", usb_dev->uac_cfg.mic_bit_resolution); + } + if (!as_mic_freq_found) { + ESP_LOGW(TAG, "\tMic frequency %"PRIu32" Not supported", usb_dev->uac_cfg.mic_samples_frequence); + } else { + uac_dev->freq_ctrl_support[UAC_MIC] = as_mic_freq_ctrl_found; + ESP_LOGI(TAG, "\tMic frequency control %s Support", as_mic_freq_ctrl_found ? "" : "Not"); + } + } else if ( usb_dev->enabled[STREAM_UAC_MIC] && usb_dev->uac_cfg.mic_interface) { + UVC_ENTER_CRITICAL(); + uac_dev->as_ifc[UAC_MIC]->interface = usb_dev->uac_cfg.mic_interface; + uac_dev->as_ifc[UAC_MIC]->interface_alt = 1; + uac_dev->as_ifc[UAC_MIC]->ep_addr = usb_dev->uac_cfg.mic_ep_addr; + UVC_EXIT_CRITICAL(); + uac_dev->as_ifc[UAC_MIC]->ep_mps = usb_dev->uac_cfg.mic_ep_mps; + ESP_LOGW(TAG, "Mic interface NOT found"); + ESP_LOGW(TAG, "Try with user's config"); + as_mic_intf_found = true; + } else { + as_mic_intf_found = false; + } + + if (usb_dev->enabled[STREAM_UAC_MIC]) { + uac_dev->as_ifc[UAC_MIC]->not_found = !as_mic_intf_found; + } + if (usb_dev->enabled[STREAM_UAC_SPK]) { + uac_dev->as_ifc[UAC_SPK]->not_found = !as_spk_intf_found; + } + if (usb_dev->enabled[STREAM_UVC]) { + uvc_dev->vs_ifc->not_found = !vs_intf_found; + } + + if (!(as_mic_intf_found || as_spk_intf_found || vs_intf_found)) { + return ESP_ERR_NOT_FOUND; + } + return ESP_OK; +} + +static esp_err_t _usb_ctrl_xfer(urb_t *urb, TickType_t xTicksToWait) +{ + UVC_CHECK(_usb_device_get_state() > STATE_DEVICE_INSTALLED, "USB Device not active", ESP_ERR_INVALID_STATE); + UVC_CHECK(_usb_device_get_state() != STATE_DEVICE_RECOVER, "USB Device not active", ESP_ERR_INVALID_STATE); + UVC_CHECK(urb != NULL, "invalid args", ESP_ERR_INVALID_ARG); + // Dequeue the previous timeout urb + ESP_LOGD(TAG, "Control Transfer Start"); + hcd_urb_dequeue(s_usb_dev.dflt_pipe_hdl); + xEventGroupClearBits(s_usb_dev.event_group_hdl, USB_CTRL_PROC_SUCCEED | USB_CTRL_PROC_FAILED); + SYSVIEW_DFLT_PIPE_XFER_START(); + esp_err_t ret = hcd_urb_enqueue(s_usb_dev.dflt_pipe_hdl, urb); + UVC_CHECK(ESP_OK == ret, "urb enqueue failed", ESP_FAIL); + EventBits_t uxBits = xEventGroupWaitBits(s_usb_dev.event_group_hdl, USB_CTRL_PROC_SUCCEED | USB_CTRL_PROC_FAILED, pdTRUE, pdFALSE, xTicksToWait); + SYSVIEW_DFLT_PIPE_XFER_STOP(); + if (uxBits & USB_CTRL_PROC_SUCCEED) { + // If not failed, handle the result of the transfer. + ESP_LOGD(TAG, "Control Transfer Done"); + } else if (uxBits & USB_CTRL_PROC_FAILED) { + // If failed, the internal error handler will handle the urb. + ESP_LOGE(TAG, "Control Transfer Failed"); + return ESP_FAIL; + } else { + // If timeout, dequeue the urb before next transfer. + ESP_LOGE(TAG, "Control Transfer Timeout"); + // Notify the usb task to halt the transfer. + _event_msg_t evt_msg = { + ._type = USER_EVENT, + ._event.user_cmd = PIPE_RECOVER + }; + xQueueSend(s_usb_dev.queue_hdl, &evt_msg, xTicksToWait); + // wait for recover done + vTaskDelay(pdMS_TO_TICKS(50)); + ESP_LOGD(TAG, "Control Transfer Recover"); + return ESP_ERR_TIMEOUT; + } + urb_t *urb_done = hcd_urb_dequeue(s_usb_dev.dflt_pipe_hdl); + UVC_CHECK(urb_done == urb, "urb status: not same", ESP_FAIL); + UVC_CHECK(USB_TRANSFER_STATUS_COMPLETED == urb_done->transfer.status, "urb status: not complete", ESP_FAIL); + return ESP_OK; +} + +static esp_err_t _usb_set_device_interface(uint16_t interface, uint16_t interface_alt) +{ + UVC_CHECK(interface != 0, "interface can't be 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + urb_t *urb_ctrl = s_usb_dev.ctrl_urb; + bool need_free = false; + + if (urb_ctrl == NULL) { + urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t), NULL); + UVC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + need_free = true; + } + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_SETUP_PACKET_INIT_SET_INTERFACE((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer, interface, interface_alt); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t); + ESP_LOGI(TAG, "Set Device Interface = %u, Alt = %u", interface, interface_alt); + esp_err_t ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + if (ESP_OK == ret) { + ESP_LOGD(TAG, "Set Device Interface Done"); + } else { + ESP_LOGE(TAG, "Set Device Interface Failed"); + } + + if (need_free) { + _usb_urb_free(urb_ctrl); + } + return ret; +} + +static esp_err_t _uvc_vs_commit_control(uvc_stream_ctrl_t *ctrl_set, uvc_stream_ctrl_t *ctrl_probed) +{ + UVC_CHECK(ctrl_set != NULL && ctrl_probed != NULL, "pointer can not be NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + urb_t *urb_ctrl = s_usb_dev.ctrl_urb; + bool need_free = false; + if (urb_ctrl == NULL) { + urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + 128, NULL); + UVC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + need_free = true; + } + + ESP_LOGD(TAG, "SET_CUR Probe"); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UVC_PROBE_SET_REQ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer); + _uvc_stream_ctrl_to_buf((urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t)), ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength, ctrl_set); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength; + esp_err_t ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "SET_CUR Probe failed", free_urb_); + ESP_LOGD(TAG, "SET_CUR Probe Done"); + + ESP_LOGD(TAG, "GET_CUR Probe"); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UVC_PROBE_GET_REQ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength, s_usb_dev.ep_mps); //IN should be integer multiple of MPS + ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "GET_CUR Probe failed", free_urb_); + if ((urb_ctrl->transfer.actual_num_bytes > sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength)) { + ESP_LOGW(TAG, "Probe data overflow"); + } + _buf_to_uvc_stream_ctrl((urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t)), ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength, ctrl_probed); + ESP_LOGD(TAG, "GET_CUR Probe Done, actual_num_bytes:%d", urb_ctrl->transfer.actual_num_bytes); +#ifdef CONFIG_UVC_PRINT_PROBE_RESULT + _uvc_stream_ctrl_printf(stdout, ctrl_probed); +#endif + + ESP_LOGD(TAG, "SET_CUR Commit"); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UVC_COMMIT_REQ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer); + _uvc_stream_ctrl_to_buf((urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t)), ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength, ctrl_probed); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength; + ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "SET_CUR Commit failed", free_urb_); + ESP_LOGD(TAG, "SET_CUR Commit Done"); + +free_urb_: + if (need_free) { + _usb_urb_free(urb_ctrl); + } + return ret; +} + +static esp_err_t _uac_as_control_set_mute(uint16_t ac_itc, uint8_t ch, uint8_t fu_id, bool if_mute) +{ + UVC_CHECK(fu_id != 0, "invalid fu_id", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + //check if enum done + urb_t *urb_ctrl = s_usb_dev.ctrl_urb; + bool need_free = false; + if (urb_ctrl == NULL) { + urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + 64, NULL); + UVC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + need_free = true; + } + esp_err_t ret = ESP_OK; + for (size_t i = 0; i < 8; i++) { + if (ch & (1 << i)) { + ESP_LOGD(TAG, "SET_CUR mute, ac_itc = %u, ch = %u, fu_id = %u", ac_itc, i, fu_id); + ESP_LOGD(TAG, "%s CH%u", if_mute ? "Mute" : "UnMute", i); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UAC_SET_FU_MUTE((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer, i, fu_id, ac_itc); + unsigned char *p_data = urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t); + p_data[0] = if_mute; + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength; + ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "SET_CUR mute failed", free_urb_); + ESP_LOGD(TAG, "SET_CUR mute Done"); + } + } + +free_urb_: + if (need_free) { + _usb_urb_free(urb_ctrl); + } + return ret; +} + +static esp_err_t _uac_as_control_set_volume(uint16_t ac_itc, uint8_t ch, uint8_t fu_id, uint32_t volume) +{ + UVC_CHECK(fu_id != 0, "invalid fu_id", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + urb_t *urb_ctrl = s_usb_dev.ctrl_urb; + bool need_free = false; + if (urb_ctrl == NULL) { + urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + 64, NULL); + UVC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + need_free = true; + } + uint32_t volume_db = UAC_SPK_VOLUME_MIN + volume * UAC_SPK_VOLUME_STEP; + esp_err_t ret = ESP_OK; + for (size_t i = 0; i < 8; i++) { + if (ch & (1 << i)) { + ESP_LOGD(TAG, "SET_CUR volume 0x%04x (%"PRIu32") ac_itc=%u, ch=%u, fu_id=%u", (uint16_t)(volume_db & 0xffff), volume, ac_itc, i, fu_id); + ESP_LOGD(TAG, "Set volume CH%u: 0x%04xdb (%"PRIu32")", i, (uint16_t)(volume_db & 0xffff), volume); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UAC_SET_FU_VOLUME((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer, i, fu_id, ac_itc); + unsigned char *p_data = urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength; + p_data[0] = volume_db & 0x00ff; + p_data[1] = (volume_db & 0xff00) >> 8; + ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "SET_CUR volume failed", free_urb_); + ESP_LOGD(TAG, "SET_CUR volume Done"); + } + } + +free_urb_: + if (need_free) { + _usb_urb_free(urb_ctrl); + } + return ret; +} + +static esp_err_t _uac_as_control_set_freq(uint8_t ep_addr, uint32_t freq) +{ + UVC_CHECK(ep_addr != 0, "invalid ep_addr", ESP_ERR_INVALID_ARG); + UVC_CHECK(freq != 0, "invalid freq", ESP_ERR_INVALID_ARG) + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + if (ep_addr & USB_EP_DIR_MASK) { + UVC_CHECK(s_usb_dev.uac->freq_ctrl_support[UAC_MIC], "Mic frequency control not support", ESP_ERR_NOT_SUPPORTED); + } else { + UVC_CHECK(s_usb_dev.uac->freq_ctrl_support[UAC_SPK], "Speaker frequency control not support", ESP_ERR_NOT_SUPPORTED); + } + + urb_t *urb_ctrl = s_usb_dev.ctrl_urb; + bool need_free = false; + if (urb_ctrl == NULL) { + urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + 64, NULL); + UVC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + need_free = true; + } + ESP_LOGI(TAG, "Set frequence endpoint 0x%02x: (%"PRIu32") Hz", ep_addr, freq); + ESP_LOGD(TAG, "SET_CUR frequence %"PRIu32"", freq); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + USB_CTRL_UAC_SET_EP_FREQ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer, ep_addr); + unsigned char *p_data = urb_ctrl->transfer.data_buffer + sizeof(usb_setup_packet_t); + urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + ((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer)->wLength; + p_data[0] = freq & 0x0000ff; + p_data[1] = (freq & 0x00ff00) >> 8; + p_data[2] = (freq & 0xff0000) >> 16; + esp_err_t ret = _usb_ctrl_xfer(urb_ctrl, pdMS_TO_TICKS(TIMEOUT_USB_CTRL_XFER_MS)); + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_GOTO(ESP_OK == ret, "SET_CUR frequence failed", free_urb_); + ESP_LOGD(TAG, "SET_CUR frequence Done"); + +free_urb_: + if (need_free) { + _usb_urb_free(urb_ctrl); + } + return ret; +} + +/***************************************************LibUVC API Implements****************************************/ +/** + * @brief Swap the working buffer with the presented buffer and notify consumers + */ +IRAM_ATTR static void _uvc_swap_buffers(_uvc_stream_handle_t *strmh) +{ + /* to prevent the latest data from being lost + * if take mutex timeout, we should drop the last frame */ + size_t timeout_ms = 0; + if (s_usb_dev.uvc->vs_ifc->xfer_type == UVC_XFER_BULK) { + // for full speed bulk mode, max 19 packet can be transmit during each millisecond + timeout_ms = (s_usb_dev.uvc->vs_ifc->urb_num - 1) * (s_usb_dev.uvc->vs_ifc->bytes_per_packet * 0.052 / USB_EP_BULK_FS_MPS); + } else { + timeout_ms = (s_usb_dev.uvc->vs_ifc->urb_num - 1) * s_usb_dev.uvc->vs_ifc->packets_per_urb; + } + size_t timeout_tick = pdMS_TO_TICKS(timeout_ms); + if (timeout_tick == 0) { + timeout_tick = 1; + } + if (xSemaphoreTake(strmh->cb_mutex, timeout_tick) == pdTRUE) { + /* code */ + /* swap the buffers */ + uint8_t *tmp_buf = strmh->holdbuf; + strmh->hold_bytes = strmh->got_bytes; + strmh->holdbuf = strmh->outbuf; + strmh->outbuf = tmp_buf; + strmh->hold_last_scr = strmh->last_scr; + strmh->hold_pts = strmh->pts; + strmh->hold_seq = strmh->seq; + ESP_LOGV(TAG, "uvc swap buffer length = %d", strmh->hold_bytes); + xTaskNotifyGive(strmh->taskh); + xSemaphoreGive(strmh->cb_mutex); + } else { + ESP_LOGD(TAG, "timeout drop frame = %"PRIu32"", strmh->seq); + } + + strmh->seq++; + strmh->got_bytes = 0; + strmh->last_scr = 0; + strmh->pts = 0; +} + +/** + * @brief Clean buffer without swap + */ +IRAM_ATTR static void _uvc_drop_buffers(_uvc_stream_handle_t *strmh) +{ + strmh->got_bytes = 0; + strmh->last_scr = 0; + strmh->pts = 0; +} + +/** + * @brief Populate the fields of a frame to be handed to user code + * must be called with stream cb lock held! + */ +void _uvc_populate_frame(_uvc_stream_handle_t *strmh) +{ + if (strmh->hold_bytes > s_usb_dev.uvc_cfg.frame_buffer_size) { + ESP_LOGW(TAG, "Frame Buffer Overflow, framesize = %u", strmh->hold_bytes); + return; + } + + uvc_frame_t *frame = &strmh->frame; + frame->frame_format = strmh->frame_format; + frame->width = s_usb_dev.uvc->frame_width; + frame->height = s_usb_dev.uvc->frame_height; + + frame->step = 0; + frame->sequence = strmh->hold_seq; + frame->capture_time_finished = strmh->capture_time_finished; + frame->data_bytes = strmh->hold_bytes; + memcpy(frame->data, strmh->holdbuf, frame->data_bytes); +} + +/** + * @brief Process each payload of uvc transfer + * + * @param strmh stream handle + * @param payload payload buffer + * @param payload_len payload buffer length + */ +IRAM_ATTR static void _uvc_process_payload(_uvc_stream_handle_t *strmh, size_t req_len, uint8_t *payload, size_t payload_len) +{ + size_t header_len = 0; + size_t variable_offset = 0; + uint8_t header_info = 0; + size_t data_len = 0; + bool bulk_xfer = (s_usb_dev.uvc->vs_ifc->xfer_type == UVC_XFER_BULK) ? true : false; + bool payload_reassembling = (strmh->reassemble_flag) ? true : false; + uint8_t flag_lstp = 0; + uint8_t flag_zlp = 0; + uint8_t flag_rsb = 0; + + if (bulk_xfer && payload_reassembling) { + if (payload_len == req_len) { + //transfer not complete + flag_rsb = 1; + } else if (payload_len == 0) { + flag_zlp = 1; + ESP_LOGV(TAG, "payload_len == 0"); + } else { + flag_lstp = 1; + } + } else if (bulk_xfer && payload_len < req_len) { + flag_lstp = 1; + } else if (payload_len == 0) { + // ignore empty payload transfers + return; + } + +#ifdef CONFIG_UVC_PRINT_PAYLOAD_HEX + ESP_LOG_BUFFER_HEXDUMP("UVC_HEX", payload, payload_len, ESP_LOG_VERBOSE); +#endif + /********************* processing header *******************/ + if (!flag_zlp) { + ESP_LOGV(TAG, "zlp=%d, lstp=%d, req_len=%d, payload_len=%d, first=0x%02x, second=0x%02x", flag_zlp, flag_lstp, req_len, payload_len, payload[0], payload_len > 1 ? payload[1] : 0); + // make sure this is a header, judge from header length and bit field + // For SCR, PTS, some vendors not set bit, but also offer 12 Bytes header. so we just check SET condition + if ( payload_len >= payload[0] + && (payload[0] == 12 || (payload[0] == 2 && !(payload[1] & 0x0C)) || (payload[0] == 6 && !(payload[1] & 0x08))) + && !(payload[1] & 0x30) +#ifdef CONFIG_UVC_CHECK_HEADER_EOH + /* EOH bit, when set, indicates the end of the BFH fields + * Most camera set this bit to 1 in each header, but some vendors may not set it. + */ + && (payload[1] & 0x80) +#endif +#ifdef CONFIG_UVC_CHECK_BULK_JPEG_HEADER + && (!payload_reassembling || ((payload[payload[0]] == 0xff) && (payload[payload[0] + 1] == 0xd8))) +#endif + ) { + header_len = payload[0]; + data_len = payload_len - header_len; + /* checking the end-of-header */ + variable_offset = 2; + header_info = payload[1]; + + ESP_LOGV(TAG, "header=%u info=0x%02x, payload_len = %u, last_pts = %"PRIu32" , last_scr = %"PRIu32"", header_len, header_info, payload_len, strmh->pts, strmh->last_scr); + if (flag_rsb) { + ESP_LOGV(TAG, "reassembling start ..."); + strmh->reassembling = 1; + } + /* ERR bit defined in Stream Header*/ + if (header_info & 0x40) { + ESP_LOGW(TAG, "bad packet: error bit set"); + strmh->reassembling = 0; + return; + } + } else if (strmh->reassembling) { + ESP_LOGV(TAG, "reassembling %u + %u", strmh->got_bytes, payload_len); + data_len = payload_len; + } else { + if (payload_len > 1) { +#ifdef CONFIG_UVC_CHECK_HEADER_EOH + /* Give warning if EOH check enable, but camera not have*/ + if (!(payload[1] & 0x80)) { + ESP_LOGD(TAG, "bogus packet: EOH bit not set"); + } +#endif + ESP_LOGD(TAG, "bogus packet: len = %u %02x %02x...%02x %02x\n", payload_len, payload[0], payload[1], payload[payload_len - 2], payload[payload_len - 1]); + } + return; + } + } + + if (header_len >= 2) { + if (strmh->fid != (header_info & 1) && strmh->got_bytes != 0) { + /* The frame ID bit was flipped, but we have image data sitting + around from prior transfers. This means the camera didn't send + an EOF for the last transfer of the previous frame. */ +#if CONFIG_UVC_DROP_NO_EOF_FRAME + ESP_LOGW(TAG, "DROP NO EOF, got data=%u B", strmh->got_bytes); + _uvc_drop_buffers(strmh); +#else + ESP_LOGD(TAG, "SWAP NO EOF %d", strmh->got_bytes); + _uvc_swap_buffers(strmh); +#endif + } + + strmh->fid = header_info & 1; + if (header_info & (1 << 2)) { + strmh->pts = DW_TO_INT(payload + variable_offset); + variable_offset += 4; + } + + if (header_info & (1 << 3)) { + strmh->last_scr = DW_TO_INT(payload + variable_offset); + variable_offset += 6; + } + } + + /********************* processing data *****************/ + if (data_len >= 1) { + if (strmh->got_bytes + data_len > s_usb_dev.uvc_cfg.xfer_buffer_size) { + /* This means transfer buffer Not enough for whole frame, just drop whole buffer here. + Please increase buffer size to handle big frame*/ +#if CONFIG_UVC_DROP_OVERFLOW_FRAME + ESP_LOGW(TAG, "Transfer buffer overflow, got data=%u B, last=%u (%02x %02x...%02x %02x)", strmh->got_bytes + data_len, data_len + , payload[0], payload_len > 1 ? payload[1] : 0, payload_len > 1 ? payload[payload_len - 2] : 0, payload_len > 1 ? payload[payload_len - 1] : 0); + _uvc_drop_buffers(strmh); +#else + ESP_LOGD(TAG, "SWAP overflow %d", strmh->got_bytes); + _uvc_swap_buffers(strmh); +#endif + strmh->reassembling = 0; + return; + } else { + if (payload_len > 1) { + ESP_LOGV(TAG, "uvc payload = %02x %02x...%02x %02x\n", payload[header_len], payload[header_len + 1], payload[payload_len - 2], payload[payload_len - 1]); + } + memcpy(strmh->outbuf + strmh->got_bytes, payload + header_len, data_len); + } + + strmh->got_bytes += data_len; + } + /* Just ignore the EOF bit if using payload reassembling in bulk transfer */ + if (((header_info & (1 << 1)) && !payload_reassembling) || flag_zlp || flag_lstp) { + /* The EOF bit is set, so publish the complete frame */ + if (strmh->got_bytes != 0) { + _uvc_swap_buffers(strmh); + } + strmh->reassembling = 0; + } +} + +/** + * @brief open a video stream (only one can be created) + * + * @param devh device handle + * @param strmhp return stream handle if succeed + * @param config ctrl configs + * @return uvc_error_t + */ +static uvc_error_t uvc_stream_open_ctrl(uvc_device_handle_t *devh, _uvc_stream_handle_t **strmhp, uvc_stream_ctrl_t *ctrl) +{ + _uvc_stream_handle_t *strmh = NULL; + uvc_error_t ret; + strmh = heap_caps_calloc(1, sizeof(_uvc_stream_handle_t), MALLOC_CAP_INTERNAL); + + if (!strmh) { + ret = UVC_ERROR_NO_MEM; + goto fail_; + } + + strmh->devh = devh; + strmh->frame.library_owns_data = 1; + strmh->cur_ctrl = *ctrl; + strmh->running = 0; + strmh->outbuf = s_usb_dev.uvc_cfg.xfer_buffer_a; + strmh->holdbuf = s_usb_dev.uvc_cfg.xfer_buffer_b; + strmh->frame.data = s_usb_dev.uvc_cfg.frame_buffer; + + strmh->cb_mutex = xSemaphoreCreateMutex(); + + if (strmh->cb_mutex == NULL) { + ESP_LOGE(TAG, "line-%u Mutex create failed", __LINE__); + ret = UVC_ERROR_NO_MEM; + goto fail_; + } + + *strmhp = strmh; + return UVC_SUCCESS; + +fail_: + + if (strmh) { + free(strmh); + } + + return ret; +} + +static void _sample_processing_task(void *arg); + +/** Begin streaming video from the stream into the callback function. + * @ingroup streaming + * + * @param strmh UVC stream + * @param cb User callback function. See {uvc_frame_callback_t} for restrictions. + * @param flags Stream setup flags, currently undefined. Set this to zero. The lower bit + * is reserved for backward compatibility. + */ +static uvc_error_t uvc_stream_start(_uvc_stream_handle_t *strmh, uvc_frame_callback_t *cb, void *user_ptr, uint8_t flags) +{ + if (strmh->running) { + ESP_LOGW(TAG, "line:%u UVC_ERROR_BUSY", __LINE__); + return UVC_ERROR_BUSY; + } + + strmh->running = 1; + strmh->seq = 1; + strmh->fid = 0; + strmh->pts = 0; + strmh->last_scr = 0; + strmh->frame_format = UVC_FRAME_FORMAT_MJPEG; + strmh->user_cb = cb; + strmh->user_ptr = user_ptr; + + if (cb && strmh->taskh == NULL) { + xTaskCreatePinnedToCore(_sample_processing_task, SAMPLE_PROC_TASK_NAME, SAMPLE_PROC_TASK_STACK_SIZE, (void *)strmh, + SAMPLE_PROC_TASK_PRIORITY, &strmh->taskh, SAMPLE_PROC_TASK_CORE); + UVC_CHECK(strmh->taskh != NULL, "sample task create failed", UVC_ERROR_OTHER); + ESP_LOGD(TAG, "Sample processing task created"); + } + + return UVC_SUCCESS; +} + +/** @brief Stop stream. + * @ingroup streaming + * + * Stops stream, ends threads and cancels pollers + * + */ +static uvc_error_t uvc_stream_stop(_uvc_stream_handle_t *strmh) +{ + if (strmh->running == 0) { + ESP_LOGW(TAG, "line:%u UVC_ERROR_INVALID_PARAM", __LINE__); + return UVC_ERROR_INVALID_PARAM; + } + strmh->running = 0; + xTaskNotifyGive(strmh->taskh); + xEventGroupWaitBits(s_usb_dev.event_group_hdl, UVC_SAMPLE_PROC_STOP_DONE, pdTRUE, pdFALSE, portMAX_DELAY); + strmh->taskh = NULL; + vTaskDelay(pdMS_TO_TICKS(WAITING_TASK_RESOURCE_RELEASE_MS)); + ESP_LOGD(TAG, "uvc_stream_stop done"); + return UVC_SUCCESS; +} + +/** @brief Close stream. + * @ingroup streaming + * + * Closes stream, frees handle and all streaming resources. + * + * @param strmh UVC stream handle + */ +static void uvc_stream_close(_uvc_stream_handle_t *strmh) +{ + vSemaphoreDelete(strmh->cb_mutex); + free(strmh); + return; +} + +/****************************************************** Task Code ******************************************************/ +IRAM_ATTR static size_t _ring_buffer_get_len(RingbufHandle_t ringbuf_hdl) +{ + if (ringbuf_hdl == NULL) { + return 0; + } + size_t size = 0; + vRingbufferGetInfo(ringbuf_hdl, NULL, NULL, NULL, NULL, &size); + return size; +} + +IRAM_ATTR static void _ring_buffer_flush(RingbufHandle_t ringbuf_hdl) +{ + if (ringbuf_hdl == NULL) { + return; + } + size_t read_bytes = 0; + size_t uxItemsWaiting = 0; + vRingbufferGetInfo(ringbuf_hdl, NULL, NULL, NULL, NULL, &uxItemsWaiting); + uint8_t *buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, &read_bytes, 1, uxItemsWaiting); + + if (buf_rcv) { + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + } + ESP_LOGD(TAG, "buffer %u, flush -%u", uxItemsWaiting, read_bytes); +} + +IRAM_ATTR static esp_err_t _ring_buffer_push(RingbufHandle_t ringbuf_hdl, uint8_t *buf, size_t write_bytes, TickType_t xTicksToWait) +{ + if (ringbuf_hdl == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (buf == NULL) { + ESP_LOGD(TAG, "can not push NULL buffer"); + return ESP_ERR_INVALID_ARG; + } + int res = xRingbufferSend(ringbuf_hdl, buf, write_bytes, xTicksToWait); + + if (res != pdTRUE) { + ESP_LOGD(TAG, "buffer is too small, push failed"); + return ESP_FAIL; + } + return ESP_OK; +} + +IRAM_ATTR static esp_err_t _ring_buffer_pop(RingbufHandle_t ringbuf_hdl, uint8_t *buf, size_t req_bytes, size_t *read_bytes, TickType_t ticks_to_wait) +{ + if (ringbuf_hdl == NULL) { + return ESP_ERR_INVALID_STATE; + } + if (buf == NULL) { + ESP_LOGD(TAG, "can not pop buffer to NULL"); + return ESP_ERR_INVALID_STATE; + } + uint8_t *buf_rcv = xRingbufferReceiveUpTo(ringbuf_hdl, read_bytes, ticks_to_wait, req_bytes); + + if (buf_rcv) { + memcpy(buf, buf_rcv, *read_bytes); + vRingbufferReturnItem(ringbuf_hdl, (void *)(buf_rcv)); + return ESP_OK; + } + return ESP_FAIL; +} + +IRAM_ATTR static void _processing_mic_pipe(hcd_pipe_handle_t pipe_hdl, mic_callback_t *user_cb, void *user_ptr, bool if_enqueue) +{ + if (pipe_hdl == NULL) { + return; + } + + bool enqueue_flag = if_enqueue; + urb_t *urb_done = hcd_urb_dequeue(pipe_hdl); + if (urb_done == NULL) { + ESP_LOGD(TAG, "mic urb dequeue error"); + return; + } + + size_t xfered_size = 0; + mic_frame_t mic_frame = { + .bit_resolution = s_usb_dev.uac->bit_resolution[UAC_MIC], + .samples_frequence = s_usb_dev.uac->samples_frequence[UAC_MIC], + .data = s_usb_dev.uac->mic_frame_buf, + }; + + for (size_t i = 0; i < urb_done->transfer.num_isoc_packets; i++) { + if (urb_done->transfer.isoc_packet_desc[i].status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGW(TAG, "line:%u bad iso transit status %d", __LINE__, urb_done->transfer.isoc_packet_desc[i].status); + continue; + } else { + int actual_num_bytes = urb_done->transfer.isoc_packet_desc[i].actual_num_bytes; + int num_bytes = urb_done->transfer.isoc_packet_desc[i].num_bytes; + uint8_t *packet_buffer = urb_done->transfer.data_buffer + (i * num_bytes); +#if UAC_MIC_PACKET_COMPENSATION + int num_bytes_ms = s_usb_dev.uac->mic_ms_bytes; + if (actual_num_bytes != num_bytes_ms) { + ESP_LOGV(TAG, "Mic receive overflow %d, %d", actual_num_bytes, num_bytes_ms); + if (i == 0) { + //if first packet loss (small probability), we just padding all 0 + memset(packet_buffer, 0, num_bytes_ms); + ESP_LOGV(TAG, "MIC: padding 0"); + } else { + //if other packets loss or overflow, we just padding the last packet + uint8_t *packet_last_buffer = urb_done->transfer.data_buffer + (i * num_bytes - num_bytes); + memcpy(packet_buffer, packet_last_buffer, num_bytes_ms); + ESP_LOGV(TAG, "MIC: padding data"); + } + actual_num_bytes = num_bytes_ms; + } +#endif + if (xfered_size + actual_num_bytes <= s_usb_dev.uac->mic_frame_buf_size) { + memcpy(mic_frame.data + xfered_size, packet_buffer, actual_num_bytes); + xfered_size += actual_num_bytes; + } + } + } + mic_frame.data_bytes = xfered_size; + ESP_LOGV(TAG, "MIC RC %d", xfered_size); + ESP_LOGV(TAG, "mic payload = %02x %02x...%02x %02x\n", urb_done->transfer.data_buffer[0], urb_done->transfer.data_buffer[1], urb_done->transfer.data_buffer[xfered_size - 2], urb_done->transfer.data_buffer[xfered_size - 1]); + + if (s_usb_dev.uac->ringbuf_hdl[UAC_MIC]) { + esp_err_t ret = _ring_buffer_push(s_usb_dev.uac->ringbuf_hdl[UAC_MIC], mic_frame.data, mic_frame.data_bytes, 0); + if (ret != ESP_OK) { + ESP_LOGV(TAG, "mic ringbuf too small, please pop in time"); + } + } + + if (xfered_size > 0 && user_cb != NULL) { + user_cb(&mic_frame, user_ptr); + } + + if (enqueue_flag) { + esp_err_t ret = hcd_urb_enqueue(pipe_hdl, urb_done); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "MIC urb enqueue failed %s", esp_err_to_name(ret)); + } + } +} + +IRAM_ATTR static void _processing_spk_pipe(hcd_pipe_handle_t pipe_hdl, bool if_dequeue, bool reset) +{ + static size_t pending_urb_num = 0; + static size_t zero_counter = 0; + static urb_t *pending_urb[NUM_ISOC_SPK_URBS] = {NULL}; + esp_err_t ret = ESP_FAIL; + + if (reset) { + pending_urb_num = 0; + zero_counter = 0; + for (size_t j = 0; j < NUM_ISOC_SPK_URBS; j++) { + pending_urb[j] = NULL; + } + ESP_LOGD(TAG, "spk processing reset"); + return; + } + + if (pipe_hdl == NULL) { + return; + } + + if (!pending_urb_num && !if_dequeue) { + return; + } + + if (if_dequeue) { + size_t xfered_size = 0; + urb_t *urb_done = hcd_urb_dequeue(pipe_hdl); + if (urb_done == NULL) { + ESP_LOGD(TAG, "spk urb dequeue error"); + return; + } + for (size_t i = 0; i < urb_done->transfer.num_isoc_packets; i++) { + if (urb_done->transfer.isoc_packet_desc[i].status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGW(TAG, "line:%u bad iso transit status %d", __LINE__, urb_done->transfer.isoc_packet_desc[i].status); + break; + } else { + xfered_size += urb_done->transfer.isoc_packet_desc[i].actual_num_bytes; + } + } + ESP_LOGV(TAG, "SPK ST actual = %d", xfered_size); + /* add done urb to pending urb list */ + for (size_t i = 0; i < NUM_ISOC_SPK_URBS; i++) { + if (pending_urb[i] == NULL) { + pending_urb[i] = urb_done; + ++pending_urb_num; + assert(pending_urb_num <= NUM_ISOC_SPK_URBS); //should never happened + break; + } + } + } + /* check if we have buffered data need to send */ + if (_ring_buffer_get_len(s_usb_dev.uac->ringbuf_hdl[UAC_SPK]) < s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet) { +#if (!UAC_SPK_PACKET_COMPENSATION) + /* if speaker packet compensation not enable, just return here */ + return; +#endif + } else { + zero_counter = 0; + } + + /* fetch a pending urb from list */ + urb_t *next_urb = NULL; + size_t next_index = 0; + for (; next_index < NUM_ISOC_SPK_URBS; next_index++) { + if (pending_urb[next_index] != NULL) { + next_urb = pending_urb[next_index]; + break; + } + } + UVC_CHECK_RETURN_VOID(next_urb != NULL, "free urb not found"); + + size_t num_bytes_to_send = 0; + size_t buffer_size = s_usb_dev.uac->spk_max_xfer_size; + uint8_t *buffer = next_urb->transfer.data_buffer; + ret = _ring_buffer_pop(s_usb_dev.uac->ringbuf_hdl[UAC_SPK], buffer, buffer_size, &num_bytes_to_send, 0); + if (ret != ESP_OK || num_bytes_to_send == 0) { +#if (!UAC_SPK_PACKET_COMPENSATION) + //should never happened + return; +#else + if (pending_urb_num == NUM_ISOC_SPK_URBS) { + if (++zero_counter == (UAC_SPK_PACKET_COMPENSATION_TIMEOUT_MS / portTICK_PERIOD_MS)) { + /* if speaker packets compensation enable, we padding 0 to speaker */ + num_bytes_to_send = s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet * (UAC_SPK_PACKET_COMPENSATION_SIZE_MS>UAC_SPK_ST_MAX_MS_DEFAULT?UAC_SPK_ST_MAX_MS_DEFAULT:UAC_SPK_PACKET_COMPENSATION_SIZE_MS); + memset(buffer, 0, num_bytes_to_send); +#if UAC_SPK_PACKET_COMPENSATION_CONTINUOUS + zero_counter = 0; +#endif + ESP_LOGV(TAG, "SPK: padding 0 length = %d", num_bytes_to_send); + } + } +#endif + } + + if (num_bytes_to_send==0) { + return; + } + + // may drop some data here? + //size_t num_bytes_send = num_bytes_to_send - num_bytes_to_send % s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet; + size_t num_bytes_send = num_bytes_to_send; + size_t last_packet_bytes = num_bytes_send % s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet; + next_urb->transfer.num_bytes = num_bytes_send; + usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&next_urb->transfer; + transfer_dummy->num_isoc_packets = num_bytes_send / s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet + (last_packet_bytes ? 1 : 0); + for (size_t j = 0; j < transfer_dummy->num_isoc_packets; j++) { + //We need to initialize each individual isoc packet descriptor of the URB + next_urb->transfer.isoc_packet_desc[j].num_bytes = s_usb_dev.uac->as_ifc[UAC_SPK]->bytes_per_packet; + } + if (last_packet_bytes) { + next_urb->transfer.isoc_packet_desc[transfer_dummy->num_isoc_packets-1].num_bytes = last_packet_bytes; + } + + ret = hcd_urb_enqueue(pipe_hdl, next_urb); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "SPK urb enqueue failed %s", esp_err_to_name(ret)); + return; + } + + pending_urb[next_index] = NULL; + --pending_urb_num; + assert(pending_urb_num <= NUM_ISOC_SPK_URBS); //should never happened + ESP_LOGV(TAG, "SPK ST %d/%d", num_bytes_send, num_bytes_to_send); + ESP_LOGV(TAG, "spk payload = %02x %02x...%02x %02x\n", buffer[0], buffer[1], buffer[num_bytes_send - 2], buffer[num_bytes_send - 1]); +} + +static esp_err_t _event_set_bits_wait_cleared(EventGroupHandle_t evt_group, EventBits_t bits, TickType_t timeout) +{ + xEventGroupSetBits(evt_group, bits); + TickType_t counter = timeout; + while (xEventGroupGetBits(evt_group) & bits) { + vTaskDelay(1); + counter -= 1; + if (counter <= 0) { + ESP_LOGE(TAG, "bits(0x%04X) clear timeout %"PRIu32, (unsigned int)bits, timeout); + return ESP_ERR_TIMEOUT; + } + } + xEventGroupClearBits(evt_group, bits); + return ESP_OK; +} + +static void _usb_stream_kill_cb(int time_out_ms) +{ + ESP_LOGD(TAG, "usb stream kill"); + _event_set_bits_wait_cleared(s_usb_dev.event_group_hdl, USB_STREAM_TASK_KILL_BIT, pdMS_TO_TICKS(time_out_ms)); + //wait for task resource release + vTaskDelay(pdMS_TO_TICKS(WAITING_TASK_RESOURCE_RELEASE_MS)); + ESP_LOGD(TAG, "usb stream kill succeed"); +} + +static void _usb_stream_connect_cb(void) +{ + if ((s_usb_dev.enabled[STREAM_UAC_MIC] && !s_usb_dev.uac->as_ifc[UAC_MIC]->not_found) + || (s_usb_dev.enabled[STREAM_UAC_SPK] && !s_usb_dev.uac->as_ifc[UAC_SPK]->not_found) + || (s_usb_dev.enabled[STREAM_UVC] && !s_usb_dev.uvc->vs_ifc->not_found) ) { + if (s_usb_dev.stream_task_hdl != NULL) { + xTaskNotifyGive(s_usb_dev.stream_task_hdl); + } + } +} + +static void _usb_stream_recover_cb(int time_out_ms) +{ + _event_set_bits_wait_cleared(s_usb_dev.event_group_hdl, USB_STREAM_TASK_RECOVER_BIT, pdMS_TO_TICKS(time_out_ms)); +} + +static esp_err_t _usb_streaming_suspend(usb_stream_t stream) +{ + UVC_CHECK(stream < STREAM_MAX, "invalid stream type", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() > STATE_DEVICE_INSTALLED, "USB Device not active", ESP_ERR_INVALID_STATE); + _stream_ifc_t *p_itf = s_usb_dev.ifc[stream]; + esp_err_t ret = ESP_OK; + UVC_ENTER_CRITICAL(); + uint16_t interface_idx = p_itf->interface; + UVC_EXIT_CRITICAL(); + if (_usb_device_get_state() == STATE_DEVICE_ACTIVE) { + ret = _usb_set_device_interface(interface_idx, 0); + if (ESP_OK != ret) { + ESP_LOGE(TAG, "Set interface %u-%u failed", interface_idx, 0); + } + } + if (stream == STREAM_UVC && s_usb_dev.uvc->uvc_stream_hdl) { + uvc_stream_stop(s_usb_dev.uvc->uvc_stream_hdl); + uvc_stream_close(s_usb_dev.uvc->uvc_stream_hdl); + s_usb_dev.uvc->uvc_stream_hdl = NULL; + } + return ret; +} + +static esp_err_t _uvc_streaming_resume(void) +{ + _uvc_device_t *uvc_dev = s_usb_dev.uvc; + uvc_stream_ctrl_t ctrl_set = (uvc_stream_ctrl_t)DEFAULT_UVC_STREAM_CTRL(); + uvc_stream_ctrl_t ctrl_probed = {0}; + uvc_frame_size_t frame_size = {0}; + /* UVC negotiation process */ + UVC_ENTER_CRITICAL(); + ctrl_set.bFormatIndex = uvc_dev->format_index; + ctrl_set.bFrameIndex = uvc_dev->frame_index; + ctrl_set.dwFrameInterval = uvc_dev->frame_interval; + ctrl_set.dwMaxVideoFrameSize = s_usb_dev.uvc_cfg.frame_buffer_size; + /* For bulk transfer, payload size config by NUM_BULK_BYTES_PER_URB for better performance */ + ctrl_set.dwMaxPayloadTransferSize = (uvc_dev->vs_ifc->xfer_type == UVC_XFER_BULK)?NUM_BULK_BYTES_PER_URB:(uvc_dev->vs_ifc->ep_mps); + frame_size.width = uvc_dev->frame_width; + frame_size.height = uvc_dev->frame_height; + UVC_EXIT_CRITICAL(); + ESP_LOGI(TAG, "Probe Format(%u) MJPEG, Frame(%u) %u*%u, interval(%"PRIu32")", ctrl_set.bFormatIndex, + ctrl_set.bFrameIndex, frame_size.width, frame_size.height, ctrl_set.dwFrameInterval); + ESP_LOGI(TAG, "Probe payload size = %"PRIu32, ctrl_set.dwMaxPayloadTransferSize); + esp_err_t ret = _uvc_vs_commit_control(&ctrl_set, &ctrl_probed); + UVC_CHECK(ESP_OK == ret, "UVC negotiate failed", ESP_FAIL); + if (ctrl_set.dwMaxPayloadTransferSize != ctrl_probed.dwMaxPayloadTransferSize) { + ESP_LOGI(TAG, "dwMaxPayloadTransferSize set = %" PRIu32 ", probed = %" PRIu32, ctrl_set.dwMaxPayloadTransferSize, ctrl_probed.dwMaxPayloadTransferSize); + } + /* start uvc streaming */ + uvc_error_t uvc_ret = UVC_SUCCESS; + uvc_ret = uvc_stream_open_ctrl(NULL, &uvc_dev->uvc_stream_hdl, &ctrl_probed); + UVC_CHECK(uvc_ret == UVC_SUCCESS, "open uvc stream failed", ESP_FAIL); + uvc_ret = uvc_stream_start(uvc_dev->uvc_stream_hdl, s_usb_dev.uvc_cfg.frame_cb, s_usb_dev.uvc_cfg.frame_cb_arg, 0); + UVC_CHECK_GOTO(uvc_ret == UVC_SUCCESS, "start uvc stream failed", free_stream_); + if (uvc_dev->vs_ifc->xfer_type == UVC_XFER_ISOC) { + UVC_ENTER_CRITICAL(); + uint16_t interface = uvc_dev->vs_ifc->interface; + uint16_t alt_interface = uvc_dev->vs_ifc->interface_alt; + UVC_EXIT_CRITICAL(); + ret = _usb_set_device_interface(interface, alt_interface); + UVC_CHECK_GOTO(ESP_OK == ret, "Resume uvc interface failed", free_stream_); + } + ESP_LOGD(TAG, "UVC Streaming..."); + return ESP_OK; + +free_stream_: + if (uvc_dev->uvc_stream_hdl) { + uvc_stream_stop(uvc_dev->uvc_stream_hdl); + uvc_stream_close(uvc_dev->uvc_stream_hdl); + } + uvc_dev->uvc_stream_hdl = NULL; + return ESP_FAIL; +} + +static esp_err_t _uac_streaming_resume(usb_stream_t stream) +{ + UVC_CHECK(stream == STREAM_UAC_MIC || stream == STREAM_UAC_SPK, "invalid stream type", ESP_ERR_INVALID_ARG); + _uac_device_t *uac_dev = s_usb_dev.uac; + esp_err_t ret = ESP_OK; + _uac_internal_stream_t uac_stream = (stream == STREAM_UAC_MIC) ? UAC_MIC : UAC_SPK; + UVC_ENTER_CRITICAL(); + uint16_t interface = uac_dev->as_ifc[uac_stream]->interface; + uint16_t interface_alt = uac_dev->as_ifc[uac_stream]->interface_alt; + UVC_EXIT_CRITICAL(); + ret = _usb_set_device_interface(interface, interface_alt); + UVC_CHECK(ESP_OK == ret, "Set device interface failed", ESP_FAIL); + + UVC_ENTER_CRITICAL(); + bool freq_ctrl_support = uac_dev->freq_ctrl_support[uac_stream]; + uint8_t ep_addr = uac_dev->as_ifc[uac_stream]->ep_addr; + uint32_t samples_frequence = uac_dev->samples_frequence[uac_stream]; + UVC_EXIT_CRITICAL(); + if (freq_ctrl_support) { + ret = _uac_as_control_set_freq(ep_addr, samples_frequence); + UVC_CHECK_CONTINUE(ESP_OK == ret, "frequence set failed"); + } + ESP_LOGD(TAG, "%s Streaming...", (stream == STREAM_UAC_MIC) ? "MIC" : "SPK"); + return ESP_OK; +} + +static esp_err_t _usb_streaming_resume(usb_stream_t stream) +{ + UVC_CHECK(stream < STREAM_MAX, "invalid stream type", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + esp_err_t ret = ESP_OK; + + switch (stream) { + case STREAM_UVC: + ret = _uvc_streaming_resume(); + break; + case STREAM_UAC_SPK: + case STREAM_UAC_MIC: + ret = _uac_streaming_resume(stream); + break; + default: + break; + } + + return ret; +} + +static void _usb_stream_handle_task(void *arg) +{ + _uac_device_t *uac_dev = s_usb_dev.uac; + _uvc_device_t *uvc_dev = s_usb_dev.uvc; + _usb_device_t *usb_dev = &s_usb_dev; + esp_err_t ret = ESP_OK; + + ESP_LOGI(TAG, "USB stream task start"); + // Waiting for usbh ready + while (!(xEventGroupGetBits(usb_dev->event_group_hdl) & (USB_STREAM_TASK_KILL_BIT))) { + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)) == 0 || (_usb_device_get_state() != STATE_DEVICE_ACTIVE)) { + //waiting for usb ready, we are in recover state + xEventGroupClearBits(usb_dev->event_group_hdl, USB_STREAM_TASK_RECOVER_BIT); + continue; + } + // Create pipe when device connect + xQueueReset(usb_dev->stream_queue_hdl); + // Check user flags to set initial stream state + for (size_t i = 0; i < STREAM_MAX; i++) { + if (usb_dev->enabled[i]) { + uint32_t suspend_flag = i==STREAM_UVC ? FLAG_UVC_SUSPEND_AFTER_START + :(i==STREAM_UAC_MIC ? FLAG_UAC_MIC_SUSPEND_AFTER_START + :FLAG_UAC_SPK_SUSPEND_AFTER_START); + if (usb_dev->flags & suspend_flag) { + usb_dev->ifc[i]->suspended = true; + } else { + usb_dev->ifc[i]->suspended = false; + } + ESP_LOGD(TAG, "initial %s stream state %s", usb_dev->ifc[i]->name, usb_dev->ifc[i]->suspended ? "suspend" : "resume"); + } + } + + for (size_t i = 0; i < STREAM_MAX; i++) { + if (usb_dev->enabled[i] && !usb_dev->ifc[i]->not_found) { + _apply_pipe_config(i); + ret = _apply_stream_config(i); + /* the not found means, not found suitable configs from device descriptor */ + UVC_CHECK_GOTO((ret == ESP_OK || ret == ESP_ERR_NOT_FOUND), "apply streaming config failed", _apply_config_failed); + } + } + + // We detach the user callback, users may reset the suspended flag in callback + if (usb_dev->state_cb) { + usb_dev->state_cb(STREAM_CONNECTED, usb_dev->state_cb_arg); + } + //prepare urb and data buffer for stream pipe + for (size_t i = 0; i < STREAM_MAX; i++) { + if (usb_dev->enabled[i] && !usb_dev->ifc[i]->not_found) { + usb_ep_desc_t stream_ep_desc = (usb_ep_desc_t) { + .bLength = USB_EP_DESC_SIZE, + .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, + .bEndpointAddress = usb_dev->ifc[i]->ep_addr, + .bmAttributes = USB_BM_ATTRIBUTES_XFER_ISOC, + .wMaxPacketSize = usb_dev->ifc[i]->ep_mps, + .bInterval = 1, + }; + + if (usb_dev->ifc[i]->xfer_type == UVC_XFER_BULK) { + stream_ep_desc.bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK; + stream_ep_desc.bInterval = 0; + } + ESP_LOGD(TAG, "Creating %s pipe: ifc=%d-%d, ep=0x%02X, mps=%"PRIu32, usb_dev->ifc[i]->name, usb_dev->ifc[i]->interface, usb_dev->ifc[i]->interface_alt, + usb_dev->ifc[i]->ep_addr, usb_dev->ifc[i]->ep_mps); + usb_dev->ifc[i]->pipe_handle = _usb_pipe_init(usb_dev->port_hdl, &stream_ep_desc, usb_dev->dev_addr, usb_dev->dev_speed, + (void *)usb_dev->ifc[i]->type, &_usb_pipe_callback, (void *)usb_dev->stream_queue_hdl); + UVC_CHECK_GOTO(usb_dev->ifc[i]->pipe_handle != NULL, "pipe init failed", _usb_stream_recover); + /* If resume the interface, depend on whether the user flags suspend the stream + * Please Note that, when disconnect and reconnect the device, the stream state will be reset + */ + if (usb_dev->ifc[i]->suspended) { + ESP_LOGD(TAG, "Suspend %s stream. Reason: user suspend", usb_dev->ifc[i]->name); + continue; + } else if (ret == ESP_ERR_NOT_FOUND) { + ESP_LOGW(TAG, "Suspend %s stream. Reason: user's expected not found", usb_dev->ifc[i]->name); + continue; + } + + ret = _usb_streaming_resume(i); + UVC_CHECK_GOTO(ret == ESP_OK, "streaming resume failed", _usb_stream_recover); + _event_msg_t evt_msg = { + ._type = USER_EVENT, + ._event.user_cmd = STREAM_RESUME, + ._event_data = (void *)i, + ._handle.user_hdl = (void *)usb_dev->stream_task_hdl, //used to identify where the event come from + }; + xQueueSend(usb_dev->stream_queue_hdl, &evt_msg, 0); + } + } + xEventGroupSetBits(usb_dev->event_group_hdl, USB_STREAM_DEVICE_READY_BIT); + while (!(xEventGroupGetBits(usb_dev->event_group_hdl) & (USB_STREAM_TASK_KILL_BIT | USB_STREAM_TASK_RECOVER_BIT))) { + _event_msg_t evt_msg = {}; + if (xQueueReceive(usb_dev->stream_queue_hdl, &evt_msg, 1) != pdTRUE) { + // check if ringbuffer has data and we have free urb, send out here + if (usb_dev->enabled[STREAM_UAC_SPK] && (xEventGroupGetBits(usb_dev->event_group_hdl) & UAC_SPK_STREAM_RUNNING)) { + _processing_spk_pipe(uac_dev->as_ifc[UAC_SPK]->pipe_handle, false, false); + } + continue; + } + switch (evt_msg._type) { + case USER_EVENT: { + EventBits_t ack_bits = USB_STREAM_TASK_PROC_FAILED; + usb_stream_t stream = (usb_stream_t)evt_msg._event_data; + _stream_ifc_t *p_itf = usb_dev->ifc[stream]; + if (evt_msg._event.user_cmd == STREAM_SUSPEND) { + UVC_CHECK_GOTO((xEventGroupGetBits(usb_dev->event_group_hdl) & p_itf->evt_bit), "stream suspend: already suspend", _feedback_result); + ESP_LOGD(TAG, "%s stream suspend: flush transfer", p_itf->name); + ret = _usb_pipe_flush(p_itf->pipe_handle, p_itf->urb_num); + UVC_CHECK_GOTO(ret == ESP_OK, "stream suspend: flush transfer, failed", _feedback_result); + if (p_itf->urb_list) { + _usb_urb_list_free(p_itf->urb_list, p_itf->urb_num); + p_itf->urb_list = NULL; + ESP_LOGD(TAG, "%s stream suspend: free urb list succeed", p_itf->name); + } + if (stream == STREAM_UAC_SPK || stream == STREAM_UAC_MIC) { + _ring_buffer_flush(uac_dev->ringbuf_hdl[stream == STREAM_UAC_SPK ? UAC_SPK : UAC_MIC]); + } + ack_bits = USB_STREAM_TASK_PROC_SUCCEED; + xEventGroupClearBits(usb_dev->event_group_hdl, p_itf->evt_bit); + ESP_LOGD(TAG, "%s stream suspend, flush transfer succeed", p_itf->name); + } else if (evt_msg._event.user_cmd == STREAM_RESUME) { + UVC_CHECK_GOTO(!(xEventGroupGetBits(usb_dev->event_group_hdl) & p_itf->evt_bit), "stream resume: already resume", _feedback_result); + ESP_LOGD(TAG, "%s stream resume: enqueue transfer", p_itf->name); + if (evt_msg._handle.user_hdl != usb_dev->stream_task_hdl) { + ret = _apply_stream_config(stream); + UVC_CHECK_GOTO(ret == ESP_OK, "stream resume: apply config", _feedback_result); + } + if (p_itf->type == STREAM_UVC && p_itf->xfer_type == UVC_XFER_BULK) { + uvc_dev->uvc_stream_hdl->reassemble_flag = 0; + if (uvc_dev->uvc_stream_hdl->cur_ctrl.dwMaxPayloadTransferSize < p_itf->bytes_per_packet) { + p_itf->bytes_per_packet = uvc_dev->uvc_stream_hdl->cur_ctrl.dwMaxPayloadTransferSize; + } else if (uvc_dev->uvc_stream_hdl->cur_ctrl.dwMaxPayloadTransferSize > p_itf->bytes_per_packet) { + uvc_dev->uvc_stream_hdl->reassemble_flag = 1; + ESP_LOGD(TAG, "UVC Bulk Packet Reassemble Enable"); + } + } + if (p_itf->urb_list == NULL && p_itf->xfer_type == UVC_XFER_BULK) { + p_itf->urb_list = _usb_urb_list_alloc(p_itf->urb_num, 0, p_itf->bytes_per_packet); + UVC_CHECK_ABORT(p_itf->urb_list != NULL, "p_urb alloc failed"); + ESP_LOGD(TAG, "%s stream resume: alloc urb list succeed", p_itf->name); + } else if (p_itf->urb_list == NULL) { + p_itf->urb_list = _usb_urb_list_alloc(p_itf->urb_num, p_itf->packets_per_urb, p_itf->bytes_per_packet); + UVC_CHECK_ABORT(p_itf->urb_list != NULL, "p_urb alloc failed"); + ESP_LOGD(TAG, "%s stream resume: alloc urb list succeed", p_itf->name); + } + if (p_itf->type == STREAM_UAC_SPK) { + // For out stream, we set a minimum start zero packet + for (int j = 0; j < p_itf->urb_num; j++) { + usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)(&(p_itf->urb_list[j]->transfer)); + transfer_dummy->num_isoc_packets = 1; + transfer_dummy->num_bytes = p_itf->bytes_per_packet; + transfer_dummy->isoc_packet_desc[0].num_bytes = p_itf->bytes_per_packet; + } + _processing_spk_pipe(NULL, false, true); + } + ret = _usb_pipe_clear(p_itf->pipe_handle, p_itf->urb_num); + UVC_CHECK_GOTO(ret == ESP_OK, "stream resume: clear pipe, failed", _feedback_result); + ret = _usb_urb_list_enqueue(p_itf->pipe_handle, p_itf->urb_list, p_itf->urb_num); + UVC_CHECK_GOTO(ret == ESP_OK, "stream resume: enqueue transfer, failed", _feedback_result); + ack_bits = USB_STREAM_TASK_PROC_SUCCEED; + xEventGroupSetBits(usb_dev->event_group_hdl, p_itf->evt_bit); + ESP_LOGD(TAG, "%s stream resume: enqueue transfer succeed", p_itf->name); + } +_feedback_result: + if (evt_msg._handle.user_hdl != usb_dev->stream_task_hdl) { + xEventGroupSetBits(usb_dev->event_group_hdl, ack_bits); + } + break; + } + case PIPE_EVENT: { + usb_stream_t stream = (usb_stream_t)hcd_pipe_get_context(evt_msg._handle.pipe_handle); + _pipe_event_dflt_process(evt_msg._handle.pipe_handle, usb_dev->ifc[stream]->name, evt_msg._event.pipe_event); + switch (evt_msg._event.pipe_event) { + case HCD_PIPE_EVENT_URB_DONE: + if (stream == STREAM_UAC_MIC) { + SYSVIEW_UAC_MIC_PIPE_HANDLE_START(); + _processing_mic_pipe(uac_dev->as_ifc[UAC_MIC]->pipe_handle, usb_dev->uac_cfg.mic_cb, usb_dev->uac_cfg.mic_cb_arg, true); + SYSVIEW_UAC_MIC_PIPE_HANDLE_STOP(); + } else if (stream == STREAM_UAC_SPK) { + SYSVIEW_UAC_SPK_PIPE_HANDLE_START(); + _processing_spk_pipe(uac_dev->as_ifc[UAC_SPK]->pipe_handle, true, false); + SYSVIEW_UAC_SPK_PIPE_HANDLE_STOP(); + } else if (stream == STREAM_UVC) { + SYSVIEW_UVC_PIPE_HANDLE_START(); + _processing_uvc_pipe(uvc_dev->uvc_stream_hdl, uvc_dev->vs_ifc->pipe_handle, true); + SYSVIEW_UVC_PIPE_HANDLE_STOP(); + } + break; + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: + case HCD_PIPE_EVENT_ERROR_XFER: + goto _usb_stream_recover; + break; + case HCD_PIPE_EVENT_ERROR_STALL: { + _event_msg_t evt_msg = { + ._type = USER_EVENT, + ._event.user_cmd = STREAM_SUSPEND, + ._event_data = (void *)stream, + }; + //if stall, we just suspend then resume the pipe + xQueueSend(usb_dev->stream_queue_hdl, &evt_msg, 0); + evt_msg._event.user_cmd = STREAM_RESUME; + xQueueSend(usb_dev->stream_queue_hdl, &evt_msg, 0); + break; + } + default: + break; + } + break; + } + default: + break; + } + } +_usb_stream_recover: + xEventGroupClearBits(usb_dev->event_group_hdl, USB_STREAM_DEVICE_READY_BIT); + /* check if reset trigger by disconnect */ + ESP_LOGI(TAG, "usb stream task wait reset"); + EventBits_t uxBits = xEventGroupWaitBits(usb_dev->event_group_hdl, USB_STREAM_TASK_KILL_BIT | + USB_STREAM_TASK_RECOVER_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(1000)); + if (uxBits & (USB_STREAM_TASK_KILL_BIT | USB_STREAM_TASK_RECOVER_BIT)) { + // if reset trigger by disconnect, we just reset to default state + ESP_LOGI(TAG, "usb stream task reset, reason: device %s", (uxBits & USB_STREAM_TASK_KILL_BIT) ? "disconnect" : "recover"); + } else if (_usb_device_get_state() == STATE_DEVICE_ACTIVE) { + // if reset trigger by other reason and device is active, we just recover the stream + xTaskNotifyGive(usb_dev->stream_task_hdl); + ESP_LOGW(TAG, "usb stream task recover, reason: stream error"); + } else { + // if reset trigger by other reason and device is not active, we just reset to default state + ESP_LOGW(TAG, "usb stream task reset, reason: device not active"); + } + + for (size_t i = 0; i < STREAM_MAX; i++) { + if (usb_dev->enabled[i] && !usb_dev->ifc[i]->not_found) { + ESP_LOGI(TAG, "Resetting %s pipe", usb_dev->ifc[i]->name); + _usb_streaming_suspend(i); + _usb_pipe_deinit(usb_dev->ifc[i]->pipe_handle, usb_dev->ifc[i]->urb_num); + usb_dev->ifc[i]->pipe_handle = NULL; + _usb_urb_list_free(usb_dev->ifc[i]->urb_list, usb_dev->ifc[i]->urb_num); + usb_dev->ifc[i]->urb_list = NULL; + } + } + if (uac_dev != NULL) { + _ring_buffer_flush(uac_dev->ringbuf_hdl[UAC_SPK]); + _ring_buffer_flush(uac_dev->ringbuf_hdl[UAC_MIC]); + } + xEventGroupClearBits(usb_dev->event_group_hdl, USB_UVC_STREAM_RUNNING | UAC_SPK_STREAM_RUNNING | UAC_MIC_STREAM_RUNNING); + if (usb_dev->state_cb) { + usb_dev->state_cb(STREAM_DISCONNECTED, usb_dev->state_cb_arg); + } + xEventGroupClearBits(usb_dev->event_group_hdl, USB_STREAM_TASK_RECOVER_BIT); +_apply_config_failed: + continue; + } //handle hotplug + ESP_LOGI(TAG, "USB stream task deleted"); + ESP_LOGD(TAG, "USB stream task watermark = %d B", uxTaskGetStackHighWaterMark(NULL)); + xEventGroupClearBits(usb_dev->event_group_hdl, USB_STREAM_TASK_KILL_BIT); + vTaskDelete(NULL); +} +static uint32_t _usb_port_actions_update(hcd_port_event_t port_evt, uint32_t action_bits) +{ + switch (port_evt) { + case HCD_PORT_EVENT_CONNECTION: + action_bits &= ~ACTION_DEVICE_DISCONNECT; + action_bits = ACTION_DEVICE_CONNECT; + break; + case HCD_PORT_EVENT_DISCONNECTION: + action_bits &= ~ACTION_DEVICE_CONNECT; + action_bits = ACTION_DEVICE_DISCONNECT; + action_bits |= ACTION_PORT_RECOVER; + action_bits |= ACTION_PIPE_DFLT_DISABLE; + break; + case HCD_PORT_EVENT_ERROR: + case HCD_PORT_EVENT_OVERCURRENT: + action_bits = ACTION_PORT_RECOVER; + action_bits |= ACTION_PIPE_DFLT_DISABLE; + break; + default: + break; + } + ESP_LOGD(TAG, "port update: action_bits = 0x%04X", (unsigned int)action_bits); + return action_bits; +} + +static uint32_t _usb_pipe_actions_update(hcd_pipe_event_t pipe_evt, uint32_t action_bits) +{ + switch (pipe_evt) { + case HCD_PIPE_EVENT_URB_DONE: + action_bits |= ACTION_PIPE_XFER_DONE; + break; + case HCD_PIPE_EVENT_ERROR_XFER: + case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + action_bits |= ACTION_PIPE_DFLT_RECOVER; + action_bits |= ACTION_PIPE_DFLT_CLEAR; + action_bits |= ACTION_PIPE_XFER_FAIL; + break; + case HCD_PIPE_EVENT_ERROR_STALL: + action_bits |= ACTION_PIPE_DFLT_CLEAR; + action_bits |= ACTION_PIPE_XFER_FAIL; + break; + default: + break; + } + ESP_LOGD(TAG, "pipe update: action_bits = 0x%04X", (unsigned int)action_bits); + return action_bits; +} + +static uint32_t _user_actions_update(_user_cmd_t usr_cmd, uint32_t action_bits) +{ + switch (usr_cmd) { + case USB_RECOVER: + action_bits = ACTION_PORT_RECOVER; + action_bits |= ACTION_PIPE_DFLT_DISABLE; + break; + case PIPE_RECOVER: + action_bits |= ACTION_PIPE_DFLT_RECOVER; + action_bits |= ACTION_PIPE_DFLT_CLEAR; + break; + default: + break; + } + ESP_LOGD(TAG, "user update: action_bits = 0x%04X", (unsigned int)action_bits); + return action_bits; +} + +static esp_err_t _uvc_uac_device_enum(bool abort_process, bool *waiting_urb_done) +{ + UVC_CHECK_GOTO(!abort_process, "Enum abort", stage_failed_); + _usb_device_t *usb_dev = &s_usb_dev; + urb_t *enum_done = NULL; + static urb_t *ctrl_urb = NULL; + static bool urb_need_free = false; + static uint16_t full_config_length = 0; + bool urb_need_enqueue = false; + esp_err_t ret = ESP_FAIL; + usb_transfer_t *enum_transfer = NULL; + if (waiting_urb_done) { + *waiting_urb_done = false; + } + if (ctrl_urb != NULL) { + enum_transfer = &ctrl_urb->transfer; + } + + if (usb_dev->enum_stage == ENUM_STAGE_NONE) { + usb_dev->enum_stage = ENUM_STAGE_START; + } else if (usb_dev->enum_stage % 2) { // Check if we are in check stage, which is always odd number + assert(usb_dev->dflt_pipe_hdl); //should not be NULL + enum_done = hcd_urb_dequeue(usb_dev->dflt_pipe_hdl); + UVC_CHECK_GOTO(enum_done == ctrl_urb, "urb cleared: enum abort", stage_failed_); + + if (enum_transfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(TAG, "Bad transfer status %d", enum_transfer->status); + if (enum_transfer->status == USB_TRANSFER_STATUS_STALL) { + hcd_pipe_command(usb_dev->dflt_pipe_hdl, HCD_PIPE_CMD_CLEAR); + } + goto stage_failed_; + } + UVC_CHECK_GOTO(enum_transfer->actual_num_bytes <= enum_transfer->num_bytes, "urb status: data overflow", stage_failed_); + } + switch (usb_dev->enum_stage) { + case ENUM_STAGE_START: { + if (!usb_dev->dflt_pipe_hdl) { + ESP_ERROR_CHECK(_usb_port_get_speed(usb_dev->port_hdl, &(usb_dev->dev_speed))); + ESP_LOGI(TAG, "USB Speed: %s-speed", usb_dev->dev_speed == USB_SPEED_FULL ? "full" : "low"); + xSemaphoreTake(usb_dev->xfer_mutex_hdl, portMAX_DELAY); + usb_dev->dflt_pipe_hdl = _usb_pipe_init(usb_dev->port_hdl, NULL, 0, usb_dev->dev_speed, + (void *)usb_dev->queue_hdl, &_usb_pipe_callback, (void *)usb_dev->queue_hdl); + xSemaphoreGive(usb_dev->xfer_mutex_hdl); + UVC_CHECK_GOTO(usb_dev->dflt_pipe_hdl != NULL, "default pipe create failed", stage_failed_); + usb_dev->ep_mps = usb_dev->dev_speed == USB_SPEED_FULL ? USB_EP0_FS_DEFAULT_MPS : USB_EP0_LS_DEFAULT_MPS; + } + //else malloc a new one for enum stage + if (enum_transfer == NULL) { + //if we already have a ctrl urb, just reuse it. else malloc a new one + if (usb_dev->ctrl_urb != NULL) { + ctrl_urb = usb_dev->ctrl_urb; + } else { + ctrl_urb = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + CTRL_TRANSFER_DATA_MAX_BYTES, NULL); + UVC_CHECK_GOTO(ctrl_urb != NULL, "default pipe create failed", stage_failed_); + urb_need_free = true; + } + } +#ifndef CONFIG_UVC_GET_DEVICE_DESC + usb_dev->enum_stage = ENUM_STAGE_CHECK_SHORT_DEV_DESC; + ret = hcd_pipe_update_mps(usb_dev->dflt_pipe_hdl, usb_dev->ep_mps); + ESP_LOGI(TAG, "Default pipe endpoint MPS update to %d", usb_dev->ep_mps); + UVC_CHECK_GOTO(ESP_OK == ret, "default pipe update MPS failed", stage_failed_); +#endif + break; + } + case ENUM_STAGE_GET_SHORT_DEV_DESC: { + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_transfer->data_buffer); + ((usb_setup_packet_t *)enum_transfer->data_buffer)->wLength = USB_SHORT_DESC_REQ_LEN; + enum_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(USB_SHORT_DESC_REQ_LEN, usb_dev->ep_mps); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_SHORT_DEV_DESC: { + usb_device_desc_t *dev_desc = (usb_device_desc_t *)(enum_done->transfer.data_buffer + sizeof(usb_setup_packet_t)); + ret = hcd_pipe_update_mps(usb_dev->dflt_pipe_hdl, dev_desc->bMaxPacketSize0); + UVC_CHECK_GOTO(ESP_OK == ret, "default pipe update MPS failed", stage_failed_); + ESP_LOGI(TAG, "Default pipe endpoint MPS update to %d", dev_desc->bMaxPacketSize0); + usb_dev->ep_mps = dev_desc->bMaxPacketSize0; + break; + } + case ENUM_STAGE_SET_ADDR: { + USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)enum_transfer->data_buffer, usb_dev->dev_addr); + enum_transfer->num_bytes = sizeof(usb_setup_packet_t); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_ADDR: { + ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(usb_dev->dflt_pipe_hdl, usb_dev->dev_addr)); + vTaskDelay(pdMS_TO_TICKS(10)); //Wait SET ADDRESS recovery interval +#ifndef CONFIG_UVC_GET_CONFIG_DESC + usb_dev->enum_stage = ENUM_STAGE_CHECK_FULL_CONFIG_DESC; +#endif + break; + } + case ENUM_STAGE_GET_FULL_DEV_DESC: { + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_transfer->data_buffer); + enum_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_device_desc_t), s_usb_dev.ep_mps); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_FULL_DEV_DESC: { + usb_device_desc_t *dev_desc = (usb_device_desc_t *)(enum_done->transfer.data_buffer + sizeof(usb_setup_packet_t)); + print_device_descriptor((const uint8_t *)dev_desc); + break; + } + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: { + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)enum_transfer->data_buffer, usb_dev->configuration - 1, USB_ENUM_SHORT_DESC_REQ_LEN); + enum_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(USB_ENUM_SHORT_DESC_REQ_LEN, s_usb_dev.ep_mps); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: { + usb_config_desc_t *cfg_desc = (usb_config_desc_t *)(enum_done->transfer.data_buffer + sizeof(usb_setup_packet_t)); + full_config_length = cfg_desc->wTotalLength; + UVC_CHECK_GOTO(full_config_length <= CTRL_TRANSFER_DATA_MAX_BYTES, "Configuration descriptor larger than control transfer max length", stage_failed_); + break; + } + case ENUM_STAGE_GET_FULL_CONFIG_DESC: { + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)enum_transfer->data_buffer, usb_dev->configuration - 1, full_config_length); + enum_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(full_config_length, s_usb_dev.ep_mps); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: { + usb_config_desc_t *cfg_desc = (usb_config_desc_t *)(enum_done->transfer.data_buffer + sizeof(usb_setup_packet_t)); + ret = _update_config_from_descriptor(cfg_desc); + UVC_CHECK_GOTO(ret == ESP_OK, "Descriptor Parse Result: No matching configurations", stage_failed_); + break; + } + case ENUM_STAGE_SET_CONFIG: { + USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)enum_transfer->data_buffer, usb_dev->configuration); + enum_transfer->num_bytes = sizeof(usb_setup_packet_t); + urb_need_enqueue = true; + break; + } + case ENUM_STAGE_CHECK_CONFIG: { + // Enum process done + break; + } + default: + break; + } + if (urb_need_enqueue) { + ret = hcd_urb_enqueue(usb_dev->dflt_pipe_hdl, ctrl_urb); + UVC_CHECK_GOTO(ret == ESP_OK, "ctrl urb enqueue failed", stage_failed_); + if (waiting_urb_done) { + *waiting_urb_done = true; + } + } + if (usb_dev->enum_stage < ENUM_STAGE_CHECK_CONFIG) { + ESP_LOGI(TAG, "ENUM Stage %s, Succeed", STAGE_STR[usb_dev->enum_stage]); + usb_dev->enum_stage++; + return ESP_OK; + } else { + usb_dev->enum_stage = ENUM_STAGE_NONE; + } +stage_failed_: + if (urb_need_free) { + _usb_urb_free(ctrl_urb); + urb_need_free = false; + } + ctrl_urb = NULL; + if (usb_dev->enum_stage == ENUM_STAGE_NONE) { + return ESP_OK; + } + ESP_LOGE(TAG, "ENUM Stage %s, Failed", STAGE_STR[usb_dev->enum_stage]); + usb_dev->enum_stage = ENUM_STAGE_FAILED; + return ESP_FAIL; +} + +static void _usb_processing_task(void *arg) +{ + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + _usb_device_t *usb_dev = &s_usb_dev; + esp_err_t ret = ESP_OK; + _event_msg_t evt_msg = {}; + uint32_t action_bits = 0; +#ifdef CONFIG_USB_ENUM_FAILED_RETRY + int enum_retry_delay_ms = 0; + int enum_retry_count = 0; +#endif + ESP_LOGD(TAG, "USB task start"); + usb_dev->port_hdl = _usb_port_init(&_usb_port_callback, (void *)usb_dev->queue_hdl); + UVC_CHECK_GOTO(usb_dev->port_hdl != NULL, "USB Port init failed", free_task_); + xEventGroupSetBits(usb_dev->event_group_hdl, USB_HOST_INIT_DONE); + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_INSTALLED; + UVC_EXIT_CRITICAL(); + ESP_LOGD(TAG, "USB Task: Waiting Connection"); + int debug_counter = 0; + while (1) { + if (xQueueReceive(usb_dev->queue_hdl, &evt_msg, 1) == pdTRUE) { + // update action bits based on events + if (evt_msg._type == PORT_EVENT) { + hcd_port_event_t port_actual_evt = _usb_port_event_dflt_process(usb_dev->port_hdl, evt_msg._event.port_event); + action_bits = _usb_port_actions_update(port_actual_evt, action_bits); +#ifdef CONFIG_USB_ENUM_FAILED_RETRY + //enum retry count reset to default if new device is connected + if (port_actual_evt == HCD_PORT_EVENT_CONNECTION) { + enum_retry_count = CONFIG_USB_ENUM_FAILED_RETRY_COUNT; + } +#endif + } else if (evt_msg._type == PIPE_EVENT) { + hcd_pipe_event_t dflt_pipe_evt = _pipe_event_dflt_process(usb_dev->dflt_pipe_hdl, "default", evt_msg._event.pipe_event); + action_bits = _usb_pipe_actions_update(dflt_pipe_evt, action_bits); + } else if (evt_msg._type == USER_EVENT) { + // process event from usb stream task + action_bits = _user_actions_update(evt_msg._event.user_cmd, action_bits); + } + } + if (xEventGroupGetBits(usb_dev->event_group_hdl) & USB_HOST_TASK_KILL_BIT) { + action_bits = ACTION_PORT_DISABLE; + action_bits |= ACTION_PIPE_DFLT_DISABLE; + } + // process action bits + if (action_bits == 0) { + // no action bits set, wait for event + if (++debug_counter % 300 == 0) { + ESP_LOGD(TAG, "USB task running %d", debug_counter); + } + vTaskDelay(pdMS_TO_TICKS(10)); + continue; + } + + if (usb_dev->state == STATE_DEVICE_ENUM && (action_bits & ACTION_PIPE_XFER_FAIL)) { + // transfer failed during enum process, back to enum action to judge if need retry + action_bits |= ACTION_DEVICE_ENUM; + } + if (usb_dev->state == STATE_DEVICE_ENUM && (action_bits & (ACTION_PORT_RECOVER + | ACTION_PORT_DISABLE | ACTION_PIPE_DFLT_DISABLE))) { + // If user disable, or port error, or disconnect happened, Force end the enum process without retry + _uvc_uac_device_enum(true, NULL); + action_bits &= ~ACTION_DEVICE_ENUM; + action_bits &= ~ACTION_DEVICE_ENUM_RECOVER; + } + if (usb_dev->state == STATE_DEVICE_ACTIVE && (action_bits & (ACTION_PIPE_XFER_FAIL + | ACTION_PIPE_DFLT_CLEAR | ACTION_PIPE_DFLT_RECOVER | ACTION_PIPE_DFLT_DISABLE))) { + // If transfer fail or pipe recovering, send a signal to transfer invoker + xEventGroupSetBits(usb_dev->event_group_hdl, USB_CTRL_PROC_FAILED); + } + +#ifdef CONFIG_USB_ENUM_FAILED_RETRY + if (action_bits & ACTION_DEVICE_ENUM_RECOVER) { + // we only retry if port not disabled or reconnect during the retry delay + if (enum_retry_delay_ms > 0) { + enum_retry_delay_ms -= 10; + ESP_LOGD(TAG, "USB enum failed, retrying in %d ms", enum_retry_delay_ms); + vTaskDelay(pdMS_TO_TICKS(10)); + } else { + ESP_LOGW(TAG, "USB enum failed, retry now"); + usb_dev->enum_stage = ENUM_STAGE_NONE; + action_bits |= ACTION_DEVICE_ENUM; + action_bits &= ~ACTION_DEVICE_ENUM_RECOVER; + } + } +#endif + + if (action_bits & (ACTION_PORT_RECOVER | ACTION_PORT_DISABLE)) { + ESP_LOGI(TAG, "Recover Stream Task"); + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_RECOVER; + UVC_EXIT_CRITICAL(); + if (usb_dev->stream_task_hdl != NULL) { + _usb_stream_recover_cb(TIMEOUT_USB_STREAM_DISCONNECT_MS); + } + } + if (action_bits & ACTION_PIPE_XFER_FAIL) { + // we do nothing here, because all conditions has been handled + ESP_LOGD(TAG, "Action: ACTION_PIPE_XFER_FAIL"); + action_bits &= ~ACTION_PIPE_XFER_FAIL; + ESP_LOGD(TAG, "Action: ACTION_PIPE_XFER_FAIL, Done!"); + } + if (action_bits & ACTION_PIPE_DFLT_RECOVER) { + ESP_LOGI(TAG, "Action: ACTION_PIPE_DFLT_RECOVER"); + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_RECOVER; + UVC_EXIT_CRITICAL(); + if (usb_dev->dflt_pipe_hdl) { + ret = _usb_pipe_flush(usb_dev->dflt_pipe_hdl, 1); + UVC_CHECK_CONTINUE(ESP_OK == ret, "Default pipe flush failed"); + } + action_bits &= ~ACTION_PIPE_DFLT_RECOVER; + ESP_LOGD(TAG, "Action: ACTION_PIPE_DFLT_RECOVER, Done!"); + } + if (action_bits & ACTION_PIPE_DFLT_CLEAR) { + ESP_LOGI(TAG, "Action: ACTION_PIPE_DFLT_CLEAR"); + hcd_urb_dequeue(usb_dev->dflt_pipe_hdl); + ret = hcd_pipe_command(usb_dev->dflt_pipe_hdl, HCD_PIPE_CMD_CLEAR); + UVC_CHECK_CONTINUE(ESP_OK == ret, "Default pipe clear failed"); + action_bits &= ~ACTION_PIPE_DFLT_CLEAR; + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_ACTIVE; + UVC_EXIT_CRITICAL(); + ESP_LOGD(TAG, "Action: ACTION_PIPE_DFLT_CLEAR, Done!"); + } + if (action_bits & ACTION_PIPE_DFLT_DISABLE) { + ESP_LOGI(TAG, "Action: ACTION_PIPE_DFLT_DISABLE"); + if (usb_dev->dflt_pipe_hdl) { + ESP_LOGD(TAG, "Resetting default pipe"); + xSemaphoreTake(s_usb_dev.xfer_mutex_hdl, portMAX_DELAY); + ret = _usb_pipe_deinit(usb_dev->dflt_pipe_hdl, 1); + usb_dev->dflt_pipe_hdl = NULL; + xSemaphoreGive(s_usb_dev.xfer_mutex_hdl); + UVC_CHECK_CONTINUE(ESP_OK == ret, "Default pipe disable failed"); + } + action_bits &= ~ACTION_PIPE_DFLT_DISABLE; + ESP_LOGD(TAG, "Action: ACTION_PIPE_DFLT_DISABLE, Done!"); + } + if (action_bits & ACTION_PORT_RECOVER) { + ESP_LOGI(TAG, "Action: ACTION_PORT_RECOVER"); + ESP_LOGD(TAG, "USB port recovering from state(%d)", hcd_port_get_state(usb_dev->port_hdl)); + ret = hcd_port_recover(usb_dev->port_hdl); + UVC_CHECK_CONTINUE(ESP_OK == ret, "PORT Recover failed"); + ESP_LOGD(TAG, "USB port after recover, state(%d)", hcd_port_get_state(usb_dev->port_hdl)); + ret = hcd_port_command(usb_dev->port_hdl, HCD_PORT_CMD_POWER_ON); + UVC_CHECK_CONTINUE(ESP_OK == ret, "PORT Power failed"); + action_bits &= ~ACTION_PORT_RECOVER; + ESP_LOGD(TAG, "Action: ACTION_PORT_RECOVER, Done!"); + xQueueReset(usb_dev->queue_hdl); + } + if (action_bits & ACTION_PORT_DISABLE) { + ESP_LOGI(TAG, "Action: ACTION_PORT_DISABLE"); + if (usb_dev->stream_task_hdl != NULL) { + _usb_stream_kill_cb(TIMEOUT_USB_STREAM_DEINIT_MS); + } + action_bits &= ~ACTION_PORT_DISABLE; + ESP_LOGD(TAG, "Action: ACTION_PORT_DISABLE, Done!"); + break; + } + if (action_bits & ACTION_DEVICE_DISCONNECT) { + ESP_LOGI(TAG, "Action: ACTION_DEVICE_DISCONN"); + action_bits &= ~ACTION_DEVICE_DISCONNECT; + ESP_LOGD(TAG, "Action: ACTION_DEVICE_DISCONN, Done!"); + ESP_LOGI(TAG, "Waiting USB Connection"); + } + if (action_bits & ACTION_DEVICE_CONNECT) { + ESP_LOGI(TAG, "Action: ACTION_DEVICE_CONNECT"); + vTaskDelay(pdMS_TO_TICKS(WAITING_USB_AFTER_CONNECTION_MS)); + ESP_LOGI(TAG, "Resetting Port"); + static int reset_retry = 3; + ret = hcd_port_command(usb_dev->port_hdl, HCD_PORT_CMD_RESET); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Port Reset failed, retry = %d", reset_retry--); + vTaskDelay(pdMS_TO_TICKS(100)); + if (reset_retry == 0) { + reset_retry = 3; + action_bits &= ~ACTION_DEVICE_CONNECT; + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_RECOVER; + UVC_EXIT_CRITICAL(); + ESP_LOGE(TAG, "Port Reset failed, retry failed"); + } + continue; + } + reset_retry = 3; + ESP_LOGI(TAG, "Setting Port FIFO, %d", usb_dev->fifo_bias); + ESP_ERROR_CHECK(hcd_port_set_fifo_bias(usb_dev->port_hdl, usb_dev->fifo_bias)); + action_bits &= ~ACTION_DEVICE_CONNECT; + action_bits |= ACTION_DEVICE_ENUM; + usb_dev->enum_stage = ENUM_STAGE_NONE; + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_CONNECTED; + UVC_EXIT_CRITICAL(); + ESP_LOGD(TAG, "Action: ACTION_DEVICE_CONNECT, Done!"); + } + if (action_bits & ACTION_DEVICE_ENUM) { + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_ENUM; + UVC_EXIT_CRITICAL(); + _enum_stage_t enum_stage = usb_dev->enum_stage; + ESP_LOGD(TAG, "Action: ACTION_DEVICE_ENUM Stage %s (%d)", STAGE_STR[enum_stage], enum_stage); + bool if_waiting = false; + ret = _uvc_uac_device_enum(false, &if_waiting); + if (if_waiting) { + action_bits &= ~ACTION_DEVICE_ENUM; + ESP_LOGD(TAG, "Action: ACTION_DEVICE_ENUM, Waiting URB Done"); + } + if (ret != ESP_OK) { + action_bits &= ~ACTION_DEVICE_ENUM; +#ifdef CONFIG_USB_ENUM_FAILED_RETRY + // if enum failed, we only retry if port not disabled or recovered + if (--enum_retry_count > 0) { + enum_retry_delay_ms = CONFIG_USB_ENUM_FAILED_RETRY_DELAY_MS; + action_bits |= ACTION_DEVICE_ENUM_RECOVER; + ESP_LOGW(TAG, "USB enum failed, retrying in %d ms...", enum_retry_delay_ms); + } else { + ESP_LOGE(TAG, "USB enum failed, no more retry"); + } + UVC_ENTER_CRITICAL(); + usb_dev->state = (action_bits & ACTION_DEVICE_ENUM_RECOVER)?STATE_DEVICE_RECOVER:STATE_DEVICE_ENUM_FAILED; + UVC_EXIT_CRITICAL(); +#else + // encounter failed, block in enum failed state + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_ENUM_FAILED; + UVC_EXIT_CRITICAL(); +#endif + } else if (usb_dev->enum_stage == ENUM_STAGE_NONE) { + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_DEVICE_ACTIVE; + UVC_EXIT_CRITICAL(); + action_bits &= ~ACTION_DEVICE_ENUM; + _usb_stream_connect_cb(); + } + ESP_LOGD(TAG, "Action: ACTION_DEVICE_ENUM, Done (%d)", enum_stage); + } + if (action_bits & ACTION_PIPE_XFER_DONE) { + ESP_LOGD(TAG, "Action: ACTION_PIPE_XFER_DONE"); + if (usb_dev->state == STATE_DEVICE_ENUM) { + action_bits |= ACTION_DEVICE_ENUM; + } else { + xEventGroupSetBits(usb_dev->event_group_hdl, USB_CTRL_PROC_SUCCEED); + } + action_bits &= ~ACTION_PIPE_XFER_DONE; + ESP_LOGD(TAG, "Action: ACTION_PIPE_XFER_DONE, Done!"); + } + } + if (usb_dev->port_hdl) { + _usb_port_deinit(usb_dev->port_hdl); + usb_dev->port_hdl = NULL; + } +free_task_: + UVC_ENTER_CRITICAL(); + usb_dev->state = STATE_NONE; + UVC_EXIT_CRITICAL(); + ESP_LOGI(TAG, "_usb_processing_task deleted"); + ESP_LOGD(TAG, "_usb_processing_task watermark = %d B", uxTaskGetStackHighWaterMark(NULL)); + xEventGroupClearBits(usb_dev->event_group_hdl, (USB_HOST_INIT_DONE | USB_HOST_TASK_KILL_BIT)); + vTaskDelete(NULL); +} + +/*populate frame then call user callback*/ +static void _sample_processing_task(void *arg) +{ + UVC_CHECK_RETURN_VOID(arg != NULL, "sample task arg should be _uvc_stream_handle_t *"); + _uvc_stream_handle_t *strmh = (_uvc_stream_handle_t *)(arg); + uint32_t last_seq = 0; + ESP_LOGI(TAG, "Sample processing task start"); + + xEventGroupClearBits(s_usb_dev.event_group_hdl, UVC_SAMPLE_PROC_STOP_DONE); + do { + xSemaphoreTake(strmh->cb_mutex, portMAX_DELAY); + + while (strmh->running && last_seq == strmh->hold_seq) { + xSemaphoreGive(strmh->cb_mutex); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ESP_LOGV(TAG, "GOT LENGTH %d", strmh->hold_bytes); + xSemaphoreTake(strmh->cb_mutex, portMAX_DELAY); + } + + if (!strmh->running) { + xSemaphoreGive(strmh->cb_mutex); + ESP_LOGI(TAG, "sample processing stop"); + break; + } + + last_seq = strmh->hold_seq; + _uvc_populate_frame(strmh); + xSemaphoreGive(strmh->cb_mutex); + //user callback for decode and display, + strmh->user_cb(&strmh->frame, strmh->user_ptr); + /* code */ + } while (1); + + + ESP_LOGI(TAG, "Sample processing task deleted"); + ESP_LOGD(TAG, "Sample processing task watermark = %d B", uxTaskGetStackHighWaterMark(NULL)); + xEventGroupSetBits(s_usb_dev.event_group_hdl, UVC_SAMPLE_PROC_STOP_DONE); + vTaskDelete(NULL); +} + +static esp_err_t usb_stream_control(usb_stream_t stream, stream_ctrl_t ctrl_type) +{ + UVC_CHECK(ctrl_type == CTRL_SUSPEND || ctrl_type == CTRL_RESUME, "USB Device not active", ESP_ERR_INVALID_ARG); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + TaskHandle_t task_handle = xTaskGetCurrentTaskHandle(); + if (task_handle == s_usb_dev.stream_task_hdl) { + //If resume/suspend is called in stream task, we just set flag and return + if (ctrl_type == CTRL_SUSPEND) { + s_usb_dev.ifc[stream]->suspended = true; + } else { + s_usb_dev.ifc[stream]->suspended = false; + } + ESP_LOGD(TAG, "%s %s in state callback", ctrl_type == CTRL_SUSPEND ? "Suspend" : "Resume", s_usb_dev.ifc[stream]->name); + return ESP_OK; + } + esp_err_t ret = ESP_OK; + _stream_ifc_t *p_itf = s_usb_dev.ifc[stream]; + _event_msg_t event_msg = { + ._type = USER_EVENT, + ._event_data = (void *)stream, + ._event.user_cmd = STREAM_SUSPEND, + }; + + if (ctrl_type == CTRL_RESUME) { + //check if streaming is not running + UVC_CHECK(!(xEventGroupGetBits(s_usb_dev.event_group_hdl) & p_itf->evt_bit), "Streaming is running", ESP_ERR_INVALID_STATE); + ESP_LOGI(TAG, "Resume %s streaming", p_itf->name); + //resume streaming interface first + ret = _usb_streaming_resume(stream); + UVC_CHECK(ret == ESP_OK, "Resume interface Failed", ESP_FAIL); + //send resume command to resume transfer + event_msg._event.user_cmd = STREAM_RESUME; + xEventGroupClearBits(s_usb_dev.event_group_hdl, USB_STREAM_TASK_PROC_SUCCEED | USB_STREAM_TASK_PROC_FAILED); + xQueueSend(s_usb_dev.stream_queue_hdl, &event_msg, portMAX_DELAY); + EventBits_t uxBits = xEventGroupWaitBits(s_usb_dev.event_group_hdl, (USB_STREAM_TASK_PROC_SUCCEED | USB_STREAM_TASK_PROC_FAILED), pdTRUE, pdFALSE, pdMS_TO_TICKS(TIMEOUT_USER_COMMAND_MS)); + UVC_CHECK(uxBits & (USB_STREAM_TASK_PROC_SUCCEED), "Reset transfer failed/timeout", ESP_FAIL); + ESP_LOGD(TAG, "Resume %s streaming Done", p_itf->name); + } else if (ctrl_type == CTRL_SUSPEND) { + //check if streaming is running + UVC_CHECK((xEventGroupGetBits(s_usb_dev.event_group_hdl) & p_itf->evt_bit), "Streaming not running", ESP_ERR_INVALID_STATE); + ESP_LOGI(TAG, "Suspend %s streaming", p_itf->name); + //send suspend command to stop transfer first + event_msg._event.user_cmd = STREAM_SUSPEND; + xEventGroupClearBits(s_usb_dev.event_group_hdl, USB_STREAM_TASK_PROC_SUCCEED | USB_STREAM_TASK_PROC_FAILED); + xQueueSend(s_usb_dev.stream_queue_hdl, &event_msg, portMAX_DELAY); + EventBits_t uxBits = xEventGroupWaitBits(s_usb_dev.event_group_hdl, (USB_STREAM_TASK_PROC_SUCCEED | USB_STREAM_TASK_PROC_FAILED), pdTRUE, pdFALSE, pdMS_TO_TICKS(TIMEOUT_USER_COMMAND_MS)); + UVC_CHECK(uxBits & (USB_STREAM_TASK_PROC_SUCCEED), "Reset transfer failed/timeout", ESP_FAIL); + //suspend streaming interface + ret = _usb_streaming_suspend(stream); + UVC_CHECK(ret == ESP_OK, "Resume interface Failed", ESP_FAIL); + ESP_LOGD(TAG, "Suspend %s streaming Done", p_itf->name); + } + return ESP_OK; +} + +static esp_err_t uac_feature_control(usb_stream_t stream, stream_ctrl_t ctrl_type, void *ctrl_value) +{ + _stream_ifc_t *p_itf = s_usb_dev.ifc[stream]; + esp_err_t ret = ESP_OK; + bool submit_ctrl = true; + if (_usb_device_get_state() != STATE_DEVICE_ACTIVE) { + submit_ctrl = false; + } + + _uac_internal_stream_t uac_stream = (stream == STREAM_UAC_SPK ? UAC_SPK : UAC_MIC); + UVC_ENTER_CRITICAL(); + uint16_t ac_interface = s_usb_dev.uac->ac_interface; + uint8_t mute_ch = s_usb_dev.uac->mute_ch[uac_stream]; + uint8_t fu_id = s_usb_dev.uac->fu_id[uac_stream]; + uint8_t volume_ch = s_usb_dev.uac->volume_ch[uac_stream]; + UVC_EXIT_CRITICAL(); + + switch (ctrl_type) { + case CTRL_UAC_MUTE: + UVC_ENTER_CRITICAL(); + s_usb_dev.uac->mute[uac_stream] = (uint32_t)ctrl_value; + UVC_EXIT_CRITICAL(); + if (submit_ctrl && fu_id != 0) { + ret = _uac_as_control_set_mute(ac_interface, mute_ch, fu_id, (uint32_t)ctrl_value); + ESP_LOGI(TAG, "Set %s %s", stream == STREAM_UAC_SPK ? "SPK":"MIC", (uint32_t)ctrl_value ? "Mute":"UnMute"); + } else if (fu_id != 0) { + ret = ESP_ERR_INVALID_SIZE; + } + break; + case CTRL_UAC_VOLUME: + UVC_ENTER_CRITICAL(); + s_usb_dev.uac->volume[uac_stream] = (uint32_t)ctrl_value; + UVC_EXIT_CRITICAL(); + if (submit_ctrl && fu_id != 0) { + ret = _uac_as_control_set_volume(ac_interface, volume_ch, fu_id, (uint32_t)ctrl_value); + ESP_LOGI(TAG, "Set %s volume = %" PRIu32, stream == STREAM_UAC_SPK ? "SPK":"MIC", (uint32_t)ctrl_value); + } else if (fu_id != 0) { + ret = ESP_ERR_INVALID_SIZE; + } + break; + default: + break; + } + + if (ret == ESP_OK) { + ESP_LOGD(TAG, "%s Feature Control: type = %d value = %p, Done", p_itf->name, ctrl_type, ctrl_value); + } else { + ESP_LOGW(TAG, "%s Feature Control: type = %d value = %p, Failed", p_itf->name, ctrl_type, ctrl_value); + } + return ret; +} + +/***************************************************** Public API *********************************************************************/ +esp_err_t uac_streaming_config(const uac_config_t *config) +{ + UVC_CHECK(s_usb_dev.event_group_hdl == NULL, "usb streaming is running", ESP_ERR_INVALID_STATE); + //We do not prevent re-config, but the configs will take effect after re-connect/recover/re-enumeration/ + UVC_CHECK(config != NULL, "config can't NULL", ESP_ERR_INVALID_ARG); + if (config->mic_samples_frequence && config->mic_bit_resolution) { + //using samples_frequence and bit_resolution as enable condition + UVC_CHECK(config->mic_samples_frequence == UAC_FREQUENCY_ANY || (config->mic_samples_frequence >= 1000 && config->mic_samples_frequence <= 48000), + "mic samples frequence must <= 48000Hz and >= 1000 Hz ", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->mic_bit_resolution == UAC_BITS_ANY || (config->mic_bit_resolution >= 8 && config->mic_bit_resolution <= 24), + "mic bit resolution must >= 8 bit and <=24 bit", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->mic_ch_num == UAC_CH_ANY || (config->mic_ch_num >= 1 && config->mic_ch_num <= 2), + "mic channel number must >= 1 and <=2", ESP_ERR_INVALID_ARG); +#ifndef CONFIG_UVC_GET_CONFIG_DESC + UVC_CHECK(config->mic_interface, "mic interface can not be 0", ESP_ERR_INVALID_ARG); + if (config->ac_interface) { + UVC_CHECK(config->mic_fu_id, "mic feature unit id can not be 0", ESP_ERR_INVALID_ARG); + } +#endif + //below params act as backup configs, if suitable config not found from device descriptors + if (config->mic_interface) { + UVC_CHECK(config->mic_ep_addr & 0x80, "mic endpoint direction must IN", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->mic_ep_mps, "mic endpoint mps must > 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->mic_samples_frequence * config->mic_bit_resolution / 8000 <= config->mic_ep_mps, "mic packet size must <= endpoint mps", ESP_ERR_INVALID_ARG); + } + } + if (config->spk_samples_frequence && config->spk_bit_resolution) { + //using samples_frequence and bit_resolution as enable condition + UVC_CHECK(config->spk_samples_frequence == UAC_FREQUENCY_ANY || (config->spk_samples_frequence >= 1000 && config->spk_samples_frequence <= 48000), + "speaker samples frequence must <= 48000Hz and >= 1000 Hz ", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_bit_resolution == UAC_BITS_ANY || (config->spk_bit_resolution >= 8 && config->spk_bit_resolution <= 24), + "speaker bit resolution must >= 8 bit and <=24 bit", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_ch_num == UAC_CH_ANY || (config->spk_ch_num >= 1 && config->spk_ch_num <= 2), + "speaker channel number must >= 1 and <=2", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_buf_size, "spk buffer size can not be 0", ESP_ERR_INVALID_ARG); +#ifndef CONFIG_UVC_GET_CONFIG_DESC + UVC_CHECK(config->spk_interface, "spk interface can not be 0", ESP_ERR_INVALID_ARG); + if (config->ac_interface) { + UVC_CHECK(config->spk_fu_id, "spk feature unit id can not be 0", ESP_ERR_INVALID_ARG); + } +#endif + if (config->spk_interface) { + //if user set this interface manually, below param should also be set + UVC_CHECK(!(config->spk_ep_addr & 0x80), "spk endpoint direction must OUT", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_buf_size > config->spk_ep_mps, "spk buffer size should larger than endpoint size", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_ep_mps, "speaker endpoint mps must > 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->spk_samples_frequence * config->spk_bit_resolution / 8000 <= config->spk_ep_mps, "spk packet size must <= endpoint mps", ESP_ERR_INVALID_ARG); + } + } + s_usb_dev.uac_cfg = *config; + s_usb_dev.flags |= config->flags; + if (s_usb_dev.flags & FLAG_UAC_SPK_SUSPEND_AFTER_START) { + ESP_LOGI(TAG, "SPK Streaming Suspend After Start"); + } else if (s_usb_dev.flags & FLAG_UAC_MIC_SUSPEND_AFTER_START) { + ESP_LOGI(TAG, "MIC Streaming Suspend After Start"); + } + ESP_LOGI(TAG, "UAC Streaming Config Succeed, Version: %d.%d.%d", USB_STREAM_VER_MAJOR, USB_STREAM_VER_MINOR, USB_STREAM_VER_PATCH); + return ESP_OK; +} + +esp_err_t uvc_streaming_config(const uvc_config_t *config) +{ + UVC_CHECK(s_usb_dev.event_group_hdl == NULL, "usb streaming is running", ESP_ERR_INVALID_STATE); + //We do not prevent re-config, but the configs will take effect after re-connect/recover/re-enumeration/ + UVC_CHECK(config != NULL, "config can't NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK((config->frame_interval >= FRAME_MIN_INTERVAL && config->frame_interval <= FRAME_MAX_INTERVAL), + "frame_interval Support 333333~2000000", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_height != 0, "frame_height can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_width != 0, "frame_width can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_buffer_size != 0, "frame_buffer_size can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->xfer_buffer_size != 0, "xfer_buffer_size can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->xfer_buffer_a != NULL, "xfer_buffer_a can't NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->xfer_buffer_b != NULL, "xfer_buffer_b can't NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_buffer != NULL, "frame_buffer can't NULL", ESP_ERR_INVALID_ARG); +#ifndef CONFIG_UVC_GET_CONFIG_DESC + //Additional check for quick start mode + UVC_CHECK(config->interface, "interface can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->xfer_type == UVC_XFER_ISOC || config->xfer_type == UVC_XFER_BULK, "xfer_type must be UVC_XFER_ISOC or UVC_XFER_BULK", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->format_index != 0, "format_index can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_index != 0, "frame_index can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_width != FRAME_RESOLUTION_ANY, "frame_width can't FRAME_RESOLUTION_ANY", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->frame_height != FRAME_RESOLUTION_ANY, "frame_height can't FRAME_RESOLUTION_ANY", ESP_ERR_INVALID_ARG); +#endif + //below params act as backup configs, if suitable config not found from device descriptors + if (config->interface) { + UVC_CHECK(config->ep_addr & 0x80, "Endpoint direction must IN", ESP_ERR_INVALID_ARG); + if (config->xfer_type == UVC_XFER_ISOC) { + UVC_CHECK(config->ep_mps <= USB_EP_ISOC_IN_MAX_MPS, "Isoc total MPS must < USB_EP_ISOC_IN_MAX_MPS", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->interface_alt != 0, "Isoc interface alt num must > 0", ESP_ERR_INVALID_ARG); + } else { + UVC_CHECK(config->interface_alt == 0, "Bulk interface alt num must == 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(config->ep_mps == USB_EP_BULK_FS_MPS || config->ep_mps == USB_EP_BULK_HS_MPS, "Bulk MPS must == 64 or 512", ESP_ERR_INVALID_ARG); + } + } + s_usb_dev.uvc_cfg = *config; + s_usb_dev.flags |= config->flags; + if (s_usb_dev.flags & FLAG_UVC_SUSPEND_AFTER_START) { + ESP_LOGI(TAG, "UVC Streaming Suspend After Start"); + } + ESP_LOGI(TAG, "UVC Streaming Config Succeed, Version: %d.%d.%d", USB_STREAM_VER_MAJOR, USB_STREAM_VER_MINOR, USB_STREAM_VER_PATCH); +#ifdef CONFIG_USB_STREAM_QUICK_START + // Please make sure your camera can skip the enumeration stage and start streaming directly + ESP_LOGI(TAG, "Quick Start Mode Enabled"); +#endif + return ESP_OK; +} + +esp_err_t usb_streaming_start() +{ + UVC_CHECK(s_usb_dev.event_group_hdl == NULL, "usb streaming is running", ESP_ERR_INVALID_STATE); + s_usb_dev.event_group_hdl = xEventGroupCreate(); + UVC_CHECK(s_usb_dev.event_group_hdl != NULL, "Create event group failed", ESP_FAIL); + s_usb_dev.queue_hdl = xQueueCreate(USB_EVENT_QUEUE_LEN, sizeof(_event_msg_t)); + UVC_CHECK_GOTO(s_usb_dev.queue_hdl != NULL, "Create event queue failed", free_resource_); + s_usb_dev.stream_queue_hdl = xQueueCreate(USB_STREAM_EVENT_QUEUE_LEN, sizeof(_event_msg_t)); + UVC_CHECK_GOTO(s_usb_dev.stream_queue_hdl != NULL, "Create event queue failed", free_resource_); + s_usb_dev.xfer_mutex_hdl = xSemaphoreCreateMutex(); + UVC_CHECK_GOTO(s_usb_dev.xfer_mutex_hdl != NULL, "Create transfer mutex failed", free_resource_); + s_usb_dev.ctrl_smp_hdl = xSemaphoreCreateBinary(); + UVC_CHECK_GOTO(s_usb_dev.ctrl_smp_hdl != NULL, "Create ctrl mutex failed", free_resource_); + xSemaphoreGive(s_usb_dev.ctrl_smp_hdl); +#if USB_PRE_ALLOC_CTRL_TRANSFER_URB + s_usb_dev.ctrl_urb = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + CTRL_TRANSFER_DATA_MAX_BYTES, NULL); + UVC_CHECK_GOTO(s_usb_dev.ctrl_urb != NULL, "malloc ctrl urb failed", free_resource_); + ESP_LOGI(TAG, "Pre-alloc ctrl urb succeed, size = %d", CTRL_TRANSFER_DATA_MAX_BYTES); +#endif + s_usb_dev.dev_speed = USB_SPEED_FULL; + s_usb_dev.dev_addr = USB_DEVICE_ADDR; + s_usb_dev.configuration = USB_CONFIG_NUM; + s_usb_dev.fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED; + s_usb_dev.mps_limits = &mps_limits_default; + + if (s_usb_dev.uac_cfg.spk_samples_frequence && s_usb_dev.uac_cfg.spk_bit_resolution) { + //using samples_frequence and bit_resolution as enable condition + s_usb_dev.uac = heap_caps_calloc(1, sizeof(_uac_device_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uac != NULL, "malloc failed", free_resource_); + s_usb_dev.uac->as_ifc[UAC_SPK] = heap_caps_calloc(1, sizeof(_stream_ifc_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uac->as_ifc[UAC_SPK] != NULL, "malloc failed", free_resource_); + s_usb_dev.ifc[STREAM_UAC_SPK] = s_usb_dev.uac->as_ifc[UAC_SPK]; + s_usb_dev.ifc[STREAM_UAC_SPK]->type = STREAM_UAC_SPK; + s_usb_dev.ifc[STREAM_UAC_SPK]->name = "SPK"; + s_usb_dev.ifc[STREAM_UAC_SPK]->evt_bit = UAC_SPK_STREAM_RUNNING; + if (s_usb_dev.uac_cfg.spk_buf_size) { + s_usb_dev.uac->ringbuf_hdl[UAC_SPK] = xRingbufferCreate(s_usb_dev.uac_cfg.spk_buf_size, RINGBUF_TYPE_BYTEBUF); + ESP_LOGD(TAG, "Speaker ringbuf create succeed, size = %"PRIu32, s_usb_dev.uac_cfg.spk_buf_size); + UVC_CHECK_GOTO(s_usb_dev.uac->ringbuf_hdl[UAC_SPK] != NULL, "Create speak buffer failed", free_resource_); + } + ESP_LOGD(TAG, "Speaker instance created"); + s_usb_dev.enabled[STREAM_UAC_SPK] = true; + } + if (s_usb_dev.uac_cfg.mic_samples_frequence && s_usb_dev.uac_cfg.mic_bit_resolution) { + //using samples_frequence and bit_resolution as enable condition + if (s_usb_dev.uac == NULL) { + s_usb_dev.uac = heap_caps_calloc(1, sizeof(_uac_device_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uac != NULL, "malloc failed", free_resource_); + } + s_usb_dev.uac->as_ifc[UAC_MIC] = heap_caps_calloc(1, sizeof(_stream_ifc_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uac->as_ifc[UAC_MIC] != NULL, "malloc failed", free_resource_); + s_usb_dev.ifc[STREAM_UAC_MIC] = s_usb_dev.uac->as_ifc[UAC_MIC]; + s_usb_dev.ifc[STREAM_UAC_MIC]->type = STREAM_UAC_MIC; + s_usb_dev.ifc[STREAM_UAC_MIC]->name = "MIC"; + s_usb_dev.ifc[STREAM_UAC_MIC]->evt_bit = UAC_MIC_STREAM_RUNNING; + if (s_usb_dev.uac_cfg.mic_buf_size) { + s_usb_dev.uac->ringbuf_hdl[UAC_MIC] = xRingbufferCreate(s_usb_dev.uac_cfg.mic_buf_size, RINGBUF_TYPE_BYTEBUF); + ESP_LOGD(TAG, "MIC ringbuf create succeed, size = %"PRIu32, s_usb_dev.uac_cfg.mic_buf_size); + UVC_CHECK_GOTO(s_usb_dev.uac->ringbuf_hdl[UAC_MIC] != NULL, "Create speak buffer failed", free_resource_); + } + ESP_LOGD(TAG, "Speaker instance created"); + s_usb_dev.enabled[STREAM_UAC_MIC] = true; + } + if (s_usb_dev.uvc_cfg.frame_width && s_usb_dev.uvc_cfg.frame_height) { + //using frame_width and frame_height as enable condition + s_usb_dev.uvc = heap_caps_calloc(1, sizeof(_uvc_device_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uvc != NULL, "malloc failed", free_resource_); + s_usb_dev.uvc->vs_ifc = heap_caps_calloc(1, sizeof(_stream_ifc_t), MALLOC_CAP_INTERNAL); + UVC_CHECK_GOTO(s_usb_dev.uvc->vs_ifc != NULL, "malloc failed", free_resource_); + s_usb_dev.ifc[STREAM_UVC] = s_usb_dev.uvc->vs_ifc; + s_usb_dev.ifc[STREAM_UVC]->type = STREAM_UVC; + s_usb_dev.ifc[STREAM_UVC]->name = "UVC"; + s_usb_dev.ifc[STREAM_UVC]->evt_bit = USB_UVC_STREAM_RUNNING; + ESP_LOGD(TAG, "Camera instance created"); + s_usb_dev.enabled[STREAM_UVC] = true; + //if enable uvc, we should set fifo bias to RX + s_usb_dev.fifo_bias = HCD_PORT_FIFO_BIAS_RX; + s_usb_dev.mps_limits = &mps_limits_bias_rx; + } + UVC_CHECK_GOTO(s_usb_dev.enabled[STREAM_UAC_MIC] == true || s_usb_dev.enabled[STREAM_UAC_SPK] == true || s_usb_dev.enabled[STREAM_UVC] == true, "uac/uvc streaming not configured", free_resource_); + + TaskHandle_t usbh_taskh = NULL; + xTaskCreatePinnedToCore(_usb_processing_task, USB_PROC_TASK_NAME, USB_PROC_TASK_STACK_SIZE, NULL, + USB_PROC_TASK_PRIORITY, &usbh_taskh, USB_PROC_TASK_CORE); + UVC_CHECK_GOTO(usbh_taskh != NULL, "Create usb processing task failed", free_resource_); + xTaskCreatePinnedToCore(_usb_stream_handle_task, USB_STREAM_NAME, USB_STREAM_STACK_SIZE, NULL, + USB_STREAM_PRIORITY, &s_usb_dev.stream_task_hdl, USB_STREAM_CORE); + assert(s_usb_dev.stream_task_hdl != NULL); //can not handle this error, just assert + xTaskNotifyGive(usbh_taskh); + xEventGroupWaitBits(s_usb_dev.event_group_hdl, USB_HOST_INIT_DONE, pdFALSE, pdFALSE, portMAX_DELAY); + ESP_LOGI(TAG, "USB Streaming Start Succeed"); + return ESP_OK; + +free_resource_: + if (s_usb_dev.event_group_hdl) { + vEventGroupDelete(s_usb_dev.event_group_hdl); + s_usb_dev.event_group_hdl = NULL; + } + if (s_usb_dev.queue_hdl) { + vQueueDelete(s_usb_dev.queue_hdl); + s_usb_dev.queue_hdl = NULL; + } + if (s_usb_dev.stream_queue_hdl) { + vQueueDelete(s_usb_dev.stream_queue_hdl); + s_usb_dev.stream_queue_hdl = NULL; + } + if (s_usb_dev.xfer_mutex_hdl) { + vSemaphoreDelete(s_usb_dev.xfer_mutex_hdl); + s_usb_dev.xfer_mutex_hdl = NULL; + } + if (s_usb_dev.ctrl_smp_hdl) { + vSemaphoreDelete(s_usb_dev.ctrl_smp_hdl); + s_usb_dev.ctrl_smp_hdl = NULL; + } + if (s_usb_dev.ctrl_urb) { + _usb_urb_free(s_usb_dev.ctrl_urb); + s_usb_dev.ctrl_urb = NULL; + } + if (s_usb_dev.uvc) { + if (s_usb_dev.uvc->vs_ifc) { + free(s_usb_dev.uvc->vs_ifc); + } + s_usb_dev.ifc[STREAM_UVC] = NULL; + free(s_usb_dev.uvc); + } + if (s_usb_dev.uac) { + if (s_usb_dev.uac->ringbuf_hdl[UAC_SPK]) { + vRingbufferDelete(s_usb_dev.uac->ringbuf_hdl[UAC_SPK]); + } + if (s_usb_dev.uac->ringbuf_hdl[UAC_MIC]) { + vRingbufferDelete(s_usb_dev.uac->ringbuf_hdl[UAC_MIC]); + } + if (s_usb_dev.uac->as_ifc[UAC_SPK]) { + free(s_usb_dev.uac->as_ifc[UAC_SPK]); + } + if (s_usb_dev.uac->as_ifc[UAC_MIC]) { + free(s_usb_dev.uac->as_ifc[UAC_MIC]); + } + s_usb_dev.ifc[STREAM_UAC_SPK] = NULL; + s_usb_dev.ifc[STREAM_UAC_MIC] = NULL; + free(s_usb_dev.uac); + } + return ESP_FAIL; +} + +esp_err_t usb_streaming_connect_wait(size_t timeout_ms) +{ + if (!s_usb_dev.event_group_hdl || !(xEventGroupGetBits(s_usb_dev.event_group_hdl) & USB_HOST_INIT_DONE)) { + ESP_LOGW(TAG, "USB Streaming not started"); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGI(TAG, "Waiting USB Device Connection"); + EventBits_t bits = xEventGroupWaitBits(s_usb_dev.event_group_hdl, USB_STREAM_DEVICE_READY_BIT, false, false, pdMS_TO_TICKS(timeout_ms)); + if (!(bits & USB_STREAM_DEVICE_READY_BIT)) { + ESP_LOGW(TAG, "Waiting Device Connection, timeout"); + return ESP_ERR_TIMEOUT; + } + //lets wait a little more time for stream be handled + vTaskDelay(pdMS_TO_TICKS(20)); + ESP_LOGI(TAG, "USB Device Connected"); + return ESP_OK; +} + +esp_err_t usb_streaming_state_register(state_callback_t *cb, void *user_ptr) +{ + if (s_usb_dev.event_group_hdl) { + ESP_LOGW(TAG, "USB streaming is running, callback need register before start"); + return ESP_ERR_INVALID_STATE; + } + if (s_usb_dev.state_cb) { + ESP_LOGW(TAG, "Overwrite registered callback"); + } + if (cb) { + s_usb_dev.state_cb = cb; + s_usb_dev.state_cb_arg = user_ptr; + } + ESP_LOGI(TAG, "USB streaming callback register succeed"); + return ESP_OK; +} + +esp_err_t usb_streaming_stop(void) +{ + if (!s_usb_dev.event_group_hdl || !(xEventGroupGetBits(s_usb_dev.event_group_hdl) & USB_HOST_INIT_DONE)) { + ESP_LOGW(TAG, "USB Streaming not started"); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGI(TAG, "USB Streaming Stop"); + esp_err_t ret = _event_set_bits_wait_cleared(s_usb_dev.event_group_hdl, USB_HOST_TASK_KILL_BIT, pdMS_TO_TICKS(TIMEOUT_USER_COMMAND_MS)); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB Streaming Stop Failed"); + return ESP_ERR_TIMEOUT; + } + if (s_usb_dev.ctrl_urb) { + _usb_urb_free(s_usb_dev.ctrl_urb); + } + if (s_usb_dev.uvc) { + if (s_usb_dev.uvc->frame_size) { +#ifdef CONFIG_UVC_GET_CONFIG_DESC + free(s_usb_dev.uvc->frame_size); +#endif + } + if (s_usb_dev.uvc->vs_ifc) { + free(s_usb_dev.uvc->vs_ifc); + } + free(s_usb_dev.uvc); + } + if (s_usb_dev.uac) { + for (size_t i = 0; i < UAC_MAX; i++) { + if (s_usb_dev.uac->ringbuf_hdl[i]) { + vRingbufferDelete(s_usb_dev.uac->ringbuf_hdl[i]); + } + if (s_usb_dev.uac->as_ifc[i]) { + free(s_usb_dev.uac->as_ifc[i]); + } + if (s_usb_dev.uac->frame_size[i]) { +#ifdef CONFIG_UVC_GET_CONFIG_DESC + free(s_usb_dev.uac->frame_size[i]); +#endif + } + } + if (s_usb_dev.uac->mic_frame_buf) { + free(s_usb_dev.uac->mic_frame_buf); + } + free(s_usb_dev.uac); + } + vQueueDelete(s_usb_dev.queue_hdl); + vQueueDelete(s_usb_dev.stream_queue_hdl); + vEventGroupDelete(s_usb_dev.event_group_hdl); + vSemaphoreDelete(s_usb_dev.xfer_mutex_hdl); + vSemaphoreDelete(s_usb_dev.ctrl_smp_hdl); + memset(&s_usb_dev, 0, sizeof(_usb_device_t)); + //wait more time for task resources recovery + vTaskDelay(pdMS_TO_TICKS(WAITING_TASK_RESOURCE_RELEASE_MS)); + ESP_LOGI(TAG, "USB Streaming Stop Done"); + return ESP_OK; +} + +esp_err_t usb_streaming_control(usb_stream_t stream, stream_ctrl_t ctrl_type, void *ctrl_value) +{ + UVC_CHECK(stream < STREAM_MAX, "Invalid stream", ESP_ERR_INVALID_ARG); + UVC_CHECK(ctrl_type < CTRL_MAX, "Invalid control type", ESP_ERR_INVALID_ARG); + if (!s_usb_dev.event_group_hdl || !(xEventGroupGetBits(s_usb_dev.event_group_hdl) & USB_HOST_INIT_DONE)) { + ESP_LOGW(TAG, "USB stream not started"); + return ESP_ERR_INVALID_STATE; + } + UVC_CHECK(s_usb_dev.enabled[stream], "stream not configured/enabled", ESP_ERR_INVALID_STATE); + xSemaphoreTake(s_usb_dev.ctrl_smp_hdl, portMAX_DELAY); + esp_err_t ret = ESP_OK; + switch (ctrl_type) { + case CTRL_SUSPEND: + case CTRL_RESUME: + ret = usb_stream_control(stream, ctrl_type); + break; + case CTRL_UAC_MUTE: + case CTRL_UAC_VOLUME: + ret = uac_feature_control(stream, ctrl_type, ctrl_value); + break; + default: + break; + } + xSemaphoreGive(s_usb_dev.ctrl_smp_hdl); + //Add delay to avoid too frequent control + vTaskDelay(pdMS_TO_TICKS(WAITING_DEVICE_CONTROL_APPLY_MS)); + return ret; +} + +esp_err_t uac_spk_streaming_write(void *data, size_t data_bytes, size_t timeout_ms) +{ + UVC_CHECK(data, "data is NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(data_bytes, "data_bytes is 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(s_usb_dev.enabled[STREAM_UAC_SPK], "spk stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uac->ringbuf_hdl[UAC_SPK] != NULL, "spk ringbuf not created", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uac->as_ifc[UAC_SPK] && !s_usb_dev.uac->as_ifc[UAC_SPK]->not_found, "spk interface not found", ESP_ERR_NOT_FOUND); + + size_t remind_timeout = timeout_ms; + bool check_valid = false; + esp_err_t ret = ESP_OK; + do { + if (xEventGroupGetBits(s_usb_dev.event_group_hdl) & UAC_SPK_STREAM_RUNNING) { + check_valid = true; + break; + } else { + if (remind_timeout >= portTICK_PERIOD_MS) { + remind_timeout -= portTICK_PERIOD_MS; + vTaskDelay(1); + } else { + remind_timeout = 0; + break; + } + } + } while (remind_timeout > 0 && !check_valid); + if (!check_valid) { + ESP_LOGD(TAG, "write timeout: spk stream not ready"); + return ESP_ERR_TIMEOUT; + } + ret = _ring_buffer_push(s_usb_dev.uac->ringbuf_hdl[UAC_SPK], data, data_bytes, remind_timeout); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "write timeout: spk ringbuf full"); + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +esp_err_t uac_mic_streaming_read(void *buf, size_t buf_size, size_t *data_bytes, size_t timeout_ms) +{ + UVC_CHECK(buf, "buf is NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(buf_size, "buf_size is 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(data_bytes, "data_bytes is NULL", ESP_ERR_INVALID_ARG); + UVC_CHECK(s_usb_dev.enabled[STREAM_UAC_MIC], "mic stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uac->ringbuf_hdl[UAC_MIC] != NULL, "mic ringbuf not created", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uac->as_ifc[UAC_MIC] && !s_usb_dev.uac->as_ifc[UAC_MIC]->not_found, "mic interface not found", ESP_ERR_NOT_FOUND); + + size_t remind_timeout = timeout_ms; + bool check_valid = false; + esp_err_t ret = ESP_OK; + do { + if (xEventGroupGetBits(s_usb_dev.event_group_hdl) & UAC_MIC_STREAM_RUNNING) { + check_valid = true; + break; + } else { + if (remind_timeout >= portTICK_PERIOD_MS) { + remind_timeout -= portTICK_PERIOD_MS; + vTaskDelay(1); + } else { + remind_timeout = 0; + break; + } + } + } while (remind_timeout > 0 && !check_valid); + if (!check_valid) { + ESP_LOGD(TAG, "spk stream not ready"); + return ESP_ERR_TIMEOUT; + } + ret = _ring_buffer_pop(s_usb_dev.uac->ringbuf_hdl[UAC_MIC], buf, buf_size, data_bytes, remind_timeout); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "read timeout: mic ringbuf empty"); + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +esp_err_t uac_frame_size_list_get(usb_stream_t stream, uac_frame_size_t *frame_list, size_t *list_size, size_t *cur_index) +{ + UVC_CHECK(stream == STREAM_UAC_SPK || stream == STREAM_UAC_MIC, "Invalid stream", ESP_ERR_INVALID_ARG); + UVC_CHECK(s_usb_dev.enabled[stream], "uac stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + if (!(s_usb_dev.ifc[stream] && !s_usb_dev.ifc[stream]->not_found)) { + if (list_size) { + *list_size = 0; + } + return ESP_ERR_INVALID_STATE; + } + _uac_internal_stream_t uac_stream = (stream == STREAM_UAC_SPK) ? UAC_SPK : UAC_MIC; + UVC_ENTER_CRITICAL(); + uint8_t frame_num = s_usb_dev.uac->frame_num[uac_stream]; + if (list_size) { + *list_size = frame_num; + } + if (frame_list && frame_num) { + memcpy(&frame_list[0], s_usb_dev.uac->frame_size[uac_stream], sizeof(uac_frame_size_t) * frame_num); + } + if (cur_index) { + *cur_index = s_usb_dev.uac->frame_index[uac_stream]-1; + } + UVC_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t uac_frame_size_reset(usb_stream_t stream, uint8_t ch_num, uint16_t bit_resolution, uint32_t samples_frequence) +{ + UVC_CHECK(stream == STREAM_UAC_SPK || stream == STREAM_UAC_MIC, "Invalid stream", ESP_ERR_INVALID_ARG); + UVC_CHECK(ch_num != 0, "ch_num can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(bit_resolution != 0, "bit_resolution can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(samples_frequence != 0, "samples_frequence can't 0", ESP_ERR_INVALID_ARG); + UVC_CHECK(s_usb_dev.enabled[stream], "uac stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.ifc[stream] && !s_usb_dev.ifc[stream]->not_found, "uac interface not found", ESP_ERR_INVALID_STATE); + if ((xEventGroupGetBits(s_usb_dev.event_group_hdl) & s_usb_dev.ifc[stream]->evt_bit)) { + ESP_LOGE(TAG, "%s stream running, please suspend before frame_size_reset", s_usb_dev.ifc[stream]->name); + return ESP_ERR_INVALID_STATE; + } + _uac_internal_stream_t uac_stream = (stream == STREAM_UAC_SPK) ? UAC_SPK : UAC_MIC; + bool frame_found = false; + uint8_t frame_index = 0; + UVC_ENTER_CRITICAL(); + size_t frame_num = s_usb_dev.uac->frame_num[uac_stream]; + uac_frame_size_t *frame_size = s_usb_dev.uac->frame_size[uac_stream]; + for (size_t i = 0; i < frame_num; i++) { + if (frame_size[i].ch_num == ch_num && frame_size[i].bit_resolution == bit_resolution + && (frame_size[i].samples_frequence == samples_frequence || (samples_frequence >= frame_size[i].samples_frequence_min + && samples_frequence <= frame_size[i].samples_frequence_max))) { + frame_index = i+1; + frame_found = true; + break; + } + } + if (frame_found && s_usb_dev.uac->ch_num[uac_stream] == ch_num + && s_usb_dev.uac->bit_resolution[uac_stream] == bit_resolution + && s_usb_dev.uac->samples_frequence[uac_stream] == samples_frequence) { + UVC_EXIT_CRITICAL(); + ESP_LOGW(TAG, "audio frame size not changed"); + return ESP_OK; + } + UVC_EXIT_CRITICAL(); + + if (frame_found) { + //Add a delay to avoid the situation that the device is not ready + vTaskDelay(pdMS_TO_TICKS(ACTIVE_DEBOUNCE_TIME_MS)); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + if (uac_stream == UAC_MIC) { + UVC_ENTER_CRITICAL(); + s_usb_dev.uac->frame_index[UAC_MIC] = frame_index; + //for mic support a range of samples_frequence + s_usb_dev.uac->frame_size[UAC_MIC][frame_index-1].samples_frequence = samples_frequence; + s_usb_dev.uac->samples_frequence[UAC_MIC] = samples_frequence; + UVC_EXIT_CRITICAL(); + //change users configuration + s_usb_dev.uac_cfg.mic_ch_num = ch_num; + s_usb_dev.uac_cfg.mic_bit_resolution = bit_resolution; + s_usb_dev.uac_cfg.mic_samples_frequence = samples_frequence; + } else { + UVC_ENTER_CRITICAL(); + s_usb_dev.uac->frame_index[UAC_SPK] = frame_index; + //for spk support a range of samples_frequence + s_usb_dev.uac->frame_size[UAC_SPK][frame_index-1].samples_frequence = samples_frequence; + s_usb_dev.uac->samples_frequence[UAC_SPK] = samples_frequence; + UVC_EXIT_CRITICAL(); + //change users configuration + s_usb_dev.uac_cfg.spk_ch_num = ch_num; + s_usb_dev.uac_cfg.spk_bit_resolution = bit_resolution; + s_usb_dev.uac_cfg.spk_samples_frequence = samples_frequence; + } + } else { + ESP_LOGE(TAG, "%s frame size not found, ch_num = %u, bit_resolution = %u, samples_frequence = %"PRIu32, s_usb_dev.ifc[stream]->name, ch_num, bit_resolution, samples_frequence); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGI(TAG, "%s frame size change, ch_num = %u, bit_resolution = %u, samples_frequence = %"PRIu32, s_usb_dev.ifc[stream]->name, ch_num, bit_resolution, samples_frequence); + return ESP_OK; +} + +esp_err_t uvc_frame_size_list_get(uvc_frame_size_t *frame_list, size_t *list_size, size_t *cur_index) +{ + UVC_CHECK(s_usb_dev.enabled[STREAM_UVC], "uvc stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + if (!(s_usb_dev.uvc->vs_ifc && !s_usb_dev.uvc->vs_ifc->not_found)) { + if (list_size) { + *list_size = 0; + } + return ESP_ERR_INVALID_STATE; + } + UVC_ENTER_CRITICAL(); + uint8_t frame_num = s_usb_dev.uvc->frame_num; + if (list_size) { + *list_size = frame_num; + } + if (frame_list && frame_num) { + memcpy(&frame_list[0], s_usb_dev.uvc->frame_size, sizeof(uvc_frame_size_t) * frame_num); + } + if (cur_index) { + *cur_index = frame_num==1?0:(s_usb_dev.uvc->frame_index - 1); + } + UVC_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t uvc_frame_size_reset(uint16_t frame_width, uint16_t frame_height, uint32_t frame_interval) +{ + UVC_CHECK(s_usb_dev.enabled[STREAM_UVC], "uvc stream not config", ESP_ERR_INVALID_STATE); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uvc->vs_ifc && !s_usb_dev.uvc->vs_ifc->not_found, "uvc interface not found", ESP_ERR_INVALID_STATE); + UVC_CHECK(s_usb_dev.uvc->frame_size != NULL, "uvc frame size list not found", ESP_ERR_INVALID_STATE); + //Add a delay to avoid the situation that the device is not ready + vTaskDelay(pdMS_TO_TICKS(ACTIVE_DEBOUNCE_TIME_MS)); + UVC_CHECK(_usb_device_get_state() == STATE_DEVICE_ACTIVE, "USB Device not active", ESP_ERR_INVALID_STATE); + if ((xEventGroupGetBits(s_usb_dev.event_group_hdl) & s_usb_dev.ifc[STREAM_UVC]->evt_bit)) { + ESP_LOGE(TAG, "%s stream running, please suspend before frame_size_reset", s_usb_dev.ifc[STREAM_UVC]->name); + return ESP_ERR_INVALID_STATE; + } + int frame_found = -1; + if (frame_width && frame_height) { + bool frame_reset = false; + UVC_ENTER_CRITICAL(); + size_t frame_num = s_usb_dev.uvc->frame_num; + uvc_frame_size_t *frame_size = s_usb_dev.uvc->frame_size; + for (int i = 0; i < frame_num; i++) { + if ((frame_width == FRAME_RESOLUTION_ANY || frame_width == frame_size[i].width) + && ( frame_height == FRAME_RESOLUTION_ANY || frame_height == frame_size[i].height)) { + if (i + 1 != s_usb_dev.uvc->frame_index) { + //change current configuration + s_usb_dev.uvc->frame_index = i + 1; + s_usb_dev.uvc->frame_height = frame_size[i].height; + s_usb_dev.uvc->frame_width = frame_size[i].width; + //change user configuration + s_usb_dev.uvc_cfg.frame_height = frame_size[i].height; + s_usb_dev.uvc_cfg.frame_width = frame_size[i].width; + frame_reset = true; + } + frame_found = i; + break; + } + } + UVC_EXIT_CRITICAL(); + if (frame_found == -1) { + ESP_LOGE(TAG, "frame size not found, width = %d, height = %d", frame_width, frame_height); + return ESP_ERR_NOT_FOUND; + } else if (frame_reset == false) { + ESP_LOGW(TAG, "frame size not changed, width = %d, height = %d", frame_width, frame_height); + return ESP_OK; + } + } + uint32_t final_interval = frame_interval; + if (frame_interval) { + if (frame_found != -1) { + final_interval = 0; + UVC_ENTER_CRITICAL(); + uvc_frame_size_t *frame_size = s_usb_dev.uvc->frame_size; + if (frame_size[frame_found].interval_step) { + // continues interval + if (frame_interval >= frame_size[frame_found].interval_min && frame_interval <= frame_size[frame_found].interval_max) { + for (uint32_t i = frame_size[frame_found].interval_min; i < frame_size[frame_found].interval_max; i+=frame_size[frame_found].interval_step) { + if (frame_interval >= i && frame_interval < (i + frame_size[frame_found].interval_step)) { + final_interval = i; + } + } + } + } else { + // fixed interval + if (frame_interval == frame_size[frame_found].interval_min) { + final_interval = frame_size[frame_found].interval_min; + } else if (frame_interval == frame_size[frame_found].interval_max) { + final_interval = frame_size[frame_found].interval_max; + } else if (frame_interval == frame_size[frame_found].interval) { + final_interval = frame_size[frame_found].interval; + } + } + UVC_EXIT_CRITICAL(); + if (final_interval == 0) { + UVC_ENTER_CRITICAL(); + final_interval = frame_size[frame_found].interval; + UVC_EXIT_CRITICAL(); + ESP_LOGW(TAG, "frame interval %" PRIu32 " not support, using = %" PRIu32, frame_interval, final_interval); + } + } else { + ESP_LOGW(TAG, "frame interval force to %" PRIu32, final_interval); + } + // else User should make sure the frame_interval is between frame_interval_min and frame_interval_max + UVC_ENTER_CRITICAL(); + s_usb_dev.uvc->frame_interval = final_interval; + UVC_EXIT_CRITICAL(); + //change user configuration + s_usb_dev.uvc_cfg.frame_interval = final_interval; + } + ESP_LOGI(TAG, "UVC frame size reset, width = %d, height = %d, interval = %"PRIu32, frame_width, frame_height, final_interval); + return ESP_OK; +} diff --git a/lib/ESP32_USB_STREAM/src/original/usb_stream_descriptor.h b/lib/ESP32_USB_STREAM/src/original/usb_stream_descriptor.h new file mode 100644 index 0000000..a58a681 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_stream_descriptor.h @@ -0,0 +1,395 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UAC_VERSION_1 0x0100 +#define UAC_VERSION_2 0x0200 +#define UAC_FORMAT_TYPEI 0x0001 + +typedef enum { + CS_INTERFACE_DESC = 0x24, + CS_ENDPOINT_DESC = 0x25, +} descriptor_types_t; + +typedef enum { + VIDEO_SUBCLASS_UNDEFINED = 0x00, + VIDEO_SUBCLASS_CONTROL = 0x01, + VIDEO_SUBCLASS_STREAMING = 0x02, + VIDEO_SUBCLASS_INTERFACE_COLLECTION = 0x03, +} video_subclass_type_t; + +typedef enum { + AUDIO_SUBCLASS_UNDEFINED = 0x00, + AUDIO_SUBCLASS_CONTROL = 0x01, + AUDIO_SUBCLASS_STREAMING = 0x02, + AUDIO_SUBCLASS_MIDI_STREAMING = 0x03, +} audio_subclass_type_t; + +typedef enum { + AUDIO_FUNC_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_FUNC_PROTOCOL_CODE_V2 = 0x20, +} audio_function_protocol_code_t; + +typedef enum { + AUDIO_TERM_TYPE_USB_UNDEFINED = 0x0100, + AUDIO_TERM_TYPE_USB_STREAMING = 0x0101, + AUDIO_TERM_TYPE_USB_VENDOR_SPEC = 0x01FF, +} audio_terminal_type_t; + +typedef enum { + AUDIO_TERM_TYPE_IN_UNDEFINED = 0x0200, + AUDIO_TERM_TYPE_IN_GENERIC_MIC = 0x0201, + AUDIO_TERM_TYPE_IN_DESKTOP_MIC = 0x0202, + AUDIO_TERM_TYPE_IN_PERSONAL_MIC = 0x0203, + AUDIO_TERM_TYPE_IN_OMNI_MIC = 0x0204, + AUDIO_TERM_TYPE_IN_ARRAY_MIC = 0x0205, + AUDIO_TERM_TYPE_IN_PROC_ARRAY_MIC = 0x0206, +} audio_terminal_input_type_t; + +typedef enum { + AUDIO_TERM_TYPE_OUT_UNDEFINED = 0x0300, + AUDIO_TERM_TYPE_OUT_GENERIC_SPEAKER = 0x0301, + AUDIO_TERM_TYPE_OUT_HEADPHONES = 0x0302, + AUDIO_TERM_TYPE_OUT_HEAD_MNT_DISP_AUIDO = 0x0303, + AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER = 0x0304, + AUDIO_TERM_TYPE_OUT_ROOM_SPEAKER = 0x0305, + AUDIO_TERM_TYPE_OUT_COMMUNICATION_SPEAKER = 0x0306, + AUDIO_TERM_TYPE_OUT_LOW_FRQ_EFFECTS_SPEAKER = 0x0307, + AUDIO_TERM_TYPE_HEADSET = 0x0402, +} audio_terminal_output_type_t; + +typedef enum { + AUDIO_FEATURE_CONTROL_MUTE = 0x0001, + AUDIO_FEATURE_CONTROL_VOLUME = 0x0002, + AUDIO_FEATURE_CONTROL_BASS = 0x0004, + AUDIO_FEATURE_CONTROL_MID = 0x0008, + AUDIO_FEATURE_CONTROL_TREBLE = 0x0010, + AUDIO_FEATURE_CONTROL_GRAPHIC_EQUALIZER = 0x0020, + AUDIO_FEATURE_CONTROL_AUTOMATIC_GAIN = 0x0040, + AUDIO_FEATURE_CONTROL_DEALY = 0x0080, +} audio_feature_unit_pos_t; + +typedef enum { + AUDIO_EP_CONTROL_UNDEF = 0x00, + AUDIO_EP_CONTROL_SAMPLING_FEQ = 0x01, + AUDIO_EP_CONTROL_PITCH = 0x02, +} audio_ep_ctrl_pos_t; + +typedef enum { + VIDEO_CS_ITF_VC_UNDEFINED = 0x00, + VIDEO_CS_ITF_VC_HEADER = 0x01, + VIDEO_CS_ITF_VC_INPUT_TERMINAL = 0x02, + VIDEO_CS_ITF_VC_OUTPUT_TERMINAL = 0x03, + VIDEO_CS_ITF_VC_SELECTOR_UNIT = 0x04, + VIDEO_CS_ITF_VC_PROCESSING_UNIT = 0x05, + VIDEO_CS_ITF_VC_EXTENSION_UNIT = 0x06, + VIDEO_CS_ITF_VC_ENCODING_UNIT = 0x07, + VIDEO_CS_ITF_VC_MAX, +} video_cs_vc_interface_subtype_t; + +typedef enum { + VIDEO_CS_ITF_VS_UNDEFINED = 0x00, + VIDEO_CS_ITF_VS_INPUT_HEADER = 0x01, + VIDEO_CS_ITF_VS_OUTPUT_HEADER = 0x02, + VIDEO_CS_ITF_VS_STILL_IMAGE_FRAME = 0x03, + VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED = 0x04, + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED = 0x05, + VIDEO_CS_ITF_VS_FORMAT_MJPEG = 0x06, + VIDEO_CS_ITF_VS_FRAME_MJPEG = 0x07, + VIDEO_CS_ITF_VS_FORMAT_MPEG2TS = 0x0A, + VIDEO_CS_ITF_VS_FORMAT_DV = 0x0C, + VIDEO_CS_ITF_VS_COLORFORMAT = 0x0D, + VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED = 0x10, + VIDEO_CS_ITF_VS_FRAME_FRAME_BASED = 0x11, + VIDEO_CS_ITF_VS_FORMAT_STREAM_BASED = 0x12, + VIDEO_CS_ITF_VS_FORMAT_H264 = 0x13, + VIDEO_CS_ITF_VS_FRAME_H264 = 0x14, + VIDEO_CS_ITF_VS_FORMAT_H264_SIMULCAST = 0x15, + VIDEO_CS_ITF_VS_FORMAT_VP8 = 0x16, + VIDEO_CS_ITF_VS_FRAME_VP8 = 0x17, + VIDEO_CS_ITF_VS_FORMAT_VP8_SIMULCAST = 0x18, +} video_cs_vs_interface_subtype_t; + +typedef enum { + AUDIO_CS_AC_INTERFACE_AC_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AC_INTERFACE_HEADER = 0x01, + AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL = 0x02, + AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL = 0x03, + AUDIO_CS_AC_INTERFACE_MIXER_UNIT = 0x04, + AUDIO_CS_AC_INTERFACE_SELECTOR_UNIT = 0x05, + AUDIO_CS_AC_INTERFACE_FEATURE_UNIT = 0x06, + AUDIO_CS_AC_INTERFACE_EFFECT_UNIT = 0x07, + AUDIO_CS_AC_INTERFACE_PROCESSING_UNIT = 0x08, + AUDIO_CS_AC_INTERFACE_EXTENSION_UNIT = 0x09, + AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE = 0x0A, + AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR = 0x0B, + AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER = 0x0C, + AUDIO_CS_AC_INTERFACE_SAMPLE_RATE_CONVERTER = 0x0D, +} audio_cs_ac_interface_subtype_t; + +typedef enum { + AUDIO_CS_AS_INTERFACE_AS_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AS_INTERFACE_AS_GENERAL = 0x01, + AUDIO_CS_AS_INTERFACE_FORMAT_TYPE = 0x02, + AUDIO_CS_AS_INTERFACE_ENCODER = 0x03, + AUDIO_CS_AS_INTERFACE_DECODER = 0x04, +} audio_cs_as_interface_subtype_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; +} desc_header_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; +} USB_DESC_ATTR ifc_assoc_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdUVC; + uint16_t wTotalLength; + uint32_t dwClockFrequency; + uint8_t bFunctionProtocol; + uint8_t bInCollection; + uint8_t baInterfaceNr; +} USB_DESC_ATTR vc_interface_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bFunctionProtocol; + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; + uint8_t bmaControls; +} USB_DESC_ATTR vs_interface_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t wMaxTransferSize; +} USB_DESC_ATTR class_specific_endpoint_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t bmFlags; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} USB_DESC_ATTR vs_format_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeigh; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwMaxVideoFrameBufSize; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + union { + uint32_t dwFrameInterval; + struct { + uint32_t dwMinFrameInterval; + uint32_t dwMaxFrameInterval; + uint32_t dwFrameIntervalStep; + }; + }; +} USB_DESC_ATTR vs_frame_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdADC; + uint16_t wTotalLength; + uint8_t bInCollection; + uint8_t baInterfaceNr[]; +} USB_DESC_ATTR ac_interface_header_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bNrChannels; + uint16_t wChannelConfig; + uint8_t iChannelNames; + uint8_t iTerminal; +} USB_DESC_ATTR ac_interface_input_terminal_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} USB_DESC_ATTR ac_interface_output_terminal_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bSourceID; + uint8_t bControlSize; + uint8_t bmaControls[2]; + uint8_t iFeature; +} USB_DESC_ATTR ac_interface_feature_unit_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalLink; + uint8_t bDelay; + uint16_t wFormatTag; +} USB_DESC_ATTR as_interface_header_desc_t; + +/// AUDIO Type I Format +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatType; + uint8_t bNrChannels; + uint8_t bSubframeSize; + uint8_t bBitResolution; + uint8_t bSamFreqType; + uint8_t tSamFreq[3]; +} USB_DESC_ATTR as_interface_type_I_format_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmAttributes; + uint8_t bLockDelayUnits; + uint16_t wLockDelay; +} USB_DESC_ATTR as_cs_ep_desc_t; + +#ifdef SUPPORT_UAC_V2 +/* UAC v2.0 is incompatible with UAC v1.0 */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdADC; + uint8_t bCategory; + uint16_t wTotalLength; + uint8_t bmControls; +} USB_DESC_ATTR ac2_interface_header_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bCSourceID; + uint8_t bNrChannels; + uint32_t bmChannelConfig; + uint16_t bmControls; + uint8_t iTerminal; +} USB_DESC_ATTR ac2_interface_input_terminal_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bUnitID; + uint8_t bSourceID; + struct TU_ATTR_PACKED { + uint32_t bmaControls; + } controls[2]; +} USB_DESC_ATTR ac2_interface_feature_unit_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalLink; + uint8_t bmControls; + uint8_t bFormatType; + uint32_t bmFormats; + uint8_t bNrChannels; + uint32_t bmChannelConfig; + uint8_t iChannelNames; +} USB_DESC_ATTR as2_interface_header_desc_t; + +/// AUDIO Type I Format +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatType; + uint8_t bSubslotSize; + uint8_t bBitResolution; +} USB_DESC_ATTR as2_interface_type_I_format_desc_t; +#endif + +void parse_ac_header_desc(const uint8_t *buff, uint16_t *bcdADC, uint8_t *intf_num); +void parse_ac_input_desc(const uint8_t *buff, uint8_t *terminal_idx, uint16_t *terminal_type); +void parse_ac_output_desc(const uint8_t *buff, uint8_t *terminal_idx, uint16_t *terminal_type); +void parse_ac_feature_desc(const uint8_t *buff, uint8_t *source_idx, uint8_t *feature_unit_idx, uint8_t *volume_ch, uint8_t *mute_ch); +void parse_as_general_desc(const uint8_t *buff, uint8_t *source_idx, uint16_t *format_tag); +void parse_as_type_desc(const uint8_t *buff, uint8_t *channel_num, uint8_t *bit_resolution, uint8_t *freq_type, const uint8_t **pp_samfreq); + +void print_cfg_desc(const uint8_t *buff); +void print_assoc_desc(const uint8_t *buff); +void print_intf_desc(const uint8_t *buff); +void parse_ep_desc(const uint8_t *buff, uint16_t *ep_mps, uint8_t *ep_addr, uint8_t *ep_attr); +void parse_vs_format_mjpeg_desc(const uint8_t *buff, uint8_t *format_idx, uint8_t *frame_num); +void parse_vs_frame_mjpeg_desc(const uint8_t *buff, uint8_t *frame_idx, uint16_t *width, uint16_t *heigh, uint8_t *interval_type, const uint32_t **pp_interval, uint32_t *dflt_interval); +void print_uvc_header_desc(const uint8_t *buff, uint8_t sub_class); +void print_device_descriptor(const uint8_t *buff); +void print_ep_desc(const uint8_t *buff); +#ifdef __cplusplus +} +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/usb_stream_sysview.h b/lib/ESP32_USB_STREAM/src/original/usb_stream_sysview.h new file mode 100644 index 0000000..37e6253 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usb_stream_sysview.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#if CONFIG_APPTRACE_SV_ENABLE && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) +#include "SEGGER_SYSVIEW.h" + +#define SYSVIEW_MARKER_DFT_PIPE_HANDLE_ID 10 +#define SYSVIEW_MARKER_UVC_PIPE_HANDLE_ID 11 +#define SYSVIEW_MARKER_UAC_MIC_PIPE_HANDLE_ID 12 +#define SYSVIEW_MARKER_UAC_SPK_PIPE_HANDLE_ID 13 +#define SYSVIEW_DFLT_PIPE_XFER_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_MARKER_DFT_PIPE_HANDLE_ID) +#define SYSVIEW_DFLT_PIPE_XFER_STOP() SEGGER_SYSVIEW_OnUserStop(SYSVIEW_MARKER_DFT_PIPE_HANDLE_ID) +#define SYSVIEW_UVC_PIPE_HANDLE_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_MARKER_UVC_PIPE_HANDLE_ID) +#define SYSVIEW_UVC_PIPE_HANDLE_STOP() SEGGER_SYSVIEW_OnUserStop(SYSVIEW_MARKER_UVC_PIPE_HANDLE_ID) +#define SYSVIEW_UAC_MIC_PIPE_HANDLE_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_MARKER_UAC_MIC_PIPE_HANDLE_ID) +#define SYSVIEW_UAC_MIC_PIPE_HANDLE_STOP() SEGGER_SYSVIEW_OnUserStop(SYSVIEW_MARKER_UAC_MIC_PIPE_HANDLE_ID) +#define SYSVIEW_UAC_SPK_PIPE_HANDLE_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_MARKER_UAC_SPK_PIPE_HANDLE_ID) +#define SYSVIEW_UAC_SPK_PIPE_HANDLE_STOP() SEGGER_SYSVIEW_OnUserStop(SYSVIEW_MARKER_UAC_SPK_PIPE_HANDLE_ID) + +#else +#define SYSVIEW_DFLT_PIPE_XFER_START() +#define SYSVIEW_DFLT_PIPE_XFER_STOP() +#define SYSVIEW_UVC_PIPE_HANDLE_START() +#define SYSVIEW_UVC_PIPE_HANDLE_STOP() +#define SYSVIEW_UAC_MIC_PIPE_HANDLE_START() +#define SYSVIEW_UAC_MIC_PIPE_HANDLE_STOP() +#define SYSVIEW_UAC_SPK_PIPE_HANDLE_START() +#define SYSVIEW_UAC_SPK_PIPE_HANDLE_STOP() + +#endif diff --git a/lib/ESP32_USB_STREAM/src/original/usbh.h b/lib/ESP32_USB_STREAM/src/original/usbh.h new file mode 100644 index 0000000..0fafea2 --- /dev/null +++ b/lib/ESP32_USB_STREAM/src/original/usbh.h @@ -0,0 +1,526 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hcd.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ Types -------------------------------------------------------- + +// ----------------------- Handles ------------------------- + +/** + * @brief Handle of a allocated endpoint + */ +typedef struct usbh_ep_handle_s *usbh_ep_handle_t; + +// ----------------------- Events -------------------------- + +typedef enum { + USBH_EVENT_DEV_NEW, /**< A new device has been enumerated and added to the device pool */ + USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ + USBH_EVENT_DEV_ALL_FREE, /**< All devices have been freed */ +} usbh_event_t; + +/** + * @brief Endpoint events + * + * @note Optimization: Keep this identical to hcd_pipe_event_t + */ +typedef enum { + USBH_EP_EVENT_NONE, /**< The EP has no events (used to indicate no events when polling) */ + USBH_EP_EVENT_URB_DONE, /**< The EP has completed a URB. The URB can be dequeued */ + USBH_EP_EVENT_ERROR_XFER, /**< The EP encountered excessive errors when transferring a URB i.e., three three consecutive transaction errors (e.g., no ACK, bad CRC etc) */ + USBH_EP_EVENT_ERROR_URB_NOT_AVAIL, /**< The EP tried to execute a transfer but no URB was available */ + USBH_EP_EVENT_ERROR_OVERFLOW, /**< The EP received more data than requested. Usually a Packet babble error (i.e., an IN packet has exceeded the EP's MPS) */ + USBH_EP_EVENT_ERROR_STALL, /**< EP received a STALL response */ +} usbh_ep_event_t; + +/** + * @brief Hub driver events for the USBH + * + * These events as passed by the Hub driver to the USBH via usbh_hub_pass_event() + * + * USBH_HUB_EVENT_PORT_ERROR: + * - The port has encountered an error (such as a sudden disconnection). The device connected to that port is no longer valid. + * - The USBH should: + * - Trigger a USBH_EVENT_DEV_GONE + * - Prevent further transfers to the device + * - Trigger the device's cleanup if it is already closed + * - When the last client closes the device via usbh_dev_close(), free the device object and issue a USBH_HUB_REQ_PORT_RECOVER request + * + * USBH_HUB_EVENT_PORT_DISABLED: + * - A previous USBH_HUB_REQ_PORT_DISABLE has completed. + * - The USBH should free the device object + */ +typedef enum { + USBH_HUB_EVENT_PORT_ERROR, /**< The port has encountered an error (such as a sudden disconnection). The device + connected to that port should be marked gone. */ + USBH_HUB_EVENT_PORT_DISABLED, /**< Previous USBH_HUB_REQ_PORT_DISABLE request completed */ +} usbh_hub_event_t; + +// ------------------ Requests/Commands -------------------- + +/** + * @brief Hub driver requests + * + * Various requests of the Hub driver that the USBH can make. + */ +typedef enum { + USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device + has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ + USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone + device has been freed). */ +} usbh_hub_req_t; + +/** + * @brief Endpoint commands + * + * @note Optimization: Keep this identical to hcd_pipe_cmd_t + */ +typedef enum { + USBH_EP_CMD_HALT, /**< Halt an active endpoint. Any currently executing URB will be canceled. Enqueued URBs are left untouched */ + USBH_EP_CMD_FLUSH, /**< Can only be called when halted. Will cause all enqueued URBs to be canceled */ + USBH_EP_CMD_CLEAR, /**< Causes a halted endpoint to become active again. Any enqueued URBs will being executing.*/ +} usbh_ep_cmd_t; + +// ---------------------- Callbacks ------------------------ + +/** + * @brief Callback used to indicate completion of control transfers submitted usbh_dev_submit_ctrl_urb() + * @note This callback is called from within usbh_process() + */ +typedef void (*usbh_ctrl_xfer_cb_t)(usb_device_handle_t dev_hdl, urb_t *urb, void *arg); + +/** + * @brief Callback used to indicate that the USBH has an event + * + * @note This callback is called from within usbh_process() + * @note On a USBH_EVENT_DEV_ALL_FREE event, the dev_hdl argument is set to NULL + */ +typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg); + +/** + * @brief Callback used by the USBH to request actions from the Hub driver + * + * The Hub Request Callback allows the USBH to request the Hub actions on a particular port. Conversely, the Hub driver + * will indicate completion of some of these requests to the USBH via the usbh_hub_event() funtion. + */ +typedef void (*usbh_hub_req_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); + +/** + * @brief Callback used to indicate an event on an endpoint + * + * Return whether to yield or not if called from an ISR. Always return false if not called from an ISR + */ +typedef bool (*usbh_ep_cb_t)(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *arg, bool in_isr); + +// ----------------------- Objects ------------------------- + +/** + * @brief Configuration for an endpoint being allocated using usbh_ep_alloc() + */ +typedef struct { + uint8_t bInterfaceNumber; /**< Interface number */ + uint8_t bAlternateSetting; /**< Alternate setting number of the interface */ + uint8_t bEndpointAddress; /**< Endpoint address */ + usbh_ep_cb_t ep_cb; /**< Endpoint event callback */ + void *ep_cb_arg; /**< Endpoint callback argument */ + void *context; /**< Endpoint context */ +} usbh_ep_config_t; + +/** + * @brief USBH configuration used in usbh_install() + */ +typedef struct { + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ + usbh_ctrl_xfer_cb_t ctrl_xfer_cb; /**< Control transfer callback */ + void *ctrl_xfer_cb_arg; /**< Control transfer callback argument */ + usbh_event_cb_t event_cb; /**< USBH event callback */ + void *event_cb_arg; /**< USBH event callback argument */ +} usbh_config_t; + +// ------------------------------------------------- USBH Functions ---------------------------------------------------- + +/** + * @brief Installs the USBH driver + * + * - This function will internally install the HCD + * - This must be called before calling any Hub driver functions + * + * @note Before calling this function, the Host Controller must already be un-clock gated and reset. The USB PHY + * (internal or external, and associated GPIOs) must already be configured. + * @param usbh_config USBH driver configuration + * @return esp_err_t + */ +esp_err_t usbh_install(const usbh_config_t *usbh_config); + +/** + * @brief Uninstall the USBH driver + * + * - This function will uninstall the HCD + * - The Hub driver must be uninstalled before calling this function + * + * @note This function will simply free the resources used by the USBH. The underlying Host Controller and USB PHY will + * not be disabled. + * @return esp_err_t + */ +esp_err_t usbh_uninstall(void); + +/** + * @brief USBH processing function + * + * - USBH processing function that must be called repeatedly to process USBH events + * - If blocking, the caller can block until the proc_req_cb() is called with USB_PROC_REQ_SOURCE_USBH as the request + * source. The USB_PROC_REQ_SOURCE_USBH source indicates that this function should be called. + * + * @note This function can block + * @return esp_err_t + */ +esp_err_t usbh_process(void); + +/** + * @brief Get the current number of devices + * + * @note This function can block + * @param[out] num_devs_ret Current number of devices + * @return esp_err_t + */ +esp_err_t usbh_num_devs(int *num_devs_ret); + +// ------------------------------------------------ Device Functions --------------------------------------------------- + +// --------------------- Device Pool ----------------------- + +/** + * @brief Fill list with address of currently connected devices + * + * - This function fills the provided list with the address of current connected devices + * - Device address can then be used in usbh_dev_open() + * - If there are more devices than the list_len, this function will only fill + * up to list_len number of devices. + * + * @param[in] list_len Length of empty list + * @param[inout] dev_addr_list Empty list to be filled + * @param[out] num_dev_ret Number of devices filled into list + * @return esp_err_t + */ +esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); + +/** + * @brief Open a device by address + * + * A device must be opened before it can be used + * + * @param[in] dev_addr Device address + * @param[out] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); + +/** + * @brief CLose a device + * + * Device can be opened by calling usbh_dev_open() + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl); + +/** + * @brief Mark that all devices should be freed at the next possible opportunity + * + * A device marked as free will not be freed until the last client using the device has called usbh_dev_close() + * + * @return + * - ESP_OK: There were no devices to free to begin with. Current state is all free + * - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed") + */ +esp_err_t usbh_dev_mark_all_free(void); + +// ------------------- Single Device ---------------------- + +/** + * @brief Get a device's address + * + * @note Can be called without opening the device + * + * @param[in] dev_hdl Device handle + * @param[out] dev_addr Device's address + * @return esp_err_t + */ +esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr); + +/** + * @brief Get a device's information + * + * @note This function can block + * @param[in] dev_hdl Device handle + * @param[out] dev_info Device information + * @return esp_err_t + */ +esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info); + +/** + * @brief Get a device's device descriptor + * + * - The device descriptor is cached when the device is created by the Hub driver + * + * @param[in] dev_hdl Device handle + * @param[out] dev_desc_ret Device descriptor + * @return esp_err_t + */ +esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret); + +/** + * @brief Get a device's active configuration descriptor + * + * Simply returns a reference to the internally cached configuration descriptor + * + * @note This function can block + * @param[in] dev_hdl Device handle + * @param config_desc_ret + * @return esp_err_t + */ +esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); + +/** + * @brief Submit a control transfer (URB) to a device + * + * @param[in] dev_hdl Device handle + * @param[in] urb URB + * @return esp_err_t + */ +esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb); + +// ----------------------------------------------- Endpoint Functions ------------------------------------------------- + +/** + * @brief Allocate an endpoint on a device + * + * This function allows clients to allocate a non-default endpoint (i.e., not EP0) on a connected device + * + * - A client must have opened the device using usbh_dev_open() before attempting to allocate an endpoint on the device + * - A client should call this function to allocate all endpoints in an interface that the client has claimed. + * - A client must allocate an endpoint using this function before attempting to communicate with it + * - Once the client allocates an endpoint, the client is now owns/manages the endpoint. No other client should use or + * deallocate the endpoint. + * + * @note This function can block + * @note Default endpoints (EP0) are owned by the USBH. For control transfers, use usbh_dev_submit_ctrl_urb() instead + * + * @param[in] dev_hdl Device handle + * @param[in] ep_config Endpoint configuration + * @param[out] ep_hdl_ret Endpoint handle + * @return esp_err_t + */ +esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, usbh_ep_handle_t *ep_hdl_ret); + +/** + * @brief Free and endpoint on a device + * + * This function frees an endpoint previously allocated by the client using usbh_ep_alloc() + * + * - Only the client that allocated the endpoint should free it + * - The client must have halted and flushed the endpoint using usbh_ep_command() before attempting to free it + * - The client must ensure that there are no more function calls to the endpoint before freeing it + * + * @note This function can block + * @param[in] ep_hdl Endpoint handle + * @return esp_err_t + */ +esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl); + +/** + * @brief Get the handle of an endpoint using its address + * + * The endpoint must have been previously allocated using usbh_ep_alloc() + * + * @param[in] dev_hdl Device handle + * @param[in] bEndpointAddress Endpoint address + * @param[out] ep_hdl_ret Endpoint handle + * @return esp_err_t + */ +esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret); + +/** + * @brief Enqueue a URB to an endpoint + * + * The URB will remain enqueued until it completes (successfully or errors out). Use usbh_ep_dequeue_urb() to dequeue + * a completed URB. + * + * @param[in] ep_hdl Endpoint handle + * @param[in] urb URB to enqueue + * @return esp_err_t + */ +esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb); + +/** + * @brief Dequeue a URB from an endpoint + * + * Dequeue a completed URB from an endpoint. The USBH_EP_EVENT_URB_DONE indicates that URBs can be dequeued + * + * @param[in] ep_hdl Endpoint handle + * @param[out] urb_ret Dequeued URB, or NULL if no more URBs to dequeue + * @return esp_err_t + */ +esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret); + +/** + * @brief Execute a command on a particular endpoint + * + * Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc) + * + * @param[in] ep_hdl Endpoint handle + * @param[in] command Endpoint command + * @return esp_err_t + */ +esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command); + +/** + * @brief Get the context of an endpoint + * + * Get the context variable assigned to and endpoint on allocation. + * + * @note This function can block + * @param[in] ep_hdl Endpoint handle + * @return Endpoint context + */ +void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); + +// -------------------------------------------------- Hub Functions ---------------------------------------------------- + +// ------------------- Device Related ---------------------- + +/** + * @brief Indicates to USBH that the Hub driver is installed + * + * - The Hub driver must call this function in its installation to indicate the the USBH that it has been installed. + * - This should only be called after the USBH has already be installed + * + * @note Hub Driver only + * @param[in] hub_req_callback Hub request callback + * @param[in] callback_arg Callback argument + * @return esp_err_t + */ +esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg); + +/** + * @brief Indicates to USBH the start of enumeration for a device + * + * - The Hub driver calls this function before it starts enumerating a new device. + * - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration + * functions. + * - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration. + * + * @note Hub Driver only + * @param[in] port_hdl Handle of the port that the device is connected to + * @param[in] dev_speed Device's speed + * @param[out] new_dev_hdl Device's handle + * @param[out] default_pipe_hdl Device's default pipe handle + * @return esp_err_t + */ +esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); + +/** + * @brief Indicates to the USBH that a hub event has occurred for a particular device + * + * @param dev_hdl Device handle + * @param hub_event Hub event + * @return esp_err_t + */ +esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event); + +// ----------------- Enumeration Related ------------------- + +/** + * @brief Assign the enumerating device's address + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param dev_addr + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); + +/** + * @brief Fill the enumerating device's descriptor + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param device_desc + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); + +/** + * @brief Fill the enumerating device's active configuration descriptor + * + * @note Hub Driver only + * @note Must call in sequence + * @note This function can block + * @param[in] dev_hdl Device handle + * @param config_desc_full + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); + +/** + * @brief Fill one of the string descriptors of the enumerating device + * + * @note Hub Driver only + * @note Must call in sequence + * @param dev_hdl Device handle + * @param str_desc Pointer to string descriptor + * @param select Select which string descriptor. 0/1/2 for Manufacturer/Product/Serial Number string descriptors respectively + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); + +/** + * @brief Indicate the device enumeration is completed + * + * This will all the device to be opened by clients, and also trigger a USBH_EVENT_DEV_NEW event. + * + * @note Hub Driver only + * @note Must call in sequence + * @note This function can block + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl); + +/** + * @brief Indicate that device enumeration has failed + * + * This will cause the enumerating device's resources to be cleaned up + * The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued. + * + * @note Hub Driver only + * @note Must call in sequence + * @note This function can block + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl); + +#ifdef __cplusplus +} +#endif