@@ -0,0 +1,97 @@
|
||||
# Changelog
|
||||
|
||||
## v0.7.6
|
||||
|
||||
### Features
|
||||
|
||||
- Support C++ build
|
||||
|
||||
## v0.7.5
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed the issue where idf_build_set_property is not a valid function for component
|
||||
|
||||
## v0.7.4
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed data bus init when direction is write in `io_http`
|
||||
|
||||
## v0.7.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed cache caps issues for esp32 in `io_file`
|
||||
|
||||
## v0.7.2
|
||||
|
||||
### Features
|
||||
|
||||
- Added io_file cache size configuration to enhance read and write performance
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Delay task creation to open stage in `io_http` to avoid task resource waste if no used
|
||||
|
||||
## v0.7.1
|
||||
|
||||
- Updated `esp-codec-dev` dependency to v1.4.0
|
||||
|
||||
## v0.7.0
|
||||
|
||||
### Features
|
||||
|
||||
- Added `esp_gmf_io_codec_dev_set_dev` API to set the audio codec device handle
|
||||
- Added `esp_gmf_io_http_set_event_callback` API to set an HTTP event callback
|
||||
- Added basic reset functionality to each IO to properly clean up resources and reset state
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue where HTTP IO did not reset the thread and databus properly
|
||||
- Fixed file open check for treat return error as valid fd
|
||||
- Corrected return value validation for *acq_write/read and *acq_release_write/read callback function implementations
|
||||
- Fixed HTTP connection timeout handling during _http_close operations
|
||||
- Standardize TAG identifier format across all I/O elements with `io` prefix
|
||||
|
||||
|
||||
## v0.6.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Changed the tag for embedded flash IO from `embed_flash` to `embed`
|
||||
- Refactor the script to make the generated files follow our coding style
|
||||
|
||||
## v0.6.2
|
||||
|
||||
### Features
|
||||
|
||||
- Limit the version of `esp_codec_dev`
|
||||
|
||||
## v0.6.1
|
||||
|
||||
- Fixed memory leaks in I/O flash destroy API
|
||||
- Corrected default values for I/O file and I/O I2S elements to ensure stable initialization
|
||||
- Enforced esp_gmf_err_t return type for all element initialization functions (I/O flash, HTTP)
|
||||
|
||||
## v0.6.0
|
||||
|
||||
### Features
|
||||
- Added support for NULL object configuration
|
||||
- Renamed component to `gmf_io`
|
||||
- Updated the License
|
||||
|
||||
### Bug Fixes
|
||||
- Standardized return values of port and data bus acquire/release APIs to esp_gmf_err_io_t only
|
||||
|
||||
## v0.5.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed the component requirements
|
||||
|
||||
|
||||
## v0.5.0
|
||||
|
||||
### Features
|
||||
|
||||
- Initial version of `esp-gmf-io`
|
||||
@@ -0,0 +1,23 @@
|
||||
idf_build_get_property(build_components BUILD_COMPONENTS)
|
||||
set(search_pattern "esp_codec_dev")
|
||||
set(index -1)
|
||||
foreach(item IN LISTS build_components)
|
||||
if("${item}" MATCHES "${search_pattern}.*")
|
||||
set(index ${item})
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
list(APPEND io_srcs "esp_gmf_io_file.c" "esp_gmf_io_embed_flash.c" "esp_gmf_io_i2s_pdm.c" "esp_gmf_io_http.c" "http_lib/gzip/gzip_miniz.c")
|
||||
if(index EQUAL -1)
|
||||
set(io_inc "")
|
||||
else()
|
||||
message(STATUS "The esp_codec_dev is installed")
|
||||
list(APPEND io_srcs "codec_dev_io/esp_gmf_io_codec_dev.c")
|
||||
set(io_inc "codec_dev_io")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${io_srcs}
|
||||
INCLUDE_DIRS ./include ${io_inc} "http_lib/gzip/" "http_lib/gzip/include"
|
||||
PRIV_INCLUDE_DIRS ""
|
||||
REQUIRES "driver" "esp_http_client")
|
||||
@@ -0,0 +1,22 @@
|
||||
Espressif Modified MIT License
|
||||
|
||||
Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
|
||||
Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.
|
||||
This includes the right to use, copy, modify, merge, publish, distribute, and sublicense
|
||||
the Software, subject to the following conditions:
|
||||
|
||||
1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.
|
||||
2. The above copyright notice and this permission notice shall be included in all copies
|
||||
or substantial portions of the Software.
|
||||
3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS
|
||||
is strictly prohibited.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
@@ -0,0 +1,36 @@
|
||||
# ESP-GMF-IO
|
||||
|
||||
- [](https://components.espressif.com/components/espressif/gmf_io)
|
||||
|
||||
- [中文版](./README_CN.md)
|
||||
|
||||
ESP GMF IO is a collection of GMF input and output streams. IO streams have two data flow directions: reading and writing. The input type is readable, and the output type is writable. IO streams classify data transmission types into byte access and block access types. In general, byte access involves copying the source data before transferring, while block access only transfers the source data address without copy overhead. Additionally, IO streams provide optional thread configuration, allowing some IO streams to establish their own independent threads. The currently supported IO streams are listed in the table below:
|
||||
|
||||
| Name | TAG | Data flow direction | Thread | Data Type| Dependent Components | Notes |
|
||||
| :----: | :----: | :----: | :----: | :----: | :----: |:----: |
|
||||
| File | io_file | RW | NO | Byte |NA | NA |
|
||||
| HTTP | io_http | RW | YES | Block | NA | Not support HTTP Live Stream |
|
||||
| Codec Dev IO | io_codec_dev | RW | NO | Byte | [ESP codec dev](https://components.espressif.com/components/espressif/esp_codec_dev) | NA |
|
||||
| Embed Flash | io_embed_flash | R | NO | Byte | NA | NA |
|
||||
| I2S PDM | io_i2s_pdm | RW | NO | Byte | NA | NA |
|
||||
|
||||
## Usage
|
||||
ESP GMF IO is typically used in combination with GMF Elements to form a pipeline, but it can also be used independently. In a pipeline, the IO stream is connected to the GMF Element's Port, and when used independently, the IO stream is accessed through the ESP_GMF_IO interface. For example code, please refer to [test_app](../test_apps/main/elements/gmf_audio_play_el_test.c)。
|
||||
|
||||
You can also create and compile a project using the following commands, taking the `pipeline_play_embed_music` project as an example. Before starting, make sure you have a working [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) environment.
|
||||
|
||||
### 1. Create the Example Project
|
||||
|
||||
Create the `pipeline_play_embed_music` example project based on the `gmf_examples` component (using version v0.7.0 as an example; update the version as needed):
|
||||
|
||||
```shell
|
||||
idf.py create-project-from-example "espressif/gmf_examples=0.7.0:pipeline_play_embed_music"
|
||||
```
|
||||
|
||||
### 2. Build and Flash the Project Using an ESP32-S3 Board
|
||||
|
||||
```shell
|
||||
cd pipeline_play_embed_music
|
||||
idf.py set-target esp32s3
|
||||
idf.py -p YOUR_PORT flash monitor
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
# ESP-GMF-IO
|
||||
|
||||
- [](https://components.espressif.com/components/espressif/gmf_io)
|
||||
|
||||
- [English](./README.md)
|
||||
|
||||
ESP GMF IO 是 GMF 输入和输出流的集合。IO 流有读写两个数据流方向,输入类型为可读,输出类型为可写。IO 流对数据传输类型进行了分类,分别是字节访问和块访问类型,一般来说,字节访问对源数据进行拷贝后传递,块访问仅传递源数据地址无拷贝开销。另外,IO 流还提供了线程的配置可选项,使一些 IO Stream 可以建立自己独立的线程。目前已支持的 IO 流参见下表:
|
||||
|
||||
| 名称 | TAG | 数据流方向 | 作为线程 | 数据类型| 依赖的组件 | 备注 |
|
||||
| :----: | :----: | :----: | :----: | :----: | :----: |:----: |
|
||||
| File | io_file | RW | NO | Byte |NA | NA |
|
||||
| HTTP | io_http | RW | YES | Block | NA | Not support HTTP Live Stream |
|
||||
| Codec Dev IO | io_codec_dev | RW | NO | Byte | [ESP codec dev](https://components.espressif.com/components/espressif/esp_codec_dev) | NA |
|
||||
| Embed Flash | io_embed_flash | R | NO | Byte | NA | NA |
|
||||
| I2S PDM | io_i2s_pdm | RW | NO | Byte | NA | NA |
|
||||
|
||||
## 示例
|
||||
ESP GMF IO 一般与 GMF 元素组成管道进行使用,也可以独立使用。在管道中,IO stream 与 GMF 元素的端口连接,独立使用时调用 IO stream 使用 ESP_GMF_IO 接口。 示例代码请参考 [test_app](../test_apps/main/elements/gmf_audio_play_el_test.c)。
|
||||
|
||||
此外,也可通过以下命令创建并编译工程,以 `pipeline_play_embed_music` 示例工程为例。开始之前需要有可运行的 [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) 环境。
|
||||
|
||||
### 1. 创建示例工程
|
||||
|
||||
基于 `gmf_examples` 组件创建 `pipeline_play_embed_music` 的示例(以 v0.7.0 版本为例, 请根据实际使用更新版本参数)
|
||||
|
||||
``` shell
|
||||
idf.py create-project-from-example "espressif/gmf_examples=0.7.0:pipeline_play_embed_music"
|
||||
```
|
||||
|
||||
### 2. 基于 ESP32S3 编译和下载
|
||||
|
||||
```shell
|
||||
cd pipeline_play_embed_music
|
||||
idf.py set-target esp32s3`
|
||||
idf.py -p YOUR_PORT flash monitor
|
||||
```
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_gmf_io_codec_dev.h"
|
||||
#include "esp_gmf_oal_mem.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/**
|
||||
* @brief Codec device io context in GMF
|
||||
*/
|
||||
typedef struct {
|
||||
esp_gmf_io_t base; /*!< The GMF codec dev io handle */
|
||||
bool is_open; /*!< The flag of whether opened */
|
||||
} codec_dev_io_stream_t;
|
||||
|
||||
static const char *TAG = "ESP_GMF_CODEC_DEV";
|
||||
|
||||
static esp_gmf_err_t esp_gmf_io_codec_dev_new(void *cfg, esp_gmf_obj_handle_t *io)
|
||||
{
|
||||
return esp_gmf_io_codec_dev_init(cfg, io);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _codec_dev_open(esp_gmf_io_handle_t io)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)io;
|
||||
codec_dev_io_cfg_t *cfg = (codec_dev_io_cfg_t *)OBJ_GET_CFG(codec_dev_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL;);
|
||||
ESP_GMF_CHECK(TAG, cfg->dev, {return ESP_GMF_ERR_FAIL;}, "There is no activated I2S driver handle");
|
||||
codec_dev_io->is_open = true;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _codec_dev_acquire_read(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
codec_dev_io_cfg_t *cfg = (codec_dev_io_cfg_t *)OBJ_GET_CFG(codec_dev_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_IO_FAIL;);
|
||||
if (esp_codec_dev_read(cfg->dev, pload->buf, wanted_size) != ESP_GMF_IO_OK) {
|
||||
ESP_LOGE(TAG, "Read failed, wanted: %ld", wanted_size);
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
pload->valid_size = wanted_size;
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _codec_dev_release_read(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)codec_dev_io, &info);
|
||||
ESP_LOGD(TAG, "Update len = %d, pos = %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, pload->valid_size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _codec_dev_acquire_write(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _codec_dev_release_write(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
codec_dev_io_cfg_t *cfg = (codec_dev_io_cfg_t *)OBJ_GET_CFG(codec_dev_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_IO_FAIL;);
|
||||
size_t wlen = pload->valid_size;
|
||||
if (esp_codec_dev_write(cfg->dev, pload->buf, pload->valid_size) != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Write failed, valid: %d", pload->valid_size);
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
esp_gmf_info_file_t info = {0};
|
||||
if (wlen > 0) {
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, wlen);
|
||||
}
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)codec_dev_io, &info);
|
||||
ESP_LOGD(TAG, "Write len: %d, pos: %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _codec_dev_seek(esp_gmf_io_handle_t io, uint64_t seek_byte_pos)
|
||||
{
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _codec_dev_close(esp_gmf_io_handle_t io)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)io;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)codec_dev_io, &info);
|
||||
ESP_LOGI(TAG, "CLose, %p, pos = %d/%d", codec_dev_io, (int)info.pos, (int)info.size);
|
||||
if (codec_dev_io->is_open) {
|
||||
codec_dev_io->is_open = false;
|
||||
}
|
||||
esp_gmf_io_set_pos((esp_gmf_io_handle_t)io, 0);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _codec_dev_delete(esp_gmf_io_handle_t io)
|
||||
{
|
||||
if (io != NULL) {
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)io;
|
||||
ESP_LOGD(TAG, "Delete, %s-%p", OBJ_GET_TAG(codec_dev_io), codec_dev_io);
|
||||
esp_gmf_oal_free(OBJ_GET_CFG(codec_dev_io));
|
||||
esp_gmf_io_deinit(io);
|
||||
esp_gmf_oal_free(codec_dev_io);
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_codec_dev_init(codec_dev_io_cfg_t *config, esp_gmf_io_handle_t *io)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, config, {return ESP_GMF_ERR_INVALID_ARG;});
|
||||
ESP_GMF_NULL_CHECK(TAG, io, {return ESP_GMF_ERR_INVALID_ARG;});
|
||||
*io = NULL;
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
codec_dev_io_stream_t *codec_dev_io = esp_gmf_oal_calloc(1, sizeof(codec_dev_io_stream_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, codec_dev_io, return ESP_GMF_ERR_MEMORY_LACK,
|
||||
"codec device", sizeof(codec_dev_io_stream_t));
|
||||
codec_dev_io->base.dir = config->dir;
|
||||
codec_dev_io->base.type = ESP_GMF_IO_TYPE_BYTE;
|
||||
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)codec_dev_io;
|
||||
obj->new_obj = esp_gmf_io_codec_dev_new;
|
||||
obj->del_obj = _codec_dev_delete;
|
||||
codec_dev_io_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(*config));
|
||||
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto _codec_dev_fail;},
|
||||
"codec device configuration", sizeof(*config));
|
||||
memcpy(cfg, config, sizeof(*config));
|
||||
esp_gmf_obj_set_config(obj, cfg, sizeof(*config));
|
||||
ret = esp_gmf_obj_set_tag(obj, (config->name == NULL ? "io_codec_dev" : config->name));
|
||||
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto _codec_dev_fail, "Failed to set obj tag");
|
||||
codec_dev_io->base.close = _codec_dev_close;
|
||||
codec_dev_io->base.open = _codec_dev_open;
|
||||
codec_dev_io->base.seek = _codec_dev_seek;
|
||||
codec_dev_io->base.reset = NULL;
|
||||
esp_gmf_io_init(obj, NULL);
|
||||
if (codec_dev_io->base.dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
codec_dev_io->base.acquire_write = _codec_dev_acquire_write;
|
||||
codec_dev_io->base.release_write = _codec_dev_release_write;
|
||||
} else if (codec_dev_io->base.dir == ESP_GMF_IO_DIR_READER) {
|
||||
codec_dev_io->base.acquire_read = _codec_dev_acquire_read;
|
||||
codec_dev_io->base.release_read = _codec_dev_release_read;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Does not set read or write function");
|
||||
ret = ESP_GMF_ERR_NOT_SUPPORT;
|
||||
goto _codec_dev_fail;
|
||||
}
|
||||
*io = obj;
|
||||
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), codec_dev_io);
|
||||
return ESP_GMF_ERR_OK;
|
||||
_codec_dev_fail:
|
||||
esp_gmf_obj_delete(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_codec_dev_set_dev(esp_gmf_io_handle_t io, esp_codec_dev_handle_t dev)
|
||||
{
|
||||
codec_dev_io_stream_t *codec_dev_io = (codec_dev_io_stream_t *)io;
|
||||
ESP_GMF_NULL_CHECK(TAG, codec_dev_io, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
|
||||
codec_dev_io_cfg_t *cfg = (codec_dev_io_cfg_t *)OBJ_GET_CFG(codec_dev_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_INVALID_STATE;);
|
||||
|
||||
cfg->dev = dev;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_codec_dev.h"
|
||||
#include "esp_gmf_io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* @brief Audio Codec Device IO configurations, if any entry is zero then the configuration will be set to default values
|
||||
*/
|
||||
typedef struct {
|
||||
esp_codec_dev_handle_t dev; /*!< Audio Codec Device handler */
|
||||
esp_gmf_io_dir_t dir; /*!< IO direction, reader or writer */
|
||||
const char *name; /*!< Name for this instance */
|
||||
} codec_dev_io_cfg_t;
|
||||
|
||||
#define ESP_GMF_IO_CODEC_DEV_CFG_DEFAULT() { \
|
||||
.dev = NULL, \
|
||||
.dir = ESP_GMF_IO_DIR_NONE, \
|
||||
.name = NULL, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the Audio Codec Device I/O with the provided configuration
|
||||
*
|
||||
* @param[in] config Audio codec device io configuration
|
||||
* @param[out] io Audio codec device io handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
|
||||
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_codec_dev_init(codec_dev_io_cfg_t *config, esp_gmf_io_handle_t *io);
|
||||
|
||||
/**
|
||||
* @brief Set or update the audio codec device handle for an existing I/O instance
|
||||
*
|
||||
* @param[in] io Audio codec device I/O handle
|
||||
* @param[in] dev New audio codec device handle to set
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments provided
|
||||
* - ESP_GMF_ERR_INVALID_STATE Invalid state
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_codec_dev_set_dev(esp_gmf_io_handle_t io, esp_codec_dev_handle_t dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include "esp_gmf_io_embed_flash.h"
|
||||
#include "esp_gmf_oal_mem.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define MAX_FILES_INDEX 1000
|
||||
|
||||
/**
|
||||
* @brief Embed flash io context in GMF
|
||||
*/
|
||||
typedef struct {
|
||||
esp_gmf_io_t base; /*!< The GMF embed flash io handle */
|
||||
int cur; /*!< The current stream pos */
|
||||
int max_files; /*!< The max file number */
|
||||
embed_item_info_t *items; /*!< The embed flash stream item */
|
||||
} embed_flash_io_t;
|
||||
|
||||
static const char *const TAG = "ESP_GMF_EMBED_FLASH";
|
||||
|
||||
static esp_gmf_err_t _embed_flash_new(void *cfg, esp_gmf_obj_handle_t *io)
|
||||
{
|
||||
return esp_gmf_io_embed_flash_init(cfg, io);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _embed_flash_open(esp_gmf_io_handle_t io)
|
||||
{
|
||||
embed_flash_io_t *embed_flash = (embed_flash_io_t *)io;
|
||||
if (embed_flash->items == NULL) {
|
||||
ESP_LOGE(TAG, "There is no embedded items, please call embed_flash_io_set_context first");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
char *uri = NULL;
|
||||
esp_gmf_io_get_uri((esp_gmf_io_handle_t)embed_flash, &uri);
|
||||
if (uri == NULL) {
|
||||
ESP_LOGE(TAG, "The file URI is NULL!");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
// The embedded file path like "embed://tone/0_alarm.mp3"
|
||||
char *temp = strchr(uri, '_');
|
||||
if (temp == NULL) {
|
||||
ESP_LOGE(TAG, "No _ in file name, %s", uri);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
*temp = 0;
|
||||
temp = strrchr(uri, '/');
|
||||
if (temp == NULL) {
|
||||
ESP_LOGE(TAG, "The file name is incorrect!, %s", uri);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
temp++;
|
||||
int file_index = strtoul(temp, 0, 10);
|
||||
ESP_LOGI(TAG, "The read item is %d, %s", file_index, uri);
|
||||
if (embed_flash->max_files < file_index) {
|
||||
ESP_LOGE(TAG, "The file index is out of range, %d", file_index);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
|
||||
embed_flash->cur = file_index;
|
||||
uint64_t total_bytes = embed_flash->items[embed_flash->cur].size;
|
||||
esp_gmf_io_set_size(io, total_bytes);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _embed_flash_acquire_read(esp_gmf_io_handle_t io, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
embed_flash_io_t *embed_flash = (embed_flash_io_t *)io;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)embed_flash, &info);
|
||||
if ((info.pos + wanted_size) > info.size) {
|
||||
wanted_size = info.size - info.pos;
|
||||
}
|
||||
ESP_LOGD(TAG, "Embed read data, ret:%ld, pos: %llu/%llu", wanted_size, info.pos, info.size);
|
||||
if (wanted_size == 0) {
|
||||
ESP_LOGW(TAG, "No more data, ret:%ld, pos: %llu/%llu", wanted_size, info.pos, info.size);
|
||||
pload->is_done = true;
|
||||
}
|
||||
memcpy(pload->buf, embed_flash->items[embed_flash->cur].address + info.pos, wanted_size);
|
||||
pload->valid_size = wanted_size;
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _embed_flash_release_read(esp_gmf_io_handle_t io, void *payload, int block_ticks)
|
||||
{
|
||||
embed_flash_io_t *embed_flash = (embed_flash_io_t *)io;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)embed_flash, &info);
|
||||
ESP_LOGD(TAG, "Update len = %d, pos = %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)embed_flash, pload->valid_size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _embed_flash_close(esp_gmf_io_handle_t io)
|
||||
{
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)io, &info);
|
||||
ESP_LOGI(TAG, "Closed, pos: %llu/%llu", info.pos, info.size);
|
||||
esp_gmf_io_set_pos(io, 0);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _embed_flash_destroy(esp_gmf_io_handle_t io)
|
||||
{
|
||||
embed_flash_io_t *embed_flash = (embed_flash_io_t *)io;
|
||||
ESP_LOGD(TAG, "Delete, %s-%p", OBJ_GET_TAG(embed_flash), embed_flash);
|
||||
void *cfg = OBJ_GET_CFG(io);
|
||||
if (cfg) {
|
||||
esp_gmf_oal_free(cfg);
|
||||
}
|
||||
if (embed_flash->items) {
|
||||
esp_gmf_oal_free(embed_flash->items);
|
||||
}
|
||||
esp_gmf_io_deinit(io);
|
||||
esp_gmf_oal_free(embed_flash);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_embed_flash_init(embed_flash_io_cfg_t *config, esp_gmf_io_handle_t *io)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
ESP_GMF_NULL_CHECK(TAG, io, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
*io = NULL;
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
embed_flash_io_t *embed_flash = esp_gmf_oal_calloc(1, sizeof(embed_flash_io_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, embed_flash, return ESP_GMF_ERR_MEMORY_LACK,
|
||||
"embed flash stream", sizeof(embed_flash_io_t));
|
||||
embed_flash->base.dir = ESP_GMF_IO_DIR_READER;
|
||||
embed_flash->base.type = ESP_GMF_IO_TYPE_BYTE;
|
||||
embed_flash->max_files = config->max_files;
|
||||
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)embed_flash;
|
||||
obj->new_obj = _embed_flash_new;
|
||||
obj->del_obj = _embed_flash_destroy;
|
||||
embed_flash_io_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(*config));
|
||||
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto _embed_fail;},
|
||||
"embed flash stream configuration", sizeof(*config));
|
||||
memcpy(cfg, config, sizeof(*config));
|
||||
esp_gmf_obj_set_config(obj, cfg, sizeof(*config));
|
||||
ret = esp_gmf_obj_set_tag(obj, (config->name == NULL ? "io_embed_flash" : config->name));
|
||||
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto _embed_fail, "Failed to set obj tag");
|
||||
embed_flash->base.open = _embed_flash_open;
|
||||
embed_flash->base.close = _embed_flash_close;
|
||||
embed_flash->base.seek = NULL;
|
||||
embed_flash->base.reset = NULL;
|
||||
esp_gmf_io_init(obj, NULL);
|
||||
if (embed_flash->base.dir == ESP_GMF_IO_DIR_READER) {
|
||||
embed_flash->base.acquire_read = _embed_flash_acquire_read;
|
||||
embed_flash->base.release_read = _embed_flash_release_read;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Does not support this operation, %d", embed_flash->base.dir);
|
||||
ret = ESP_GMF_ERR_NOT_SUPPORT;
|
||||
goto _embed_fail;
|
||||
}
|
||||
*io = obj;
|
||||
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), embed_flash);
|
||||
return ESP_GMF_ERR_OK;
|
||||
_embed_fail:
|
||||
esp_gmf_obj_delete(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_embed_flash_set_context(esp_gmf_io_handle_t io, const embed_item_info_t *context, int max_num)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, io, return ESP_GMF_ERR_INVALID_ARG);
|
||||
ESP_GMF_NULL_CHECK(TAG, context, return ESP_GMF_ERR_INVALID_ARG);
|
||||
embed_flash_io_t *embed_flash = (embed_flash_io_t *)io;
|
||||
if (embed_flash->items) {
|
||||
esp_gmf_oal_free(embed_flash->items);
|
||||
}
|
||||
embed_flash->items = esp_gmf_oal_calloc(max_num, sizeof(embed_item_info_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, embed_flash->items, return ESP_GMF_ERR_MEMORY_LACK,
|
||||
"embed item information", sizeof(embed_item_info_t));
|
||||
embed_flash->max_files = max_num;
|
||||
memcpy(embed_flash->items, context, sizeof(embed_item_info_t) * max_num);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "errno.h"
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_gmf_io_file.h"
|
||||
#include "esp_gmf_oal_mem.h"
|
||||
#include "fcntl.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define IO_FILE_ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define IO_FILE_DEFAULT_CACHE_ALIGN (512)
|
||||
|
||||
/**
|
||||
* @brief File io context in GMF
|
||||
*/
|
||||
typedef struct {
|
||||
esp_gmf_io_t base; /*!< The GMF file io handle */
|
||||
bool is_open; /*!< The flag of whether opened */
|
||||
FILE *file; /*!< The handle of file stream */
|
||||
uint8_t *cache; /*!< The cache buffer */
|
||||
int cache_size; /*!< The cache size */
|
||||
int cache_caps; /*!< The cache memory capabilities */
|
||||
} file_io_stream_t;
|
||||
|
||||
static const char *TAG = "ESP_GMF_FILE";
|
||||
|
||||
static char *get_mount_path(char *uri)
|
||||
{
|
||||
/* support format: /sdcard, /spiffs, /storage etc ... */
|
||||
if (uri[0] == '/') {
|
||||
return uri;
|
||||
}
|
||||
/* support format: scheme://basepath... */
|
||||
char *skip_scheme = strstr(uri, "://");
|
||||
if (skip_scheme == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
skip_scheme += 2;
|
||||
/* support format: scheme:///basepath... */
|
||||
if (skip_scheme[1] == '/') {
|
||||
skip_scheme++;
|
||||
}
|
||||
return skip_scheme;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_new(void *cfg, esp_gmf_obj_handle_t *io)
|
||||
{
|
||||
return esp_gmf_io_file_init(cfg, io);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_open(esp_gmf_io_handle_t io)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)io;
|
||||
char *uri = NULL;
|
||||
esp_gmf_io_get_uri((esp_gmf_io_handle_t)file_io, &uri);
|
||||
if (uri == NULL) {
|
||||
ESP_LOGE(TAG, "Error, uri is not set, handle: %p", io);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
ESP_LOGI(TAG, "Open, dir:%d, uri:%s", ((file_io_cfg_t *)file_io->base.parent.cfg)->dir, uri);
|
||||
char *path = get_mount_path(uri);
|
||||
if (path == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid URI (%s).", uri);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (file_io->is_open) {
|
||||
ESP_LOGE(TAG, "Already opened, p: %p, path: %s", file_io, path);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (((file_io_cfg_t *)file_io->base.parent.cfg)->dir == ESP_GMF_IO_DIR_READER) {
|
||||
file_io->file = fopen(path, "rb");
|
||||
if (file_io->file == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open on read, path: %s, err: %s", path, strerror(errno));
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
struct stat sz = {0};
|
||||
stat(path, &sz);
|
||||
esp_gmf_io_set_size((esp_gmf_io_handle_t)file_io, sz.st_size);
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)file_io, &info);
|
||||
ESP_LOGI(TAG, "File size: %d byte, file position: %lld", (int)sz.st_size, info.pos);
|
||||
if (info.pos > 0) {
|
||||
if (fseek(file_io->file, info.pos, SEEK_SET) != 0) {
|
||||
ESP_LOGE(TAG, "Seek to %lld failed, err: %s", info.pos, strerror(errno));
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
} else if (((file_io_cfg_t *)file_io->base.parent.cfg)->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
file_io->file = fopen(path, "wb");
|
||||
if (file_io->file == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open on write, path: %s, err: %s", path, strerror(errno));
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The type must be reader or writer");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (file_io->cache_size > 0) {
|
||||
file_io->cache = heap_caps_malloc(file_io->cache_size, file_io->cache_caps);
|
||||
ESP_GMF_MEM_VERIFY(TAG, file_io->cache, { fclose(file_io->file); return ESP_GMF_ERR_MEMORY_LACK;},
|
||||
"file stream cache", file_io->cache_size);
|
||||
setvbuf(file_io->file, (char *)file_io->cache, _IOFBF, file_io->cache_size);
|
||||
ESP_LOGD(TAG, "File_io cache: %p, size: %d, caps: 0x%x", file_io->cache, file_io->cache_size, file_io->cache_caps);
|
||||
}
|
||||
|
||||
file_io->is_open = true;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _file_acquire_read(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
size_t rlen = fread(pload->buf, 1, wanted_size, file_io->file);
|
||||
ESP_LOGD(TAG, "Read len: %zu-%ld", rlen, wanted_size);
|
||||
if (rlen == 0 && ferror(file_io->file)) {
|
||||
ESP_LOGE(TAG, "The error is happened in reading data, error msg: %s", strerror(errno));
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
pload->valid_size = rlen;
|
||||
if (rlen < wanted_size) {
|
||||
rlen = fread(pload->buf + rlen, 1, (wanted_size - rlen), file_io->file);
|
||||
if (rlen == 0) {
|
||||
pload->is_done = true;
|
||||
ESP_LOGI(TAG, "No more data, ret: %zu", rlen);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
pload->valid_size += rlen;
|
||||
}
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _file_release_read(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)file_io, &info);
|
||||
ESP_LOGD(TAG, "Update len = %d, pos = %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, pload->valid_size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _file_acquire_write(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _file_release_write(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
size_t wlen_total = 0;
|
||||
uint8_t *pbuf = pload->buf;
|
||||
if (file_io->cache_size > 0) {
|
||||
while (pload->valid_size > wlen_total) {
|
||||
int size = MIN(pload->valid_size - wlen_total, file_io->cache_size - 1);
|
||||
size_t wlen = fwrite(pbuf, 1, size, file_io->file);
|
||||
if (wlen == 0) {
|
||||
break;
|
||||
}
|
||||
pbuf += wlen;
|
||||
wlen_total += wlen;
|
||||
}
|
||||
} else {
|
||||
wlen_total = fwrite(pload->buf, 1, pload->valid_size, file_io->file);
|
||||
}
|
||||
if (wlen_total != pload->valid_size) {
|
||||
ESP_LOGE(TAG, "The error is happened in writing data, error msg:%s", strerror(errno));
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)file_io, &info);
|
||||
ESP_LOGD(TAG, "Write len = %zu, pos = %d/%d", wlen_total, (int)info.pos, (int)info.size);
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, wlen_total);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_seek(esp_gmf_io_handle_t io, uint64_t seek_byte_pos)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)io;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)file_io, &info);
|
||||
ESP_LOGI(TAG, "Seek position, total_bytes: %lld, seek: %lld",
|
||||
info.size, seek_byte_pos);
|
||||
if (seek_byte_pos > info.size) {
|
||||
ESP_LOGE(TAG, "Seek position is out of range, total_bytes: %lld, seek: %lld",
|
||||
info.size, seek_byte_pos);
|
||||
return ESP_GMF_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
if (fseek(file_io->file, seek_byte_pos, SEEK_SET) != 0) {
|
||||
ESP_LOGE(TAG, "Error seek file, error message: %s, line: %d", strerror(errno), __LINE__);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_reset(esp_gmf_io_handle_t io)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)io;
|
||||
if (file_io->file != NULL) {
|
||||
fseek(file_io->file, 0, SEEK_SET);
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_close(esp_gmf_io_handle_t io)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)io;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)file_io, &info);
|
||||
ESP_LOGI(TAG, "CLose, %p, pos = %d/%d", file_io, (int)info.pos, (int)info.size);
|
||||
if (file_io->is_open) {
|
||||
fclose(file_io->file);
|
||||
file_io->file = NULL;
|
||||
file_io->is_open = false;
|
||||
}
|
||||
esp_gmf_io_set_pos((esp_gmf_io_handle_t)io, 0);
|
||||
if (file_io->cache) {
|
||||
esp_gmf_oal_free(file_io->cache);
|
||||
file_io->cache = NULL;
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _file_delete(esp_gmf_io_handle_t io)
|
||||
{
|
||||
file_io_stream_t *file_io = (file_io_stream_t *)io;
|
||||
ESP_LOGD(TAG, "Delete, %s-%p", OBJ_GET_TAG(file_io), file_io);
|
||||
void *cfg = OBJ_GET_CFG(io);
|
||||
if (cfg) {
|
||||
esp_gmf_oal_free(cfg);
|
||||
}
|
||||
esp_gmf_io_deinit(io);
|
||||
esp_gmf_oal_free(file_io);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_file_init(file_io_cfg_t *config, esp_gmf_io_handle_t *io)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_INVALID_ARG);
|
||||
ESP_GMF_NULL_CHECK(TAG, io, return ESP_GMF_ERR_INVALID_ARG);
|
||||
*io = NULL;
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
file_io_stream_t *file_io = esp_gmf_oal_calloc(1, sizeof(file_io_stream_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, file_io, return ESP_GMF_ERR_MEMORY_LACK,
|
||||
"file stream", sizeof(file_io_stream_t));
|
||||
file_io->base.dir = config->dir;
|
||||
file_io->base.type = ESP_GMF_IO_TYPE_BYTE;
|
||||
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)file_io;
|
||||
obj->new_obj = _file_new;
|
||||
obj->del_obj = _file_delete;
|
||||
file_io_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(*config));
|
||||
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto _file_fail;}, "file_io cfg", sizeof(*config));
|
||||
memcpy(cfg, config, sizeof(*config));
|
||||
esp_gmf_obj_set_config(obj, cfg, sizeof(*config));
|
||||
ret = esp_gmf_obj_set_tag(obj, (config->name == NULL ? "io_file" : config->name));
|
||||
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto _file_fail, "Failed to set obj tag");
|
||||
file_io->base.close = _file_close;
|
||||
file_io->base.open = _file_open;
|
||||
file_io->base.seek = _file_seek;
|
||||
file_io->base.reset = _file_reset;
|
||||
file_io_cfg_t *fat_cfg = (file_io_cfg_t *)config;
|
||||
esp_gmf_io_init(obj, NULL);
|
||||
if (fat_cfg->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
file_io->base.acquire_write = _file_acquire_write;
|
||||
file_io->base.release_write = _file_release_write;
|
||||
} else if (fat_cfg->dir == ESP_GMF_IO_DIR_READER) {
|
||||
file_io->base.acquire_read = _file_acquire_read;
|
||||
file_io->base.release_read = _file_release_read;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Does not set read or write function");
|
||||
ret = ESP_GMF_ERR_NOT_SUPPORT;
|
||||
goto _file_fail;
|
||||
}
|
||||
*io = obj;
|
||||
if (cfg->cache_size <= IO_FILE_DEFAULT_CACHE_ALIGN) {
|
||||
file_io->cache_size = 0;
|
||||
} else {
|
||||
file_io->cache_caps = (cfg->cache_caps == 0) ? (MALLOC_CAP_DMA) : cfg->cache_caps;
|
||||
file_io->cache_size = IO_FILE_ALIGN_UP(cfg->cache_size, IO_FILE_DEFAULT_CACHE_ALIGN);
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
_file_fail:
|
||||
esp_gmf_obj_delete(obj);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "gzip_miniz.h"
|
||||
|
||||
#include "esp_gmf_new_databus.h"
|
||||
#include "esp_gmf_io_http.h"
|
||||
#include "esp_gmf_oal_mem.h"
|
||||
|
||||
#define HTTP_STREAM_BUFFER_SIZE (3 * 1024)
|
||||
#define HTTP_MAX_CONNECT_TIMES (5)
|
||||
|
||||
/**
|
||||
* @brief Http io context in GMF
|
||||
*/
|
||||
typedef struct http_io {
|
||||
esp_gmf_io_t base; /*!< The GMF http io handle */
|
||||
bool is_open; /*!< The flag of whether opened */
|
||||
esp_http_client_handle_t client; /*!< The http client handle */
|
||||
int _errno; /*!< Errno code for http */
|
||||
int connect_times; /*!< Max reconnect times */
|
||||
bool gzip_encoding; /*!< Content is encoded */
|
||||
gzip_miniz_handle_t gzip; /*!< GZIP instance */
|
||||
esp_gmf_db_handle_t data_bus; /*!< The data bus handle */
|
||||
} http_stream_t;
|
||||
|
||||
static const char *TAG = "ESP_GMF_HTTP";
|
||||
|
||||
static esp_gmf_err_t _http_destroy(esp_gmf_io_handle_t self);
|
||||
|
||||
static int _gzip_read_data(uint8_t *data, int size, void *ctx)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)ctx;
|
||||
return esp_http_client_read(http->client, (char *)data, size);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_event_handle(esp_http_client_event_t *evt)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)evt->user_data;
|
||||
if (evt->event_id != HTTP_EVENT_ON_HEADER) {
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
if (strcasecmp(evt->header_key, "Content-Encoding") == 0) {
|
||||
http->gzip_encoding = true;
|
||||
if (strcasecmp(evt->header_value, "gzip") == 0) {
|
||||
gzip_miniz_cfg_t cfg = {
|
||||
.chunk_size = 1024,
|
||||
.ctx = http,
|
||||
.read_cb = _gzip_read_data,
|
||||
};
|
||||
http->gzip = gzip_miniz_init(&cfg);
|
||||
}
|
||||
if (http->gzip == NULL) {
|
||||
ESP_LOGE(TAG, "Content-Encoding %s not supported", evt->header_value);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static int dispatch_hook(esp_gmf_io_handle_t self, http_stream_event_id_t type, void *buffer, int buffer_len)
|
||||
{
|
||||
http_stream_t *http_stream = (http_stream_t *)self;
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(http_stream);
|
||||
http_stream_event_msg_t msg;
|
||||
msg.event_id = type;
|
||||
msg.http_client = http_stream->client;
|
||||
msg.user_data = http_io_cfg->user_data;
|
||||
msg.buffer = buffer;
|
||||
msg.buffer_len = buffer_len;
|
||||
if (http_io_cfg->event_handle) {
|
||||
return http_io_cfg->event_handle(&msg);
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static int _http_read_data(http_stream_t *http, char *buffer, int len)
|
||||
{
|
||||
if (http->gzip_encoding == false) {
|
||||
return esp_http_client_read(http->client, buffer, len);
|
||||
}
|
||||
// use gzip to uncompress data
|
||||
return gzip_miniz_read(http->gzip, (uint8_t *)buffer, len);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_new(void *cfg, esp_gmf_obj_handle_t *io)
|
||||
{
|
||||
return esp_gmf_io_http_init(cfg, io);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_open(esp_gmf_io_handle_t self)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
esp_gmf_err_t err;
|
||||
if (http->is_open) {
|
||||
ESP_LOGW(TAG, "The HTTP already opened, %p", http);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
http->_errno = 0;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)http, &info);
|
||||
char *uri = (char *)info.uri;
|
||||
if (uri == NULL) {
|
||||
ESP_LOGE(TAG, "Error open connection, uri = NULL");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
char *hls_type = strrchr(uri, '/');
|
||||
if (hls_type && strstr(hls_type, ".m3u")) {
|
||||
ESP_LOGE(TAG, "The HTTP stream does not support HTTP Live Streaming. URI:%s", uri);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)http, &info);
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(http);
|
||||
ESP_LOGI(TAG, "HTTP Open, URI = %s", uri);
|
||||
if (http->client == NULL) {
|
||||
esp_http_client_config_t http_cfg = {
|
||||
.url = uri,
|
||||
.event_handler = _http_event_handle,
|
||||
.user_data = self,
|
||||
.timeout_ms = 30 * 1000,
|
||||
.buffer_size = HTTP_STREAM_BUFFER_SIZE,
|
||||
.buffer_size_tx = 1024,
|
||||
.cert_pem = http_io_cfg->cert_pem,
|
||||
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
.crt_bundle_attach = http_io_cfg->crt_bundle_attach,
|
||||
#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE */
|
||||
};
|
||||
http->client = esp_http_client_init(&http_cfg);
|
||||
ESP_GMF_CHECK(TAG, http->client, return ESP_GMF_ERR_MEMORY_LACK, "Failed to initialize http client");
|
||||
} else {
|
||||
esp_http_client_set_url(http->client, uri);
|
||||
}
|
||||
|
||||
if (info.pos) {
|
||||
char rang_header[32];
|
||||
snprintf(rang_header, 32, "bytes=%d-", (int)info.pos);
|
||||
esp_http_client_set_header(http->client, "Range", rang_header);
|
||||
} else {
|
||||
esp_http_client_delete_header(http->client, "Range");
|
||||
}
|
||||
|
||||
if (dispatch_hook(self, HTTP_STREAM_PRE_REQUEST, NULL, 0) != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to process user callback:%d", __LINE__);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
|
||||
if (http->data_bus == NULL) {
|
||||
err = esp_gmf_db_new_block(1, http_io_cfg->out_buf_size, &http->data_bus);
|
||||
if (err != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to create the buffer for %d, sz: %d, %s-%p", http_io_cfg->dir, http_io_cfg->out_buf_size, OBJ_GET_TAG(http), http);
|
||||
return err;
|
||||
}
|
||||
esp_gmf_data_bus_type_t db_type = 0;
|
||||
esp_gmf_db_get_type(http->data_bus, &db_type);
|
||||
http->base.type = db_type;
|
||||
}
|
||||
|
||||
if (http_io_cfg->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
err = esp_http_client_open(http->client, -1);
|
||||
if (err == ESP_GMF_ERR_OK) {
|
||||
http->is_open = true;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
char *buffer = NULL;
|
||||
int post_len = esp_http_client_get_post_field(http->client, &buffer);
|
||||
_stream_redirect:
|
||||
if (http->gzip_encoding) {
|
||||
gzip_miniz_deinit(http->gzip);
|
||||
http->gzip = NULL;
|
||||
http->gzip_encoding = false;
|
||||
}
|
||||
if ((err = esp_http_client_open(http->client, post_len)) != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open http stream");
|
||||
return err;
|
||||
}
|
||||
|
||||
int wrlen = dispatch_hook(self, HTTP_STREAM_ON_REQUEST, buffer, post_len);
|
||||
if (wrlen < 0) {
|
||||
ESP_LOGE(TAG, "Failed to process user callback:%d", __LINE__);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (post_len && buffer && wrlen == 0) {
|
||||
if (esp_http_client_write(http->client, buffer, post_len) <= 0) {
|
||||
ESP_LOGE(TAG, "Failed to write data to http stream");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, "len=%d, data=%s", post_len, buffer);
|
||||
}
|
||||
|
||||
if (dispatch_hook(self, HTTP_STREAM_POST_REQUEST, NULL, 0) < 0) {
|
||||
esp_http_client_close(http->client);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
/**
|
||||
* Due to the total byte of content has been changed after seek, set info.total_bytes at beginning only.
|
||||
*/
|
||||
int64_t cur_pos = esp_http_client_fetch_headers(http->client);
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)http, &info);
|
||||
if (info.pos <= 0) {
|
||||
info.size = cur_pos;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "The total size is %d bytes", (int)info.size);
|
||||
int status_code = esp_http_client_get_status_code(http->client);
|
||||
if (status_code == 301 || status_code == 302) {
|
||||
esp_http_client_set_redirection(http->client);
|
||||
goto _stream_redirect;
|
||||
}
|
||||
if (status_code != 200
|
||||
&& (esp_http_client_get_status_code(http->client) != 206)) {
|
||||
ESP_LOGE(TAG, "Invalid HTTP stream, status code = %d", status_code);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
esp_gmf_io_set_size(self, info.size);
|
||||
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_prev_close(esp_gmf_io_handle_t self)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
esp_gmf_db_abort(http->data_bus);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_close(esp_gmf_io_handle_t self)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
ESP_LOGD(TAG, "_http_close, %p", http);
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(http);
|
||||
if (http->is_open && (http_io_cfg->dir == ESP_GMF_IO_DIR_WRITER)) {
|
||||
do {
|
||||
if (dispatch_hook(self, HTTP_STREAM_POST_REQUEST, NULL, 0) < 0) {
|
||||
break;
|
||||
}
|
||||
if (esp_http_client_fetch_headers(http->client) < 0) {
|
||||
break;
|
||||
}
|
||||
if (dispatch_hook(self, HTTP_STREAM_FINISH_REQUEST, NULL, 0) < 0) {
|
||||
break;
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
http->is_open = false;
|
||||
if (http->gzip) {
|
||||
gzip_miniz_deinit(http->gzip);
|
||||
http->gzip = NULL;
|
||||
}
|
||||
if (http->client) {
|
||||
esp_http_client_close(http->client);
|
||||
esp_http_client_cleanup(http->client);
|
||||
http->client = NULL;
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_reconnect(esp_gmf_io_handle_t self)
|
||||
{
|
||||
esp_gmf_err_t err = ESP_GMF_ERR_OK;
|
||||
ESP_GMF_NULL_CHECK(TAG, self, return ESP_GMF_ERR_FAIL);
|
||||
esp_gmf_info_file_t info = {0};
|
||||
err |= esp_gmf_io_get_info((esp_gmf_io_handle_t)self, &info);
|
||||
err |= _http_close(self);
|
||||
err |= esp_gmf_io_update_pos(self, info.pos);
|
||||
err |= _http_open(self);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int _http_read(esp_gmf_io_handle_t self, char *buffer, int len, TickType_t ticks_to_wait, void *context)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)http, &info);
|
||||
int wrlen = dispatch_hook(self, HTTP_STREAM_ON_RESPONSE, buffer, len);
|
||||
int rlen = wrlen;
|
||||
if (rlen == 0) {
|
||||
rlen = _http_read_data(http, buffer, len);
|
||||
}
|
||||
if (rlen <= 0) {
|
||||
http->_errno = esp_http_client_get_errno(http->client);
|
||||
ESP_LOGW(TAG, "No more data, errno: %d, read bytes: %llu, rlen = %d", http->_errno, info.pos, rlen);
|
||||
if (http->_errno != 0) { // Error occuered, reset connection
|
||||
ESP_LOGW(TAG, "Got %d errno(%s)", http->_errno, strerror(http->_errno));
|
||||
return http->_errno;
|
||||
}
|
||||
return ESP_GMF_ERR_OK;
|
||||
} else {
|
||||
esp_gmf_io_update_pos(self, rlen);
|
||||
}
|
||||
ESP_LOGD(TAG, "req length = %d, read = %d, pos = %d/%d", len, rlen, (int)info.pos, (int)info.size);
|
||||
return rlen;
|
||||
}
|
||||
|
||||
static int _http_write(esp_gmf_io_handle_t self, char *buffer, int len, TickType_t ticks_to_wait, void *context)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
int wrlen = dispatch_hook(self, HTTP_STREAM_ON_REQUEST, buffer, len);
|
||||
if (wrlen < 0) {
|
||||
ESP_LOGE(TAG, "Failed to process user callback:%d", __LINE__);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (wrlen > 0) {
|
||||
return wrlen;
|
||||
}
|
||||
if ((wrlen = esp_http_client_write(http->client, buffer, len)) <= 0) {
|
||||
http->_errno = esp_http_client_get_errno(http->client);
|
||||
ESP_LOGE(TAG, "Failed to write data to http stream, wrlen = %d, errno = %d(%s)", wrlen, http->_errno, strerror(http->_errno));
|
||||
}
|
||||
return wrlen;
|
||||
}
|
||||
|
||||
static esp_gmf_job_err_t _http_process(esp_gmf_io_handle_t self, void *params)
|
||||
{
|
||||
int r_size = 0;
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
esp_gmf_data_bus_block_t blk = {0};
|
||||
esp_gmf_job_err_t job_err = ESP_GMF_JOB_ERR_OK;
|
||||
http->is_open = true;
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(http);
|
||||
if (http_io_cfg->dir == ESP_GMF_IO_DIR_READER) {
|
||||
esp_gmf_db_acquire_write(http->data_bus, &blk, HTTP_STREAM_BUFFER_SIZE, portMAX_DELAY);
|
||||
r_size = _http_read(self, (char *)blk.buf, blk.buf_length, portMAX_DELAY, NULL);
|
||||
blk.valid_size = r_size;
|
||||
ESP_LOGD(TAG, "Read: %d, len: %d", r_size, blk.buf_length);
|
||||
if (r_size > 0) {
|
||||
if (http->_errno != 0) {
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
if (http->connect_times > HTTP_MAX_CONNECT_TIMES) {
|
||||
ESP_LOGE(TAG, "Reconnect times more than %d, disconnect http stream", HTTP_MAX_CONNECT_TIMES);
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
};
|
||||
http->connect_times++;
|
||||
ret = _http_reconnect(self);
|
||||
if (ret != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to reset connection");
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGW(TAG, "Reconnect to peer successful");
|
||||
return ESP_GMF_ERR_INVALID_STATE;
|
||||
} else {
|
||||
http->connect_times = 0;
|
||||
if (http_io_cfg->dir == ESP_GMF_IO_DIR_READER) {
|
||||
esp_gmf_db_release_write(http->data_bus, &blk, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
} else if (r_size == 0) {
|
||||
esp_gmf_db_done_write(http->data_bus);
|
||||
esp_gmf_db_release_write(http->data_bus, &blk, portMAX_DELAY);
|
||||
job_err = ESP_GMF_JOB_ERR_DONE;
|
||||
} else {
|
||||
job_err = r_size;
|
||||
esp_gmf_db_abort(http->data_bus);
|
||||
}
|
||||
} else {
|
||||
r_size = esp_gmf_db_acquire_read(http->data_bus, &blk, HTTP_STREAM_BUFFER_SIZE, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "ACQ, read: %d, vld: %d, buf_len: %d", r_size, blk.valid_size, blk.buf_length);
|
||||
if (blk.valid_size > 0) {
|
||||
int w_size = _http_write(self, (char *)blk.buf, blk.valid_size, portMAX_DELAY, NULL);
|
||||
if (w_size <= 0) {
|
||||
job_err = ESP_GMF_JOB_ERR_FAIL;
|
||||
}
|
||||
} else if (r_size == ESP_GMF_IO_OK || r_size == ESP_GMF_IO_ABORT) {
|
||||
job_err = ESP_GMF_JOB_ERR_DONE;
|
||||
} else {
|
||||
job_err = r_size;
|
||||
}
|
||||
esp_gmf_db_release_read(http->data_bus, &blk, portMAX_DELAY);
|
||||
}
|
||||
return job_err;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_destroy(esp_gmf_io_handle_t self)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)self;
|
||||
ESP_LOGD(TAG, "%s-%p", __FUNCTION__, http);
|
||||
if (http->data_bus) {
|
||||
esp_gmf_db_deinit(http->data_bus);
|
||||
http->data_bus = NULL;
|
||||
}
|
||||
void *cfg = OBJ_GET_CFG(self);
|
||||
if (cfg) {
|
||||
esp_gmf_oal_free(cfg);
|
||||
}
|
||||
esp_gmf_io_deinit(http);
|
||||
esp_gmf_oal_free(http);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _http_seek(esp_gmf_io_handle_t handle, uint64_t pos)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)handle, &info);
|
||||
if (pos > info.size) {
|
||||
ESP_LOGE(TAG, "The seek position is out of range, pos %llu > %llu, http: %p", pos, info.size, http);
|
||||
return ESP_GMF_ERR_OUT_OF_RANGE;
|
||||
}
|
||||
ESP_LOGD(TAG, "HTTP Seek to: %lld, %p", pos, http);
|
||||
_http_close(handle);
|
||||
|
||||
esp_gmf_io_set_pos(http, pos);
|
||||
esp_gmf_db_reset(http->data_bus);
|
||||
_http_open(handle);
|
||||
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _http_acquire_read(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
esp_gmf_data_bus_block_t *blk = (esp_gmf_data_bus_block_t *)payload;
|
||||
esp_gmf_err_io_t ret = esp_gmf_db_acquire_read(http->data_bus, payload, wanted_size, block_ticks);
|
||||
ESP_LOGD(TAG, "acq_rd: %ld, vld: %d, done: %d, %p, %d", wanted_size, blk->valid_size, blk->is_last, blk->buf, blk->buf_length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _http_release_read(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
esp_gmf_data_bus_block_t *blk = (esp_gmf_data_bus_block_t *)payload;
|
||||
ESP_LOGD(TAG, "rel_rd: %p, vld: %d, len: %d done: %d", blk->buf, blk->valid_size, blk->buf_length, blk->is_last);
|
||||
return esp_gmf_db_release_read(http->data_bus, payload, block_ticks);
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _http_acquire_write(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
esp_gmf_data_bus_block_t *blk = (esp_gmf_data_bus_block_t *)payload;
|
||||
ESP_LOGD(TAG, "acq_wr: %ld, vld: %d, done: %d, %p, %d", wanted_size, blk->valid_size, blk->is_last, blk->buf, blk->buf_length);
|
||||
return esp_gmf_db_acquire_write(http->data_bus, payload, wanted_size, block_ticks);
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _http_release_write(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
esp_gmf_data_bus_block_t *blk = (esp_gmf_data_bus_block_t *)payload;
|
||||
ESP_LOGD(TAG, "rel_wr: %p, vld: %d, len: %d, done: %d", blk->buf, blk->valid_size, blk->buf_length, blk->is_last);
|
||||
return esp_gmf_db_release_write(http->data_bus, payload, block_ticks);
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_http_reset(esp_gmf_io_handle_t handle)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
http_stream_t *http = (http_stream_t *)handle;
|
||||
if (http->data_bus) {
|
||||
esp_gmf_db_reset(http->data_bus);
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset, %p", http);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_http_set_server_cert(esp_gmf_io_handle_t handle, const char *cert)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
ESP_GMF_NULL_CHECK(TAG, cert, return ESP_GMF_ERR_INVALID_ARG);
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(handle);
|
||||
http_io_cfg->cert_pem = cert;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_http_set_event_callback(esp_gmf_io_handle_t handle, http_io_event_handle_t event_callback)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
|
||||
http_io_cfg_t *http_io_cfg = (http_io_cfg_t *)OBJ_GET_CFG(handle);
|
||||
http_io_cfg->event_handle = event_callback;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_http_init(http_io_cfg_t *config, esp_gmf_io_handle_t *io)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_INVALID_ARG);
|
||||
ESP_GMF_NULL_CHECK(TAG, io, return ESP_GMF_ERR_INVALID_ARG);
|
||||
*io = NULL;
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
http_stream_t *http = esp_gmf_oal_calloc(1, sizeof(http_stream_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, http, return ESP_GMF_ERR_MEMORY_LACK,
|
||||
"http stream", sizeof(http_stream_t));
|
||||
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)http;
|
||||
obj->new_obj = _http_new;
|
||||
obj->del_obj = _http_destroy;
|
||||
http_io_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(*config));
|
||||
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto _http_init_fail;},
|
||||
"http stream configuration", sizeof(*config));
|
||||
memcpy(cfg, config, sizeof(*config));
|
||||
esp_gmf_obj_set_config(obj, cfg, sizeof(*config));
|
||||
ret = esp_gmf_obj_set_tag(obj, "io_http");
|
||||
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto _http_init_fail, "Failed to set obj tag");
|
||||
http->base.dir = config->dir;
|
||||
http->base.type = ESP_GMF_IO_TYPE_BLOCK;
|
||||
http->base.open = _http_open;
|
||||
http->base.process = _http_process;
|
||||
http->base.seek = _http_seek;
|
||||
http->base.prev_close = _http_prev_close;
|
||||
http->base.close = _http_close;
|
||||
http->base.reset = esp_gmf_io_http_reset;
|
||||
if (config->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
http->base.acquire_write = _http_acquire_write;
|
||||
http->base.release_write = _http_release_write;
|
||||
} else if (config->dir == ESP_GMF_IO_DIR_READER) {
|
||||
http->base.acquire_read = _http_acquire_read;
|
||||
http->base.release_read = _http_release_read;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Does not set read or write function");
|
||||
ret = ESP_GMF_ERR_NOT_SUPPORT;
|
||||
goto _http_init_fail;
|
||||
}
|
||||
esp_gmf_io_cfg_t io_cfg = {
|
||||
.thread.stack = config->task_stack,
|
||||
.thread.prio = config->task_prio,
|
||||
.thread.core = config->task_core,
|
||||
.thread.stack_in_ext = config->stack_in_ext,
|
||||
};
|
||||
ret = esp_gmf_io_init(&http->base, &io_cfg);
|
||||
if(ret != ESP_GMF_ERR_OK) {
|
||||
goto _http_init_fail;
|
||||
}
|
||||
*io = obj;
|
||||
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(http), http);
|
||||
return ESP_GMF_ERR_OK;
|
||||
_http_init_fail:
|
||||
esp_gmf_obj_delete(obj);
|
||||
return ret;
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_gmf_io_i2s_pdm.h"
|
||||
#include "esp_gmf_oal_mem.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define PDM_TX_DONE_BIT BIT(0)
|
||||
|
||||
/**
|
||||
* @brief I2S pdm io context in GMF
|
||||
*/
|
||||
typedef struct {
|
||||
esp_gmf_io_t base; /*!< The GMF i2s pdm io handle */
|
||||
bool is_open; /*!< The flag of whether opened */
|
||||
EventGroupHandle_t pdm_event; /*!< The event group handle of i2s pdm */
|
||||
} i2s_pdm_io_stream_t;
|
||||
|
||||
static const char *TAG = "ESP_GMF_IIS_PDM";
|
||||
|
||||
bool _i2s_pdm_tx_done_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
|
||||
{
|
||||
EventGroupHandle_t evt = (EventGroupHandle_t)user_ctx;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
BaseType_t xResult = xEventGroupSetBitsFromISR(evt, PDM_TX_DONE_BIT, &xHigherPriorityTaskWoken);
|
||||
return xResult;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_new(void *cfg, esp_gmf_obj_handle_t *io)
|
||||
{
|
||||
return esp_gmf_io_i2s_pdm_init(cfg, io);
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_open(esp_gmf_io_handle_t io)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)io;
|
||||
i2s_pdm_io_cfg_t *cfg = (i2s_pdm_io_cfg_t *)OBJ_GET_CFG(i2s_pdm_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL;);
|
||||
if (cfg->pdm_chan == NULL) {
|
||||
ESP_LOGE(TAG, "There is no activated I2S driver handle");
|
||||
return ESP_GMF_ERR_FAIL;
|
||||
}
|
||||
if (cfg->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
i2s_pdm_io->pdm_event = xEventGroupCreate();
|
||||
ESP_GMF_CHECK(TAG, i2s_pdm_io->pdm_event, return ESP_GMF_ERR_MEMORY_LACK, "Failed to create i2s pdm event");
|
||||
i2s_event_callbacks_t cbs = {
|
||||
.on_recv = NULL,
|
||||
.on_recv_q_ovf = NULL,
|
||||
.on_sent = NULL,
|
||||
.on_send_q_ovf = _i2s_pdm_tx_done_callback,
|
||||
};
|
||||
i2s_channel_register_event_callback(cfg->pdm_chan, &cbs, i2s_pdm_io->pdm_event);
|
||||
}
|
||||
i2s_channel_enable(cfg->pdm_chan);
|
||||
i2s_pdm_io->is_open = true;
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _i2s_pdm_acquire_read(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
size_t rlen = 0;
|
||||
i2s_pdm_io_cfg_t *cfg = (i2s_pdm_io_cfg_t *)OBJ_GET_CFG(i2s_pdm_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_IO_FAIL;);
|
||||
if (i2s_channel_read(cfg->pdm_chan, pload->buf, wanted_size, &rlen, ESP_GMF_MAX_DELAY) != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Read I2S data error, wanted: %ld", wanted_size);
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
pload->valid_size = rlen;
|
||||
ESP_LOGD(TAG, "Read len: %d", rlen);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _i2s_pdm_release_read(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)i2s_pdm_io, &info);
|
||||
ESP_LOGD(TAG, "Update len = %d, pos = %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, pload->valid_size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _i2s_pdm_acquire_write(esp_gmf_io_handle_t handle, void *payload, uint32_t wanted_size, int block_ticks)
|
||||
{
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_io_t _i2s_pdm_release_write(esp_gmf_io_handle_t handle, void *payload, int block_ticks)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)handle;
|
||||
esp_gmf_payload_t *pload = (esp_gmf_payload_t *)payload;
|
||||
size_t wlen = 0;
|
||||
i2s_pdm_io_cfg_t *cfg = (i2s_pdm_io_cfg_t *)OBJ_GET_CFG(i2s_pdm_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_IO_FAIL;);
|
||||
if (i2s_channel_write(cfg->pdm_chan, pload->buf, pload->valid_size, &wlen, ESP_GMF_MAX_DELAY) != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "I2S write failed, valid: %d", pload->valid_size);
|
||||
return ESP_GMF_IO_FAIL;
|
||||
}
|
||||
esp_gmf_info_file_t info = {0};
|
||||
if (pload->is_done && i2s_pdm_io->pdm_event) {
|
||||
ESP_LOGE(TAG, "Clear the PDM_TX_DONE_BIT, len = %d", pload->valid_size);
|
||||
xEventGroupClearBits(i2s_pdm_io->pdm_event, PDM_TX_DONE_BIT);
|
||||
}
|
||||
if (wlen > 0) {
|
||||
esp_gmf_io_update_pos((esp_gmf_io_handle_t)handle, wlen);
|
||||
}
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)i2s_pdm_io, &info);
|
||||
ESP_LOGD(TAG, "Write len = %d, pos = %d/%d", pload->valid_size, (int)info.pos, (int)info.size);
|
||||
return ESP_GMF_IO_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_seek(esp_gmf_io_handle_t io, uint64_t seek_byte_pos)
|
||||
{
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_reset(esp_gmf_io_handle_t io)
|
||||
{
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_close(esp_gmf_io_handle_t io)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)io;
|
||||
esp_gmf_info_file_t info = {0};
|
||||
i2s_pdm_io_cfg_t *cfg = (i2s_pdm_io_cfg_t *)OBJ_GET_CFG(i2s_pdm_io);
|
||||
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_IO_FAIL;);
|
||||
esp_gmf_io_get_info((esp_gmf_io_handle_t)i2s_pdm_io, &info);
|
||||
ESP_LOGI(TAG, "Try to close, %p, pos = %d/%d, pdm_event: %p", i2s_pdm_io, (int)info.pos, (int)info.size, i2s_pdm_io->pdm_event);
|
||||
if (i2s_pdm_io->pdm_event) {
|
||||
xEventGroupWaitBits(i2s_pdm_io->pdm_event, PDM_TX_DONE_BIT, pdTRUE, pdFALSE, ESP_GMF_MAX_DELAY);
|
||||
}
|
||||
ESP_LOGI(TAG, "CLose, %p, pos = %d/%d", i2s_pdm_io, (int)info.pos, (int)info.size);
|
||||
if (i2s_pdm_io->is_open) {
|
||||
i2s_pdm_io->is_open = false;
|
||||
}
|
||||
esp_gmf_io_set_pos((esp_gmf_io_handle_t)io, 0);
|
||||
i2s_channel_disable(cfg->pdm_chan);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
static esp_gmf_err_t _i2s_pdm_delete(esp_gmf_io_handle_t io)
|
||||
{
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = (i2s_pdm_io_stream_t *)io;
|
||||
ESP_LOGD(TAG, "Delete, %s-%p", OBJ_GET_TAG(i2s_pdm_io), i2s_pdm_io);
|
||||
if (i2s_pdm_io->pdm_event) {
|
||||
vEventGroupDelete(i2s_pdm_io->pdm_event);
|
||||
}
|
||||
void *cfg = OBJ_GET_CFG(io);
|
||||
if (cfg) {
|
||||
esp_gmf_oal_free(cfg);
|
||||
}
|
||||
esp_gmf_io_deinit(io);
|
||||
esp_gmf_oal_free(i2s_pdm_io);
|
||||
return ESP_GMF_ERR_OK;
|
||||
}
|
||||
|
||||
esp_gmf_err_t esp_gmf_io_i2s_pdm_init(i2s_pdm_io_cfg_t *config, esp_gmf_io_handle_t *io)
|
||||
{
|
||||
ESP_GMF_NULL_CHECK(TAG, config, {return ESP_GMF_ERR_INVALID_ARG;});
|
||||
ESP_GMF_NULL_CHECK(TAG, io, {return ESP_GMF_ERR_INVALID_ARG;});
|
||||
*io = NULL;
|
||||
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
|
||||
i2s_pdm_io_stream_t *i2s_pdm_io = esp_gmf_oal_calloc(1, sizeof(i2s_pdm_io_stream_t));
|
||||
ESP_GMF_MEM_VERIFY(TAG, i2s_pdm_io, {return ESP_GMF_ERR_MEMORY_LACK;},
|
||||
"I2s pdm stream", sizeof(i2s_pdm_io_stream_t));
|
||||
i2s_pdm_io->base.dir = config->dir;
|
||||
i2s_pdm_io->base.type = ESP_GMF_IO_TYPE_BYTE;
|
||||
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)i2s_pdm_io;
|
||||
obj->new_obj = _i2s_pdm_new;
|
||||
obj->del_obj = _i2s_pdm_delete;
|
||||
i2s_pdm_io_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(*config));
|
||||
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto _i2s_pdm_fail;},
|
||||
"I2s pdm stream configuration", sizeof(*config));
|
||||
memcpy(cfg, config, sizeof(*config));
|
||||
esp_gmf_obj_set_config(obj, cfg, sizeof(*config));
|
||||
ret = esp_gmf_obj_set_tag(obj, (config->name == NULL ? "io_i2s_pdm" : config->name));
|
||||
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto _i2s_pdm_fail, "Failed to set obj tag");
|
||||
i2s_pdm_io->base.close = _i2s_pdm_close;
|
||||
i2s_pdm_io->base.open = _i2s_pdm_open;
|
||||
i2s_pdm_io->base.seek = _i2s_pdm_seek;
|
||||
i2s_pdm_io->base.reset = _i2s_pdm_reset;
|
||||
esp_gmf_io_init(obj, NULL);
|
||||
if (cfg->dir == ESP_GMF_IO_DIR_WRITER) {
|
||||
i2s_pdm_io->base.acquire_write = _i2s_pdm_acquire_write;
|
||||
i2s_pdm_io->base.release_write = _i2s_pdm_release_write;
|
||||
} else if (cfg->dir == ESP_GMF_IO_DIR_READER) {
|
||||
i2s_pdm_io->base.acquire_read = _i2s_pdm_acquire_read;
|
||||
i2s_pdm_io->base.release_read = _i2s_pdm_release_read;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Does not set read or write function");
|
||||
ret = ESP_GMF_ERR_NOT_SUPPORT;
|
||||
goto _i2s_pdm_fail;
|
||||
}
|
||||
*io = obj;
|
||||
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), i2s_pdm_io);
|
||||
return ESP_GMF_ERR_OK;
|
||||
_i2s_pdm_fail:
|
||||
esp_gmf_obj_delete(obj);
|
||||
return ret;
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_log.h"
|
||||
#include "gzip_miniz.h"
|
||||
#include "miniz_inflate.h"
|
||||
|
||||
#define TAG "GZIP_MINIZ"
|
||||
#define GZIP_HEADER_SIZE (10)
|
||||
|
||||
typedef struct {
|
||||
gzip_miniz_cfg_t cfg;
|
||||
uint8_t *chunk_ptr;
|
||||
int chunk_filled;
|
||||
int chunk_consumed;
|
||||
bool unzip_error;
|
||||
bool first_data;
|
||||
bool final_data;
|
||||
int head_flag;
|
||||
int extra_len;
|
||||
int head_filled;
|
||||
mz_stream s;
|
||||
} gzip_miniz_t;
|
||||
|
||||
static bool verify_gzip_header(gzip_miniz_t *zip, uint8_t *data, int size)
|
||||
{
|
||||
if (size < GZIP_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (data[0] != 0x1F || data[1] != 0x8B || data[2] != 0x8) {
|
||||
return false;
|
||||
}
|
||||
zip->head_flag = data[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
static int gzip_miniz_skip_head(gzip_miniz_t *zip, uint8_t *data, int size)
|
||||
{
|
||||
if (zip->head_flag == 0) {
|
||||
return 0;
|
||||
}
|
||||
int org_size = size;
|
||||
if (zip->head_flag & 4) {
|
||||
// 2 bytes extra len
|
||||
if (zip->head_filled > 2) {
|
||||
return -1;
|
||||
}
|
||||
if (zip->head_filled + size >= 2) {
|
||||
int used = 2 - zip->head_filled;
|
||||
if (used) {
|
||||
size -= used;
|
||||
while (used--) {
|
||||
zip->extra_len = zip->extra_len + (*data << (zip->head_filled * 8));
|
||||
zip->head_filled++;
|
||||
data++;
|
||||
}
|
||||
}
|
||||
if (size >= zip->extra_len) {
|
||||
size -= zip->extra_len;
|
||||
data += zip->extra_len;
|
||||
zip->extra_len = 0;
|
||||
zip->head_flag &= ~4;
|
||||
zip->head_filled = 0;
|
||||
} else {
|
||||
zip->extra_len -= size;
|
||||
return org_size;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < size; i++) {
|
||||
zip->extra_len = zip->extra_len + (*data << (zip->head_filled * 8));
|
||||
zip->head_filled++;
|
||||
data++;
|
||||
}
|
||||
zip->head_filled += size;
|
||||
return org_size;
|
||||
}
|
||||
}
|
||||
if (zip->head_flag & 8) {
|
||||
// name
|
||||
while (size) {
|
||||
size--;
|
||||
data++;
|
||||
if (*(data - 1) == '\0') {
|
||||
zip->head_flag &= ~8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (size == 0) {
|
||||
return org_size;
|
||||
}
|
||||
}
|
||||
if (zip->head_flag & 0x10) {
|
||||
// comment
|
||||
while (size) {
|
||||
size--;
|
||||
data++;
|
||||
if (*(data - 1) == '\0') {
|
||||
zip->head_flag &= ~0x10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (size == 0) {
|
||||
return org_size;
|
||||
}
|
||||
}
|
||||
if (zip->head_flag & 0x2) {
|
||||
// CRC16
|
||||
if (zip->head_filled > 2) {
|
||||
return -1;
|
||||
}
|
||||
if (zip->head_filled + size >= 2) {
|
||||
int used = 2 - zip->head_filled;
|
||||
size -= used;
|
||||
data += used;
|
||||
zip->head_flag &= ~0x2;
|
||||
} else {
|
||||
zip->head_filled += size;
|
||||
return org_size;
|
||||
}
|
||||
}
|
||||
return org_size - size;
|
||||
}
|
||||
|
||||
gzip_miniz_handle_t gzip_miniz_init(gzip_miniz_cfg_t *cfg)
|
||||
{
|
||||
if (cfg->read_cb == NULL) {
|
||||
ESP_LOGE(TAG, "Read callback must be provided");
|
||||
return NULL;
|
||||
}
|
||||
gzip_miniz_t *zip = (gzip_miniz_t *) calloc(1, sizeof(gzip_miniz_t));
|
||||
if (zip == NULL) {
|
||||
ESP_LOGE(TAG, "No memory for instance");
|
||||
return NULL;
|
||||
}
|
||||
zip->cfg = *cfg;
|
||||
int chunk_size = cfg->chunk_size ? cfg->chunk_size : 32;
|
||||
zip->chunk_ptr = (uint8_t *) malloc(chunk_size);
|
||||
if (zip->chunk_ptr == NULL) {
|
||||
free(zip);
|
||||
ESP_LOGE(TAG, "No memory for chunk");
|
||||
return NULL;
|
||||
}
|
||||
zip->cfg.chunk_size = chunk_size;
|
||||
zip->first_data = true;
|
||||
mz_inflateInit2(&zip->s, -MZ_DEFAULT_WINDOW_BITS);
|
||||
return (gzip_miniz_handle_t)zip;
|
||||
}
|
||||
|
||||
int gzip_miniz_read(gzip_miniz_handle_t h, uint8_t *out, int out_size)
|
||||
{
|
||||
gzip_miniz_t *zip = (gzip_miniz_t *) h;
|
||||
if (zip == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (zip->unzip_error) {
|
||||
return -2;
|
||||
}
|
||||
if (zip->final_data) {
|
||||
return 0;
|
||||
}
|
||||
int size = 0;
|
||||
if (zip->first_data == true) {
|
||||
zip->first_data = false;
|
||||
int size = zip->cfg.read_cb(zip->chunk_ptr, zip->cfg.chunk_size, zip->cfg.ctx);
|
||||
if (size < 0) {
|
||||
zip->unzip_error = true;
|
||||
return -1;
|
||||
}
|
||||
zip->chunk_filled = size;
|
||||
if (verify_gzip_header(zip, zip->chunk_ptr, zip->chunk_filled) == false) {
|
||||
zip->unzip_error = true;
|
||||
ESP_LOGE(TAG, "Wrong data not match gzip header");
|
||||
return -1;
|
||||
}
|
||||
zip->chunk_consumed = GZIP_HEADER_SIZE;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
uint8_t *data = zip->chunk_ptr + zip->chunk_consumed;
|
||||
size = zip->chunk_filled - zip->chunk_consumed;
|
||||
if (size) {
|
||||
int skip = gzip_miniz_skip_head(zip, data, size);
|
||||
if (skip == 0) {
|
||||
break;
|
||||
}
|
||||
zip->chunk_consumed += skip;
|
||||
if (size > skip) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size = zip->cfg.read_cb(zip->chunk_ptr, zip->cfg.chunk_size, zip->cfg.ctx);
|
||||
if (size < 0) {
|
||||
zip->unzip_error = true;
|
||||
ESP_LOGE(TAG, "Fail to read data");
|
||||
return -1;
|
||||
}
|
||||
if (size == 0) {
|
||||
zip->final_data = true;
|
||||
return 0;
|
||||
}
|
||||
zip->chunk_filled = size;
|
||||
zip->chunk_consumed = 0;
|
||||
}
|
||||
|
||||
int org_size = out_size;
|
||||
// set input buffer
|
||||
mz_stream *s = &zip->s;
|
||||
s->next_in = zip->chunk_ptr + zip->chunk_consumed;
|
||||
s->avail_in = zip->chunk_filled - zip->chunk_consumed;
|
||||
s->next_out = out;
|
||||
s->avail_out = out_size;
|
||||
while (1) {
|
||||
int ret = mz_inflate(s, MZ_SYNC_FLUSH);
|
||||
if (ret < 0) {
|
||||
zip->unzip_error = true;
|
||||
ESP_LOGE(TAG, "Fail to inflate ret %d", ret);
|
||||
break;
|
||||
}
|
||||
int out_consume = out_size - s->avail_out;
|
||||
out += out_consume;
|
||||
out_size -= out_consume;
|
||||
if (ret == MZ_STREAM_END) {
|
||||
zip->final_data = true;
|
||||
break;
|
||||
}
|
||||
if (s->avail_out == 0) {
|
||||
// All output consumed
|
||||
int org_size = zip->chunk_filled - zip->chunk_consumed;
|
||||
zip->chunk_consumed += (org_size - s->avail_in);
|
||||
break;
|
||||
}
|
||||
if (s->avail_in == 0) {
|
||||
size = zip->cfg.read_cb(zip->chunk_ptr, zip->cfg.chunk_size, zip->cfg.ctx);
|
||||
if (size < 0) {
|
||||
zip->unzip_error = true;
|
||||
ESP_LOGE(TAG, "Fail to read data");
|
||||
return -1;
|
||||
}
|
||||
zip->chunk_filled = size;
|
||||
zip->chunk_consumed = 0;
|
||||
// Update pointer
|
||||
s->next_in = zip->chunk_ptr;
|
||||
s->avail_in = zip->chunk_filled - zip->chunk_consumed;
|
||||
s->next_out = out;
|
||||
s->avail_out = out_size;
|
||||
|
||||
} else {
|
||||
// Impossible case
|
||||
break;
|
||||
}
|
||||
}
|
||||
return org_size - out_size;
|
||||
}
|
||||
|
||||
int gzip_miniz_deinit(gzip_miniz_handle_t h)
|
||||
{
|
||||
gzip_miniz_t *zip = (gzip_miniz_t *) h;
|
||||
if (zip == NULL) {
|
||||
return -1;
|
||||
}
|
||||
mz_inflateEnd(&zip->s);
|
||||
free(zip->chunk_ptr);
|
||||
free(zip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gzip_miniz_zip(const uint8_t *input, size_t input_size, uint8_t *out, int out_size)
|
||||
{
|
||||
int pos = 0;
|
||||
const uint8_t header[10] = {0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03};
|
||||
memcpy(out, header, 10);
|
||||
pos += 10;
|
||||
|
||||
mz_stream stream = {0};
|
||||
if (mz_deflateInit2(&stream, MZ_BEST_COMPRESSION, MZ_DEFLATED, -MZ_DEFAULT_WINDOW_BITS, 1, MZ_DEFAULT_STRATEGY) != MZ_OK) {
|
||||
ESP_LOGE(TAG, "Failed to init deflate");
|
||||
return -1;
|
||||
}
|
||||
|
||||
stream.next_in = (const unsigned char *)input;
|
||||
stream.avail_in = input_size;
|
||||
int ret;
|
||||
do {
|
||||
stream.next_out = out + pos;
|
||||
stream.avail_out = out_size - pos;
|
||||
ret = mz_deflate(&stream, MZ_FINISH);
|
||||
if (ret != MZ_OK && ret != MZ_STREAM_END) {
|
||||
ESP_LOGE(TAG, "Failed to deflate ret %d", ret);
|
||||
mz_deflateEnd(&stream);
|
||||
return -2;
|
||||
}
|
||||
pos += (out_size - pos) - stream.avail_out;
|
||||
} while (ret != MZ_STREAM_END);
|
||||
// Clean up compression stream
|
||||
mz_deflateEnd(&stream);
|
||||
|
||||
uint32_t crc_value = mz_crc32(0, input, input_size);
|
||||
memcpy(out + pos, &crc_value, 4);
|
||||
pos += 4;
|
||||
memcpy(out + pos, &input_size, 4);
|
||||
pos += 4;
|
||||
return pos;
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
#ifndef _GZIP_MINIZ_H_
|
||||
#define _GZIP_MINIZ_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configuration for gzip using miniz library
|
||||
*/
|
||||
typedef struct {
|
||||
int (*read_cb)(uint8_t *data, int size, void *ctx); /*!< Read callback return size being read */
|
||||
int chunk_size; /*!< Chunk size default 32 if set to 0 */
|
||||
void *ctx; /*!< Read context */
|
||||
} gzip_miniz_cfg_t;
|
||||
|
||||
/**
|
||||
* @brief Handle for gzip
|
||||
*/
|
||||
typedef void *gzip_miniz_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize for gzip using miniz
|
||||
* @param cfg: Configuration for gzip using miniz
|
||||
* @return NULL: Input parameter wrong or no memory
|
||||
* Others: Handle for gzip inflate operation
|
||||
*/
|
||||
gzip_miniz_handle_t gzip_miniz_init(gzip_miniz_cfg_t *cfg);
|
||||
|
||||
/**
|
||||
* @brief Inflate and read data
|
||||
* @param out: Data to read after inflated
|
||||
* @param out_size: Data size to read
|
||||
* @return >= 0: Data size being read
|
||||
* -1: Wrong input parameter or wrong data
|
||||
* -2: Inflate error by miniz
|
||||
*/
|
||||
int gzip_miniz_read(gzip_miniz_handle_t zip, uint8_t *out, int out_size);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize gzip using miniz
|
||||
* @param zip: Handle for gzip
|
||||
* @return 0: On success
|
||||
* -1: Input parameter wrong
|
||||
*/
|
||||
int gzip_miniz_deinit(gzip_miniz_handle_t zip);
|
||||
|
||||
/**
|
||||
* @brief Zip data into gzip format
|
||||
* @param in: Data to be zipped
|
||||
* @param in_size: Data size to be zipped
|
||||
* @param in_size: Data size to be zipped
|
||||
* @param out: Store output gzip format data
|
||||
* @param out_size: Zipped data size
|
||||
* @return >= 0: On success
|
||||
* -1: Fail to init gzip deflate
|
||||
* -2: Fail to deflate
|
||||
*/
|
||||
int gzip_miniz_zip(const uint8_t* in, size_t in_size, uint8_t *out, int out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
#ifndef _MINIZ_INFLATE_H
|
||||
#define _MINIZ_INFLATE_H
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_idf_version.h"
|
||||
#if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR < 3)
|
||||
#include "esp32/rom/miniz.h"
|
||||
#elif (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0))
|
||||
#include "rom/miniz.h"
|
||||
#else
|
||||
#include "miniz.h"
|
||||
#endif
|
||||
|
||||
// Add the API missing in miniz of ROM code
|
||||
// Original code can be gotten from: https://github.com/richgel999/miniz/blob/master/miniz.c
|
||||
#ifdef MINIZ_NO_ZLIB_APIS
|
||||
|
||||
// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer
|
||||
// to the zlib docs).
|
||||
enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 };
|
||||
|
||||
// Return status codes. MZ_PARAM_ERROR is non-standard.
|
||||
enum {
|
||||
MZ_OK = 0,
|
||||
MZ_STREAM_END = 1,
|
||||
MZ_NEED_DICT = 2,
|
||||
MZ_ERRNO = -1,
|
||||
MZ_STREAM_ERROR = -2,
|
||||
MZ_DATA_ERROR = -3,
|
||||
MZ_MEM_ERROR = -4,
|
||||
MZ_BUF_ERROR = -5,
|
||||
MZ_VERSION_ERROR = -6,
|
||||
MZ_PARAM_ERROR = -10000
|
||||
};
|
||||
|
||||
// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and
|
||||
// may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL.
|
||||
enum {
|
||||
MZ_NO_COMPRESSION = 0,
|
||||
MZ_BEST_SPEED = 1,
|
||||
MZ_BEST_COMPRESSION = 9,
|
||||
MZ_UBER_COMPRESSION = 10,
|
||||
MZ_DEFAULT_LEVEL = 6,
|
||||
MZ_DEFAULT_COMPRESSION = -1
|
||||
};
|
||||
|
||||
// Window bits
|
||||
#define MZ_DEFAULT_WINDOW_BITS 15
|
||||
#define MZ_MIN(a, b) (a > b ? b : a)
|
||||
|
||||
struct mz_internal_state;
|
||||
|
||||
// Compression/decompression stream struct.
|
||||
typedef struct mz_stream_s {
|
||||
const unsigned char *next_in; // pointer to next byte to read
|
||||
unsigned int avail_in; // number of bytes available at next_in
|
||||
mz_ulong total_in; // total number of bytes consumed so far
|
||||
|
||||
unsigned char *next_out; // pointer to next byte to write
|
||||
unsigned int avail_out; // number of bytes that can be written to next_out
|
||||
mz_ulong total_out; // total number of bytes produced so far
|
||||
|
||||
char *msg; // error msg (unused)
|
||||
struct mz_internal_state *state; // internal state, allocated by zalloc/zfree
|
||||
|
||||
int data_type; // data_type (unused)
|
||||
mz_ulong adler; // adler32 of the source or uncompressed data
|
||||
mz_ulong crc32; // crc32 of the source or uncompressed data
|
||||
} mz_stream;
|
||||
|
||||
typedef mz_stream *mz_streamp;
|
||||
|
||||
typedef struct {
|
||||
tinfl_decompressor m_decomp;
|
||||
mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;
|
||||
int m_window_bits;
|
||||
mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
|
||||
tinfl_status m_last_status;
|
||||
} inflate_state;
|
||||
|
||||
static int mz_inflateInit2(mz_streamp pStream, int window_bits)
|
||||
{
|
||||
inflate_state *pDecomp;
|
||||
if (!pStream)
|
||||
return MZ_STREAM_ERROR;
|
||||
if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))
|
||||
return MZ_PARAM_ERROR;
|
||||
|
||||
pStream->data_type = 0;
|
||||
pStream->adler = 0;
|
||||
pStream->crc32 = 0;
|
||||
pStream->msg = NULL;
|
||||
pStream->total_in = 0;
|
||||
pStream->total_out = 0;
|
||||
|
||||
pDecomp = (inflate_state *) calloc(1, sizeof(inflate_state));
|
||||
if (!pDecomp)
|
||||
return MZ_MEM_ERROR;
|
||||
|
||||
pStream->state = (struct mz_internal_state *) pDecomp;
|
||||
|
||||
tinfl_init(&pDecomp->m_decomp);
|
||||
pDecomp->m_dict_ofs = 0;
|
||||
pDecomp->m_dict_avail = 0;
|
||||
pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
|
||||
pDecomp->m_first_call = 1;
|
||||
pDecomp->m_has_flushed = 0;
|
||||
pDecomp->m_window_bits = window_bits;
|
||||
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
static int mz_inflate(mz_streamp pStream, int flush)
|
||||
{
|
||||
inflate_state *pState;
|
||||
mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
|
||||
size_t in_bytes, out_bytes, orig_avail_in;
|
||||
tinfl_status status;
|
||||
|
||||
if ((!pStream) || (!pStream->state))
|
||||
return MZ_STREAM_ERROR;
|
||||
if (flush == MZ_PARTIAL_FLUSH)
|
||||
flush = MZ_SYNC_FLUSH;
|
||||
if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))
|
||||
return MZ_STREAM_ERROR;
|
||||
|
||||
pState = (inflate_state *) pStream->state;
|
||||
if (pState->m_window_bits > 0)
|
||||
decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
|
||||
orig_avail_in = pStream->avail_in;
|
||||
|
||||
first_call = pState->m_first_call;
|
||||
pState->m_first_call = 0;
|
||||
if (pState->m_last_status < 0)
|
||||
return MZ_DATA_ERROR;
|
||||
|
||||
if (pState->m_has_flushed && (flush != MZ_FINISH))
|
||||
return MZ_STREAM_ERROR;
|
||||
pState->m_has_flushed |= (flush == MZ_FINISH);
|
||||
|
||||
if ((flush == MZ_FINISH) && (first_call)) {
|
||||
// MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire
|
||||
// compressed/decompressed file.
|
||||
decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
|
||||
in_bytes = pStream->avail_in;
|
||||
out_bytes = pStream->avail_out;
|
||||
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out,
|
||||
&out_bytes, decomp_flags);
|
||||
pState->m_last_status = status;
|
||||
pStream->next_in += (mz_uint) in_bytes;
|
||||
pStream->avail_in -= (mz_uint) in_bytes;
|
||||
pStream->total_in += (mz_uint) in_bytes;
|
||||
pStream->adler = tinfl_get_adler32(&pState->m_decomp);
|
||||
pStream->next_out += (mz_uint) out_bytes;
|
||||
pStream->avail_out -= (mz_uint) out_bytes;
|
||||
pStream->total_out += (mz_uint) out_bytes;
|
||||
|
||||
if (status < 0)
|
||||
return MZ_DATA_ERROR;
|
||||
else if (status != TINFL_STATUS_DONE) {
|
||||
pState->m_last_status = TINFL_STATUS_FAILED;
|
||||
return MZ_BUF_ERROR;
|
||||
}
|
||||
return MZ_STREAM_END;
|
||||
}
|
||||
// flush != MZ_FINISH then we must assume there's more input.
|
||||
if (flush != MZ_FINISH)
|
||||
decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;
|
||||
|
||||
if (pState->m_dict_avail) {
|
||||
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
|
||||
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
|
||||
pStream->next_out += n;
|
||||
pStream->avail_out -= n;
|
||||
pStream->total_out += n;
|
||||
pState->m_dict_avail -= n;
|
||||
pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
|
||||
return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
in_bytes = pStream->avail_in;
|
||||
out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;
|
||||
|
||||
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict,
|
||||
pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
|
||||
pState->m_last_status = status;
|
||||
|
||||
pStream->next_in += (mz_uint) in_bytes;
|
||||
pStream->avail_in -= (mz_uint) in_bytes;
|
||||
pStream->total_in += (mz_uint) in_bytes;
|
||||
pStream->adler = tinfl_get_adler32(&pState->m_decomp);
|
||||
|
||||
pState->m_dict_avail = (mz_uint) out_bytes;
|
||||
|
||||
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
|
||||
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
|
||||
pStream->next_out += n;
|
||||
pStream->avail_out -= n;
|
||||
pStream->total_out += n;
|
||||
pState->m_dict_avail -= n;
|
||||
pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
|
||||
|
||||
if (status < 0)
|
||||
return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output
|
||||
// dictionary - oh well).
|
||||
else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
|
||||
return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by
|
||||
// setting flush to MZ_FINISH.
|
||||
else if (flush == MZ_FINISH) {
|
||||
// The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH.
|
||||
if (status == TINFL_STATUS_DONE)
|
||||
return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
|
||||
// status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If
|
||||
// there's no more room left in the output buffer then something is wrong.
|
||||
else if (!pStream->avail_out)
|
||||
return MZ_BUF_ERROR;
|
||||
} else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) ||
|
||||
(pState->m_dict_avail))
|
||||
break;
|
||||
}
|
||||
|
||||
return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
|
||||
}
|
||||
|
||||
static int mz_inflateEnd(mz_streamp pStream)
|
||||
{
|
||||
if (!pStream)
|
||||
return MZ_STREAM_ERROR;
|
||||
if (pStream->state) {
|
||||
free(pStream->state);
|
||||
pStream->state = NULL;
|
||||
}
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
static int mz_deflateEnd(mz_streamp pStream)
|
||||
{
|
||||
if (!pStream)
|
||||
return MZ_STREAM_ERROR;
|
||||
if (pStream->state)
|
||||
{
|
||||
free(pStream->state);
|
||||
pStream->state = NULL;
|
||||
}
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
static int tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
|
||||
{
|
||||
static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
|
||||
mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
|
||||
if (window_bits > 0)
|
||||
comp_flags |= TDEFL_WRITE_ZLIB_HEADER;
|
||||
|
||||
if (!level)
|
||||
comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
|
||||
else if (strategy == MZ_FILTERED)
|
||||
comp_flags |= TDEFL_FILTER_MATCHES;
|
||||
else if (strategy == MZ_HUFFMAN_ONLY)
|
||||
comp_flags &= ~TDEFL_MAX_PROBES_MASK;
|
||||
else if (strategy == MZ_FIXED)
|
||||
comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
|
||||
else if (strategy == MZ_RLE)
|
||||
comp_flags |= TDEFL_RLE_MATCHES;
|
||||
|
||||
return comp_flags;
|
||||
}
|
||||
|
||||
static int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
|
||||
{
|
||||
tdefl_compressor *pComp;
|
||||
mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);
|
||||
|
||||
if (!pStream)
|
||||
return MZ_STREAM_ERROR;
|
||||
if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))
|
||||
return MZ_PARAM_ERROR;
|
||||
|
||||
pStream->data_type = 0;
|
||||
pStream->adler = MZ_ADLER32_INIT;
|
||||
pStream->msg = NULL;
|
||||
pStream->total_in = 0;
|
||||
pStream->total_out = 0;
|
||||
pComp = (tdefl_compressor *)calloc(1, sizeof(tdefl_compressor));
|
||||
if (!pComp)
|
||||
return MZ_MEM_ERROR;
|
||||
|
||||
pStream->state = (struct mz_internal_state *)pComp;
|
||||
|
||||
if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
|
||||
{
|
||||
mz_deflateEnd(pStream);
|
||||
return MZ_PARAM_ERROR;
|
||||
}
|
||||
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
static int mz_deflate(mz_streamp pStream, int flush)
|
||||
{
|
||||
size_t in_bytes, out_bytes;
|
||||
mz_ulong orig_total_in, orig_total_out;
|
||||
int mz_status = MZ_OK;
|
||||
|
||||
if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))
|
||||
return MZ_STREAM_ERROR;
|
||||
if (!pStream->avail_out)
|
||||
return MZ_BUF_ERROR;
|
||||
|
||||
if (flush == MZ_PARTIAL_FLUSH)
|
||||
flush = MZ_SYNC_FLUSH;
|
||||
|
||||
if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
|
||||
return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;
|
||||
|
||||
orig_total_in = pStream->total_in;
|
||||
orig_total_out = pStream->total_out;
|
||||
for (;;)
|
||||
{
|
||||
tdefl_status defl_status;
|
||||
in_bytes = pStream->avail_in;
|
||||
out_bytes = pStream->avail_out;
|
||||
|
||||
defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
|
||||
pStream->next_in += (mz_uint)in_bytes;
|
||||
pStream->avail_in -= (mz_uint)in_bytes;
|
||||
pStream->total_in += (mz_uint)in_bytes;
|
||||
pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);
|
||||
|
||||
pStream->next_out += (mz_uint)out_bytes;
|
||||
pStream->avail_out -= (mz_uint)out_bytes;
|
||||
pStream->total_out += (mz_uint)out_bytes;
|
||||
|
||||
if (defl_status < 0)
|
||||
{
|
||||
mz_status = MZ_STREAM_ERROR;
|
||||
break;
|
||||
}
|
||||
else if (defl_status == TDEFL_STATUS_DONE)
|
||||
{
|
||||
mz_status = MZ_STREAM_END;
|
||||
break;
|
||||
}
|
||||
else if (!pStream->avail_out)
|
||||
break;
|
||||
else if ((!pStream->avail_in) && (flush != MZ_FINISH))
|
||||
{
|
||||
if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
|
||||
break;
|
||||
return MZ_BUF_ERROR; /* Can't make forward progress without some input.
|
||||
*/
|
||||
}
|
||||
}
|
||||
return mz_status;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
version: "0.7.6"
|
||||
description: Espressif GMF IO Module
|
||||
url: https://github.com/espressif/esp-gmf/tree/main/elements/gmf_io
|
||||
documentation: "https://github.com/espressif/esp-gmf/blob/main/elements/gmf_io/README.md"
|
||||
issues: "https://github.com/espressif/esp-gmf/issues"
|
||||
|
||||
tags:
|
||||
- "gmf"
|
||||
|
||||
dependencies:
|
||||
espressif/esp_codec_dev:
|
||||
require: "public"
|
||||
version: "^1.4"
|
||||
espressif/gmf_core:
|
||||
require: public
|
||||
version: "^0.7"
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_gmf_io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* @brief Espressif embed flash configurations, if any entry is zero then the configuration will be set to default values
|
||||
*/
|
||||
typedef struct {
|
||||
int max_files; /*!< IO direction, reader or writer */
|
||||
const char *name; /*!< Name for this instance */
|
||||
} embed_flash_io_cfg_t;
|
||||
|
||||
#define EMBED_FLASH_CFG_DEFAULT() { \
|
||||
.max_files = 200, \
|
||||
.name = NULL, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Embed information in flash
|
||||
*/
|
||||
typedef struct embed_item_info {
|
||||
const uint8_t *address; /*!< The corresponding address in flash */
|
||||
int size; /*!< Size of corresponding data */
|
||||
} embed_item_info_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes the embed flash stream I/O with the provided configuration
|
||||
*
|
||||
* @param[in] config Pointer to the embed flash IO configuration
|
||||
* @param[out] io Pointer to the embed flash IO handle to be initialized
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
|
||||
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_embed_flash_init(embed_flash_io_cfg_t *config, esp_gmf_io_handle_t *io);
|
||||
|
||||
/**
|
||||
* @brief Set the embed flash context
|
||||
* This function mainly provides information about embed flash data
|
||||
*
|
||||
* @param[in] io The embed flash element handle
|
||||
* @param[in] context The embed flash context
|
||||
* @param[in] max_num The number of embed flash context
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK
|
||||
* - ESP_GMF_ERR_FAIL
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_embed_flash_set_context(esp_gmf_io_handle_t io, const embed_item_info_t *context, int max_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_gmf_io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* @brief File IO configurations, if any entry is zero then the configuration will be set to default values
|
||||
*/
|
||||
typedef struct {
|
||||
int dir; /*!< IO direction, reader or writer */
|
||||
const char *name; /*!< Name for this instance */
|
||||
int cache_size; /*!< Cache size for file IO operations in bytes. If size <= 512, it will be set to 0.
|
||||
Note: Larger cache size will improve read and write performance but consume more memory */
|
||||
int cache_caps; /*!< Cache memory capabilities, if zero then it will be set to MALLOC_CAP_DMA.
|
||||
Note:
|
||||
1. If chips have SOC_SDMMC_PSRAM_DMA_CAPABLE capability(such as ESP32P4),
|
||||
then you can set (MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA) to save SRAM
|
||||
2. For ESP32, should use (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) or MALLOC_CAP_DMA caps to malloc cache
|
||||
3. For ESP32Sxx and ESP32Cxx, can also use MALLOC_CAP_INTERNAL caps to malloc cache */
|
||||
} file_io_cfg_t;
|
||||
|
||||
#define FILE_IO_CFG_DEFAULT() { \
|
||||
.dir = ESP_GMF_IO_DIR_READER, \
|
||||
.name = NULL, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the file stream I/O with the provided configuration
|
||||
*
|
||||
* @param[in] config Pointer to the file IO configuration
|
||||
* @param[out] io Pointer to the file IO handle to be initialized
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
|
||||
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_file_init(file_io_cfg_t *config, esp_gmf_io_handle_t *io);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_gmf_io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#define HTTP_STREAM_TASK_STACK (6 * 1024)
|
||||
#define HTTP_STREAM_TASK_CORE (0)
|
||||
#define HTTP_STREAM_TASK_PRIO (10)
|
||||
#define HTTP_STREAM_RINGBUFFER_SIZE (20 * 1024)
|
||||
|
||||
/**
|
||||
* @brief HTTP Stream hook type
|
||||
*/
|
||||
typedef enum {
|
||||
HTTP_STREAM_PRE_REQUEST = 0x01, /*!< The event handler will be called before HTTP Client making the connection to the server */
|
||||
HTTP_STREAM_ON_REQUEST, /*!< The event handler will be called when HTTP Client is requesting data,
|
||||
* If the function return the value (-1: ESP_GMF_ERR_FAIL), HTTP Client will be stopped
|
||||
* If the function return the value > 0, HTTP Stream will ignore the post_field
|
||||
* If the function return the value = 0, HTTP Stream continue send data from post_field (if any)
|
||||
*/
|
||||
HTTP_STREAM_ON_RESPONSE, /*!< The event handler will be called when HTTP Client is receiving data
|
||||
* If the function return the value (-1: ESP_GMF_ERR_FAIL), HTTP Client will be stopped
|
||||
* If the function return the value > 0, HTTP Stream will ignore the read function
|
||||
* If the function return the value = 0, HTTP Stream continue read data from HTTP Server
|
||||
*/
|
||||
HTTP_STREAM_POST_REQUEST, /*!< The event handler will be called after HTTP Client send header and body to the server, before fetching the headers */
|
||||
HTTP_STREAM_FINISH_REQUEST, /*!< The event handler will be called after HTTP Client fetch the header and ready to read HTTP body */
|
||||
} http_stream_event_id_t;
|
||||
|
||||
/**
|
||||
* @brief Stream event message
|
||||
*/
|
||||
typedef struct {
|
||||
http_stream_event_id_t event_id; /*!< Event ID */
|
||||
void *http_client; /*!< Reference to HTTP Client using by this HTTP Stream */
|
||||
void *buffer; /*!< Reference to Buffer using by the IO */
|
||||
int buffer_len; /*!< Length of buffer */
|
||||
void *user_data; /*!< User data context, from `http_io_cfg_t` */
|
||||
} http_stream_event_msg_t;
|
||||
|
||||
typedef int (*http_io_event_handle_t)(http_stream_event_msg_t *msg);
|
||||
|
||||
/**
|
||||
* @brief HTTP Stream configurations
|
||||
* Default value will be used if any entry is zero
|
||||
*/
|
||||
typedef struct {
|
||||
int task_stack; /*!< Task stack size */
|
||||
int task_core; /*!< Task running in core (0 or 1) */
|
||||
int task_prio; /*!< Task priority (based on freeRTOS priority) */
|
||||
bool stack_in_ext; /*!< Try to allocate stack in external memory */
|
||||
int dir; /*!< Type of stream */
|
||||
int out_buf_size; /*!< Size of output buffer */
|
||||
http_io_event_handle_t event_handle; /*!< The hook function for HTTP Stream */
|
||||
void *user_data; /*!< User data context */
|
||||
const char *cert_pem; /*!< SSL server certification, PEM format as string, if the client requires to verify server */
|
||||
esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification
|
||||
bundle for server verification, must be enabled in menuconfig */
|
||||
} http_io_cfg_t;
|
||||
|
||||
#define HTTP_STREAM_CFG_DEFAULT() { \
|
||||
.task_stack = HTTP_STREAM_TASK_STACK, \
|
||||
.task_core = HTTP_STREAM_TASK_CORE, \
|
||||
.task_prio = HTTP_STREAM_TASK_PRIO, \
|
||||
.stack_in_ext = true, \
|
||||
.dir = ESP_GMF_IO_DIR_READER, \
|
||||
.out_buf_size = HTTP_STREAM_RINGBUFFER_SIZE, \
|
||||
.event_handle = NULL, \
|
||||
.user_data = NULL, \
|
||||
.cert_pem = NULL, \
|
||||
.crt_bundle_attach = NULL, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the HTTP stream I/O with the specified configuration
|
||||
*
|
||||
* @param[in] config Pointer to an `http_io_cfg_t` structure containing the configuration
|
||||
* settings for the HTTP I/O
|
||||
* @param[out] io Pointer to a `esp_gmf_io_handle_t` where the initialized I/O object
|
||||
* handle will be stored
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Initialization successful
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid argument(s)
|
||||
* - ESP_GMF_ERR_MEMORY_LACK Insufficient memory for initialization
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_http_init(http_io_cfg_t *config, esp_gmf_io_handle_t *io);
|
||||
|
||||
/**
|
||||
* @brief Reset http information.
|
||||
*
|
||||
* This function can be used in event_handler of http IO.
|
||||
* User can call this function to connect to next track in playlist when he/she gets `HTTP_STREAM_FINISH_TRACK` event
|
||||
*
|
||||
* @param handle The http IO handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK On success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_http_reset(esp_gmf_io_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Set SSL server certification
|
||||
*
|
||||
* @note PEM format as string, if the client requires to verify server
|
||||
*
|
||||
* @param handle The http IO handle
|
||||
* @param cert Server certification
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK On success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_http_set_server_cert(esp_gmf_io_handle_t handle, const char *cert);
|
||||
|
||||
/**
|
||||
* @brief Set the event handle for the HTTP IO
|
||||
*
|
||||
* @param handle The http IO handle
|
||||
* @param event_callback The event callback handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK on success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_http_set_event_callback(esp_gmf_io_handle_t handle, http_io_event_handle_t event_callback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
*
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/i2s_pdm.h"
|
||||
#include "esp_gmf_io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* @brief I2S PDM IO configurations, if any entry is zero then the configuration will be set to default values
|
||||
*/
|
||||
typedef struct {
|
||||
i2s_chan_handle_t pdm_chan; /*!< I2S tx channel handler */
|
||||
int dir; /*!< IO direction, reader or writer */
|
||||
const char *name; /*!< Name for this instance */
|
||||
} i2s_pdm_io_cfg_t;
|
||||
|
||||
#define ESP_GMF_IO_I2S_PDM_CFG_DEFAULT() { \
|
||||
.pdm_chan = NULL, \
|
||||
.dir = ESP_GMF_IO_DIR_READER, \
|
||||
.name = NULL, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the I2S PDM I/O with the provided configuration
|
||||
*
|
||||
* @param[in] config Pointer to the file IO configuration
|
||||
* @param[out] io Pointer to the file IO handle to be initialized
|
||||
*
|
||||
* @return
|
||||
* - ESP_GMF_ERR_OK Success
|
||||
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
|
||||
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
|
||||
*/
|
||||
esp_gmf_err_t esp_gmf_io_i2s_pdm_init(i2s_pdm_io_cfg_t *config, esp_gmf_io_handle_t *io);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
#!/user/bin/env python3
|
||||
|
||||
# Espressif Modified MIT License
|
||||
#
|
||||
# Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD
|
||||
#
|
||||
# Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.
|
||||
# This includes the right to use, copy, modify, merge, publish, distribute, and sublicense
|
||||
# the Software, subject to the following conditions:
|
||||
#
|
||||
# 1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.
|
||||
# 2. The above copyright notice and this permission notice shall be included in all copies
|
||||
# or substantial portions of the Software.
|
||||
# 3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS
|
||||
# is strictly prohibited.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
# FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import logging
|
||||
from typing import Dict, Tuple, List
|
||||
from datetime import datetime
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
LICENSE_STR = f'''/*
|
||||
* SPDX-FileCopyrightText: {datetime.now().year} Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
'''
|
||||
|
||||
GEN_HEAD_FILE_NAME = 'esp_embed_tone.h'
|
||||
GEN_CMAKE_FILE_NAME = 'esp_embed_tone.cmake'
|
||||
SUPPORTED_EXTENSIONS = ('.wav', '.mp3', '.aac', '.m4a')
|
||||
|
||||
def sanitize_filename(filename: str) -> str:
|
||||
# Replace invalid characters with underscore
|
||||
sanitized = filename.replace('-', '_').replace('.', '_').replace(' ', '_')
|
||||
# Ensure it starts with a letter
|
||||
if sanitized[0].isdigit():
|
||||
sanitized = 'tone_' + sanitized
|
||||
return sanitized
|
||||
|
||||
def get_file_info(path: str) -> Dict[str, int]:
|
||||
try:
|
||||
file_list = [x for x in os.listdir(path) if x.lower().endswith(SUPPORTED_EXTENSIONS)]
|
||||
file_list.sort()
|
||||
if not file_list:
|
||||
logger.warning(f'No supported audio files found in {path}')
|
||||
return {}
|
||||
|
||||
file_info = {}
|
||||
for filename in file_list:
|
||||
try:
|
||||
size = os.path.getsize(os.path.join(path, filename))
|
||||
file_info[filename] = size
|
||||
logger.info(f'Found audio file: {filename} ({size} bytes)')
|
||||
except OSError as e:
|
||||
logger.error(f'Error getting size for {filename}: {e}')
|
||||
continue
|
||||
return file_info
|
||||
except OSError as e:
|
||||
logger.error(f'Error scanning directory {path}: {e}')
|
||||
return {}
|
||||
|
||||
def gen_h_file(file_info: Dict[str, int]) -> Tuple[str, str]:
|
||||
if not file_info:
|
||||
logger.error('No file information provided')
|
||||
return '', ''
|
||||
|
||||
# Header file content
|
||||
h_file = [
|
||||
'#pragma once',
|
||||
'',
|
||||
'/**',
|
||||
' * @brief Structure for embedding tone information',
|
||||
' */',
|
||||
'typedef struct {',
|
||||
' const uint8_t *address; /*!< Pointer to the embedded tone data */',
|
||||
' int size; /*!< Size of the tone data in bytes */',
|
||||
'} esp_embed_tone_t;',
|
||||
''
|
||||
]
|
||||
# CMake file content
|
||||
cmake_file = ['set(COMPONENT_EMBED_TXTFILES']
|
||||
# Generate content for each file
|
||||
enum_entries = []
|
||||
url_entries = []
|
||||
tone_entries = []
|
||||
for idx, (filename, size) in enumerate(file_info.items()):
|
||||
sanitized = sanitize_filename(filename)
|
||||
# Add external reference
|
||||
h_file.extend([
|
||||
'/**',
|
||||
f' * @brief External reference to embedded tone data: {filename}',
|
||||
' */',
|
||||
f'extern const uint8_t {sanitized}[] asm("_binary_{sanitized}_start");',
|
||||
''
|
||||
])
|
||||
# Add to CMake file
|
||||
cmake_file.append(f' {filename}')
|
||||
# Add tone entry
|
||||
tone_entries.append(
|
||||
f' [{idx}] = {{\n'
|
||||
f' .address = {sanitized},\n'
|
||||
f' .size = {size},\n'
|
||||
f' }},'
|
||||
)
|
||||
# Add enum entry
|
||||
enum_entries.append(
|
||||
f' ESP_EMBED_TONE_{sanitized.upper()} = {idx},'
|
||||
)
|
||||
# Add URL entry with hyphens replaced by underscores
|
||||
url_filename = filename.replace('-', '_')
|
||||
url_entries.append(
|
||||
f' "embed://tone/{idx}_{url_filename}",'
|
||||
)
|
||||
# Complete header file
|
||||
h_file.extend([
|
||||
'/**',
|
||||
' * @brief Array of embedded tone information',
|
||||
' */',
|
||||
'esp_embed_tone_t g_esp_embed_tone[] = {',
|
||||
*tone_entries,
|
||||
'};',
|
||||
'',
|
||||
'/**',
|
||||
' * @brief Enumeration for tone URLs',
|
||||
' */',
|
||||
'enum esp_embed_tone_index {',
|
||||
*enum_entries,
|
||||
f' ESP_EMBED_TONE_URL_MAX = {len(file_info)}',
|
||||
'};',
|
||||
'',
|
||||
'/**',
|
||||
' * @brief Array of tone URLs',
|
||||
' */',
|
||||
'const char * esp_embed_tone_url[] = {',
|
||||
*url_entries,
|
||||
'};',
|
||||
''
|
||||
])
|
||||
# Complete CMake file
|
||||
cmake_file.append(')')
|
||||
return '\n'.join(h_file), ''.join(cmake_file)
|
||||
|
||||
def write_file(content: str, filepath: str, is_header: bool = False) -> bool:
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
with open(filepath, 'w+') as f:
|
||||
if is_header:
|
||||
f.write(LICENSE_STR)
|
||||
f.write(content)
|
||||
logger.info(f'Successfully wrote {filepath}')
|
||||
return True
|
||||
except OSError as e:
|
||||
logger.error(f'Error writing to {filepath}: {e}')
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate embedded audio tone header and CMake files',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--path',
|
||||
type=str,
|
||||
required=True,
|
||||
help='Base folder containing audio files'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Enable verbose output'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# Get file information
|
||||
file_info = get_file_info(args.path)
|
||||
if not file_info:
|
||||
logger.error('No valid audio files found')
|
||||
sys.exit(1)
|
||||
# Generate files
|
||||
h_content, cmake_content = gen_h_file(file_info)
|
||||
# Write files
|
||||
h_path = os.path.join(args.path, GEN_HEAD_FILE_NAME)
|
||||
cmake_path = os.path.join(args.path, GEN_CMAKE_FILE_NAME)
|
||||
if not write_file(h_content, h_path, is_header=True):
|
||||
sys.exit(1)
|
||||
if not write_file(cmake_content, cmake_path):
|
||||
sys.exit(1)
|
||||
logger.info('Successfully generated embedded tone files')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user