1
This commit is contained in:
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,19 +0,0 @@
|
||||
tmp/
|
||||
components/
|
||||
managed_components/
|
||||
build/
|
||||
.vscode/
|
||||
.devcontainer/
|
||||
sdkconfig.old
|
||||
sdkconfig
|
||||
dependencies.lock
|
||||
.env
|
||||
releases/
|
||||
main/assets/lang_config.h
|
||||
main/mmap_generate_emoji.h
|
||||
.DS_Store
|
||||
.cache
|
||||
main/mmap_generate_emoji.h
|
||||
*.pyc
|
||||
*.bin
|
||||
mmap_generate_*.h
|
||||
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
3
managed_components/78__esp-ml307/.gitignore
vendored
3
managed_components/78__esp-ml307/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
dist/
|
||||
.component_hash
|
||||
.idea
|
||||
File diff suppressed because one or more lines are too long
@@ -1,33 +0,0 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"src/at_uart.cc"
|
||||
"src/at_modem.cc"
|
||||
"src/ec801e/ec801e_at_modem.cc"
|
||||
"src/ec801e/ec801e_tcp.cc"
|
||||
"src/ec801e/ec801e_ssl.cc"
|
||||
"src/ec801e/ec801e_udp.cc"
|
||||
"src/ec801e/ec801e_mqtt.cc"
|
||||
"src/ml307/ml307_at_modem.cc"
|
||||
"src/ml307/ml307_tcp.cc"
|
||||
"src/ml307/ml307_ssl.cc"
|
||||
"src/ml307/ml307_mqtt.cc"
|
||||
"src/ml307/ml307_udp.cc"
|
||||
"src/ml307/ml307_http.cc"
|
||||
"src/esp/esp_network.cc"
|
||||
"src/esp/esp_ssl.cc"
|
||||
"src/esp/esp_tcp.cc"
|
||||
"src/esp/esp_mqtt.cc"
|
||||
"src/esp/esp_udp.cc"
|
||||
"src/web_socket.cc"
|
||||
"src/http_client.cc"
|
||||
INCLUDE_DIRS
|
||||
"include"
|
||||
PRIV_INCLUDE_DIRS
|
||||
"."
|
||||
REQUIRES
|
||||
"esp_driver_gpio"
|
||||
"esp_driver_uart"
|
||||
"esp-tls"
|
||||
"pthread"
|
||||
"mqtt"
|
||||
)
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,408 +0,0 @@
|
||||
# ML307 / Quectel-E Series Cat.1 AT Modem (v3.0)
|
||||
|
||||
这是一个适用于 ML307R / EC801E / NT26K LTE Cat.1 模组的组件。
|
||||
本项目最初为 https://github.com/78/xiaozhi-esp32 项目创建。
|
||||
|
||||
## 🆕 版本 3.0 新特性
|
||||
|
||||
- **自动模组检测**: 自动识别 ML307 和 EC801E 模组
|
||||
- **统一接口**: 通过 `NetworkInterface` 基类提供一致的API
|
||||
- **智能内存管理**: 使用 `std::unique_ptr` 确保内存安全
|
||||
- **简化的API**: 更加直观和易用的接口设计
|
||||
|
||||
## 功能特性
|
||||
|
||||
- AT 命令
|
||||
- MQTT / MQTTS
|
||||
- HTTP / HTTPS
|
||||
- TCP / SSL TCP
|
||||
- UDP
|
||||
- WebSocket
|
||||
- 自动模组检测和初始化
|
||||
|
||||
## 支持的模组
|
||||
|
||||
- ML307R
|
||||
- ML307A
|
||||
- EC801E \*
|
||||
- NT26K \*
|
||||
|
||||
\* 需要在购买时咨询是否已烧录支持 SSL TCP 的固件
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基础用法
|
||||
|
||||
```cpp
|
||||
#include "esp_log.h"
|
||||
#include "at_modem.h"
|
||||
|
||||
static const char *TAG = "ML307_DEMO";
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
// 自动检测并初始化模组
|
||||
auto modem = AtModem::Detect(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15, 921600);
|
||||
|
||||
if (!modem) {
|
||||
ESP_LOGE(TAG, "模组检测失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置网络状态回调
|
||||
modem->OnNetworkStateChanged([](bool ready) {
|
||||
ESP_LOGI(TAG, "网络状态: %s", ready ? "已连接" : "已断开");
|
||||
});
|
||||
|
||||
// 等待网络就绪
|
||||
NetworkStatus status = modem->WaitForNetworkReady(30000);
|
||||
if (status != NetworkStatus::Ready) {
|
||||
ESP_LOGE(TAG, "网络连接失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 打印模组信息
|
||||
ESP_LOGI(TAG, "模组版本: %s", modem->GetModuleRevision().c_str());
|
||||
ESP_LOGI(TAG, "IMEI: %s", modem->GetImei().c_str());
|
||||
ESP_LOGI(TAG, "ICCID: %s", modem->GetIccid().c_str());
|
||||
ESP_LOGI(TAG, "运营商: %s", modem->GetCarrierName().c_str());
|
||||
ESP_LOGI(TAG, "信号强度: %d", modem->GetCsq());
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP 客户端
|
||||
|
||||
```cpp
|
||||
void TestHttp(std::unique_ptr<AtModem>& modem) {
|
||||
ESP_LOGI(TAG, "开始 HTTP 测试");
|
||||
|
||||
// 创建 HTTP 客户端
|
||||
auto http = modem->CreateHttp(0);
|
||||
|
||||
// 设置请求头
|
||||
http->SetHeader("User-Agent", "Xiaozhi/3.0.0");
|
||||
http->SetTimeout(10000);
|
||||
|
||||
// 发送 GET 请求
|
||||
if (http->Open("GET", "https://httpbin.org/json")) {
|
||||
ESP_LOGI(TAG, "HTTP 状态码: %d", http->GetStatusCode());
|
||||
ESP_LOGI(TAG, "响应内容长度: %zu bytes", http->GetBodyLength());
|
||||
|
||||
// 读取响应内容
|
||||
std::string response = http->ReadAll();
|
||||
ESP_LOGI(TAG, "响应内容: %s", response.c_str());
|
||||
|
||||
http->Close();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "HTTP 请求失败");
|
||||
}
|
||||
|
||||
// unique_ptr 会自动释放内存,无需手动 delete
|
||||
}
|
||||
```
|
||||
|
||||
### MQTT 客户端
|
||||
|
||||
```cpp
|
||||
void TestMqtt(std::unique_ptr<AtModem>& modem) {
|
||||
ESP_LOGI(TAG, "开始 MQTT 测试");
|
||||
|
||||
// 创建 MQTT 客户端
|
||||
auto mqtt = modem->CreateMqtt(0);
|
||||
|
||||
// 设置回调函数
|
||||
mqtt->OnConnected([]() {
|
||||
ESP_LOGI(TAG, "MQTT 连接成功");
|
||||
});
|
||||
|
||||
mqtt->OnDisconnected([]() {
|
||||
ESP_LOGI(TAG, "MQTT 连接断开");
|
||||
});
|
||||
|
||||
mqtt->OnMessage([](const std::string& topic, const std::string& payload) {
|
||||
ESP_LOGI(TAG, "收到消息 [%s]: %s", topic.c_str(), payload.c_str());
|
||||
});
|
||||
|
||||
// 连接到 MQTT 代理
|
||||
if (mqtt->Connect("broker.emqx.io", 1883, "esp32_client", "", "")) {
|
||||
// 订阅主题
|
||||
mqtt->Subscribe("test/esp32/message");
|
||||
|
||||
// 发布消息
|
||||
mqtt->Publish("test/esp32/hello", "Hello from ESP32!");
|
||||
|
||||
// 等待一段时间接收消息
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
|
||||
mqtt->Disconnect();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "MQTT 连接失败");
|
||||
}
|
||||
|
||||
// unique_ptr 会自动释放内存,无需手动 delete
|
||||
}
|
||||
```
|
||||
|
||||
### WebSocket 客户端
|
||||
|
||||
```cpp
|
||||
void TestWebSocket(std::unique_ptr<AtModem>& modem) {
|
||||
ESP_LOGI(TAG, "开始 WebSocket 测试");
|
||||
|
||||
// 创建 WebSocket 客户端
|
||||
auto ws = modem->CreateWebSocket(0);
|
||||
|
||||
// 设置请求头
|
||||
ws->SetHeader("Protocol-Version", "3");
|
||||
|
||||
// 设置回调函数
|
||||
ws->OnConnected([]() {
|
||||
ESP_LOGI(TAG, "WebSocket 连接成功");
|
||||
});
|
||||
|
||||
ws->OnData([](const char* data, size_t length, bool binary) {
|
||||
ESP_LOGI(TAG, "收到数据: %.*s", (int)length, data);
|
||||
});
|
||||
|
||||
ws->OnDisconnected([]() {
|
||||
ESP_LOGI(TAG, "WebSocket 连接断开");
|
||||
});
|
||||
|
||||
ws->OnError([](int error) {
|
||||
ESP_LOGE(TAG, "WebSocket 错误: %d", error);
|
||||
});
|
||||
|
||||
// 连接到 WebSocket 服务器
|
||||
if (ws->Connect("wss://echo.websocket.org/")) {
|
||||
// 发送消息
|
||||
for (int i = 0; i < 5; i++) {
|
||||
std::string message = "{\"type\": \"ping\", \"id\": " + std::to_string(i) + "}";
|
||||
ws->Send(message);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
ws->Close();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WebSocket 连接失败");
|
||||
}
|
||||
|
||||
// unique_ptr 会自动释放内存,无需手动 delete
|
||||
}
|
||||
```
|
||||
|
||||
### TCP 客户端
|
||||
|
||||
```cpp
|
||||
void TestTcp(std::unique_ptr<AtModem>& modem) {
|
||||
ESP_LOGI(TAG, "开始 TCP 测试");
|
||||
|
||||
// 创建 TCP 客户端
|
||||
auto tcp = modem->CreateTcp(0);
|
||||
|
||||
// 设置数据接收回调
|
||||
tcp->OnStream([](const std::string& data) {
|
||||
ESP_LOGI(TAG, "TCP 接收数据: %s", data.c_str());
|
||||
});
|
||||
|
||||
// 设置断开连接回调
|
||||
tcp->OnDisconnected([]() {
|
||||
ESP_LOGI(TAG, "TCP 连接已断开");
|
||||
});
|
||||
|
||||
if (tcp->Connect("httpbin.org", 80)) {
|
||||
// 发送 HTTP 请求
|
||||
std::string request = "GET /ip HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n";
|
||||
int sent = tcp->Send(request);
|
||||
ESP_LOGI(TAG, "TCP 发送了 %d 字节", sent);
|
||||
|
||||
// 等待接收响应(通过回调处理)
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
|
||||
tcp->Disconnect();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "TCP 连接失败");
|
||||
}
|
||||
|
||||
// unique_ptr 会自动释放内存,无需手动 delete
|
||||
}
|
||||
```
|
||||
|
||||
### UDP 客户端
|
||||
|
||||
```cpp
|
||||
void TestUdp(std::unique_ptr<AtModem>& modem) {
|
||||
ESP_LOGI(TAG, "开始 UDP 测试");
|
||||
|
||||
// 创建 UDP 客户端
|
||||
auto udp = modem->CreateUdp(0);
|
||||
|
||||
// 设置数据接收回调
|
||||
udp->OnMessage([](const std::string& data) {
|
||||
ESP_LOGI(TAG, "UDP 接收数据: %s", data.c_str());
|
||||
});
|
||||
|
||||
// 连接到 UDP 服务器
|
||||
if (udp->Connect("8.8.8.8", 53)) {
|
||||
// 发送简单的测试数据
|
||||
std::string test_data = "Hello UDP Server!";
|
||||
int sent = udp->Send(test_data);
|
||||
ESP_LOGI(TAG, "UDP 发送了 %d 字节", sent);
|
||||
|
||||
// 等待接收响应(通过回调处理)
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
|
||||
udp->Disconnect();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "UDP 连接失败");
|
||||
}
|
||||
|
||||
// unique_ptr 会自动释放内存,无需手动 delete
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 直接访问 AtUart
|
||||
|
||||
```cpp
|
||||
void DirectAtCommand(std::unique_ptr<AtModem>& modem) {
|
||||
// 获取共享的 AtUart 实例
|
||||
auto uart = modem->GetAtUart();
|
||||
|
||||
// 发送自定义 AT 命令
|
||||
if (uart->SendCommand("AT+CSQ", 1000)) {
|
||||
std::string response = uart->GetResponse();
|
||||
ESP_LOGI(TAG, "信号强度查询结果: %s", response.c_str());
|
||||
}
|
||||
|
||||
// 可以在多个地方安全地持有 uart 引用
|
||||
std::shared_ptr<AtUart> my_uart = modem->GetAtUart();
|
||||
// my_uart 可以在其他线程或对象中安全使用
|
||||
}
|
||||
```
|
||||
|
||||
### 网络状态监控
|
||||
|
||||
```cpp
|
||||
void MonitorNetwork(std::unique_ptr<AtModem>& modem) {
|
||||
// 监控网络状态变化
|
||||
modem->OnNetworkStateChanged([&modem](bool ready) {
|
||||
if (ready) {
|
||||
ESP_LOGI(TAG, "网络已就绪");
|
||||
ESP_LOGI(TAG, "信号强度: %d", modem->GetCsq());
|
||||
|
||||
auto reg_state = modem->GetRegistrationState();
|
||||
ESP_LOGI(TAG, "注册状态: %s", reg_state.ToString().c_str());
|
||||
} else {
|
||||
ESP_LOGE(TAG, "网络连接丢失");
|
||||
}
|
||||
});
|
||||
|
||||
// 检查网络状态
|
||||
if (modem->network_ready()) {
|
||||
ESP_LOGI(TAG, "当前网络状态: 已连接");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "当前网络状态: 未连接");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 提前释放网络对象
|
||||
|
||||
```cpp
|
||||
void EarlyReleaseExample(std::unique_ptr<AtModem>& modem) {
|
||||
// 创建 HTTP 客户端
|
||||
auto http = modem->CreateHttp(0);
|
||||
|
||||
// 使用完毕后提前释放
|
||||
http->Close();
|
||||
http.reset(); // 显式释放内存
|
||||
|
||||
// 或者让 unique_ptr 在作用域结束时自动释放
|
||||
{
|
||||
auto tcp = modem->CreateTcp(0);
|
||||
tcp->Connect("example.com", 80);
|
||||
// 作用域结束时 tcp 自动释放
|
||||
}
|
||||
|
||||
// 此时 tcp 已经自动释放,可以创建新的连接
|
||||
auto udp = modem->CreateUdp(0);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
```cpp
|
||||
void HandleErrors(std::unique_ptr<AtModem>& modem) {
|
||||
// 等待网络就绪,处理各种错误情况
|
||||
NetworkStatus status = modem->WaitForNetworkReady(30000);
|
||||
|
||||
switch (status) {
|
||||
case NetworkStatus::Ready:
|
||||
ESP_LOGI(TAG, "网络连接成功");
|
||||
break;
|
||||
case NetworkStatus::ErrorInsertPin:
|
||||
ESP_LOGE(TAG, "SIM 卡未插入或 PIN 码错误");
|
||||
break;
|
||||
case NetworkStatus::ErrorRegistrationDenied:
|
||||
ESP_LOGE(TAG, "网络注册被拒绝");
|
||||
break;
|
||||
case NetworkStatus::ErrorTimeout:
|
||||
ESP_LOGE(TAG, "网络连接超时");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "未知网络错误");
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 迁移指南 (v2.x → v3.0)
|
||||
|
||||
### 旧版本 (v2.x)
|
||||
|
||||
```cpp
|
||||
// 旧方式:需要明确指定模组类型和GPIO引脚
|
||||
Ml307AtModem modem(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15);
|
||||
NetworkStatus status = modem.WaitForNetworkReady();
|
||||
|
||||
Ml307Http http(modem);
|
||||
http.Open("GET", "https://example.com");
|
||||
```
|
||||
|
||||
### 新版本 (v3.0)
|
||||
|
||||
```cpp
|
||||
// 新方式:自动检测模组类型,使用智能指针管理内存
|
||||
auto modem = AtModem::Detect(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15);
|
||||
NetworkStatus status = modem->WaitForNetworkReady();
|
||||
|
||||
auto http = modem->CreateHttp(0);
|
||||
http->Open("GET", "https://example.com");
|
||||
// 无需手动 delete,unique_ptr 自动管理内存
|
||||
```
|
||||
|
||||
## 架构优势
|
||||
|
||||
1. **自动化**: 无需手动指定模组类型,提高代码通用性
|
||||
2. **统一接口**: 不同模组使用相同的API
|
||||
3. **代码复用**: 避免重复实现相同功能
|
||||
4. **易于维护**: 公共逻辑集中管理
|
||||
5. **扩展性**: 便于添加新的模组类型支持
|
||||
6. **内存安全**: `std::unique_ptr` 提供自动内存管理,避免内存泄漏
|
||||
7. **线程安全**: 支持多线程安全访问
|
||||
8. **RAII 原则**: 资源获取即初始化,作用域结束时自动释放
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 构造函数已变化,现在使用 `AtModem::Detect()` 方法
|
||||
2. 协议客户端需要通过 `CreateXxx()` 方法创建,返回 `std::unique_ptr`
|
||||
3. **无需手动 delete**,`std::unique_ptr` 会自动管理内存
|
||||
4. 网络状态通过回调函数异步通知
|
||||
5. `GetAtUart()` 返回 `shared_ptr<AtUart>`,支持安全共享
|
||||
6. 如果需要提前释放网络对象,可以调用 `.reset()` 方法
|
||||
7. 所有网络接口方法现在都有默认参数 `connect_id = -1`
|
||||
|
||||
## 作者
|
||||
|
||||
- 虾哥 Terrence (terrence@tenclass.com)
|
||||
@@ -1,11 +0,0 @@
|
||||
dependencies:
|
||||
idf: '>=5.3'
|
||||
description: ESP32 ML307 / EC801E / NT26K Cat.1 Cellular Module
|
||||
files:
|
||||
exclude:
|
||||
- .git
|
||||
- dist
|
||||
license: MIT
|
||||
repository: https://github.com/78/esp-ml307
|
||||
url: https://github.com/78/esp-ml307
|
||||
version: 3.3.3
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef _AT_MODEM_H_
|
||||
#define _AT_MODEM_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/uart.h>
|
||||
#include "at_uart.h"
|
||||
#include "network_interface.h"
|
||||
|
||||
#define AT_EVENT_PIN_ERROR BIT2
|
||||
#define AT_EVENT_NETWORK_ERROR BIT3
|
||||
#define AT_EVENT_NETWORK_READY BIT4
|
||||
|
||||
enum class NetworkStatus {
|
||||
ErrorInsertPin = -1,
|
||||
ErrorRegistrationDenied = -2,
|
||||
ErrorTimeout = -3,
|
||||
Ready = 0,
|
||||
Error = 1,
|
||||
};
|
||||
|
||||
struct CeregState {
|
||||
int stat = 0; // <stat>
|
||||
std::string tac; // <tac>
|
||||
std::string ci; // <ci>
|
||||
int AcT = -1; // <AcT>
|
||||
|
||||
std::string ToString() const {
|
||||
std::string json = "{";
|
||||
json += "\"stat\":" + std::to_string(stat);
|
||||
if (!tac.empty()) json += ",\"tac\":\"" + tac + "\"";
|
||||
if (!ci.empty()) json += ",\"ci\":\"" + ci + "\"";
|
||||
if (AcT >= 0) json += ",\"AcT\":" + std::to_string(AcT);
|
||||
json += "}";
|
||||
return json;
|
||||
}
|
||||
};
|
||||
|
||||
class AtModem : public NetworkInterface {
|
||||
public:
|
||||
// 静态检测方法
|
||||
static std::unique_ptr<AtModem> Detect(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC, int baud_rate = 115200);
|
||||
|
||||
// 构造函数和析构函数
|
||||
AtModem(std::shared_ptr<AtUart> at_uart);
|
||||
virtual ~AtModem();
|
||||
std::shared_ptr<AtUart> GetAtUart() { return at_uart_; }
|
||||
void OnNetworkStateChanged(std::function<void(bool network_ready)> callback);
|
||||
|
||||
// 网络状态管理
|
||||
virtual void Reboot();
|
||||
virtual NetworkStatus WaitForNetworkReady(int timeout_ms = -1);
|
||||
virtual bool SetSleepMode(bool enable, int delay_seconds=0);
|
||||
virtual void SetFlightMode(bool enable);
|
||||
|
||||
// 模组信息获取
|
||||
std::string GetImei();
|
||||
std::string GetIccid();
|
||||
std::string GetModuleRevision();
|
||||
CeregState GetRegistrationState();
|
||||
std::string GetCarrierName();
|
||||
int GetCsq();
|
||||
|
||||
// 状态查询
|
||||
bool pin_ready() const { return pin_ready_; }
|
||||
bool network_ready() const { return network_ready_; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
std::mutex mutex_;
|
||||
std::string iccid_;
|
||||
std::string imei_;
|
||||
std::string carrier_name_;
|
||||
std::string module_revision_;
|
||||
int csq_ = -1;
|
||||
bool pin_ready_ = true;
|
||||
bool network_ready_ = false;
|
||||
|
||||
gpio_num_t dtr_pin_;
|
||||
EventGroupHandle_t event_group_handle_ = nullptr;
|
||||
|
||||
CeregState cereg_state_;
|
||||
|
||||
virtual void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments);
|
||||
|
||||
std::function<void(bool network_state)> on_network_state_changed_;
|
||||
};
|
||||
|
||||
#endif // _AT_MODEM_H_
|
||||
@@ -1,123 +0,0 @@
|
||||
#ifndef _AT_UART_H_
|
||||
#define _AT_UART_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/uart.h>
|
||||
|
||||
// UART事件定义
|
||||
#define AT_EVENT_DATA_AVAILABLE BIT1
|
||||
#define AT_EVENT_COMMAND_DONE BIT2
|
||||
#define AT_EVENT_COMMAND_ERROR BIT3
|
||||
|
||||
// 默认配置
|
||||
#define UART_NUM UART_NUM_1
|
||||
|
||||
// AT命令参数值结构
|
||||
struct AtArgumentValue {
|
||||
enum class Type { String, Int, Double };
|
||||
Type type;
|
||||
std::string string_value;
|
||||
int int_value;
|
||||
double double_value;
|
||||
|
||||
std::string ToString() const {
|
||||
switch (type) {
|
||||
case Type::String:
|
||||
return "\"" + string_value + "\"";
|
||||
case Type::Int:
|
||||
return std::to_string(int_value);
|
||||
case Type::Double:
|
||||
return std::to_string(double_value);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 数据接收回调函数类型
|
||||
typedef std::function<void(const std::string& command, const std::vector<AtArgumentValue>& arguments)> UrcCallback;
|
||||
|
||||
class AtUart {
|
||||
public:
|
||||
// 构造函数
|
||||
AtUart(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
|
||||
~AtUart();
|
||||
|
||||
// 初始化和配置
|
||||
void Initialize();
|
||||
|
||||
// 波特率管理
|
||||
bool SetBaudRate(int new_baud_rate);
|
||||
int GetBaudRate() const { return baud_rate_; }
|
||||
|
||||
// 数据发送
|
||||
bool SendCommand(const std::string& command, size_t timeout_ms = 1000, bool add_crlf = true);
|
||||
bool SendCommandWithData(const std::string& command, size_t timeout_ms = 1000, bool add_crlf = true, const char* data = nullptr, size_t data_length = 0);
|
||||
const std::string& GetResponse() const { return response_; }
|
||||
int GetCmeErrorCode() const { return cme_error_code_; }
|
||||
|
||||
// 回调管理
|
||||
std::list<UrcCallback>::iterator RegisterUrcCallback(UrcCallback callback);
|
||||
void UnregisterUrcCallback(std::list<UrcCallback>::iterator iterator);
|
||||
|
||||
// 控制接口
|
||||
void SetDtrPin(bool high);
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
std::string EncodeHex(const std::string& data);
|
||||
std::string DecodeHex(const std::string& data);
|
||||
void EncodeHexAppend(std::string& dest, const char* data, size_t length);
|
||||
void DecodeHexAppend(std::string& dest, const char* data, size_t length);
|
||||
|
||||
private:
|
||||
// 配置参数
|
||||
gpio_num_t tx_pin_;
|
||||
gpio_num_t rx_pin_;
|
||||
gpio_num_t dtr_pin_;
|
||||
uart_port_t uart_num_;
|
||||
int baud_rate_;
|
||||
bool initialized_;
|
||||
int cme_error_code_ = 0;
|
||||
std::string response_;
|
||||
bool wait_for_response_ = false;
|
||||
std::mutex command_mutex_;
|
||||
std::mutex mutex_;
|
||||
|
||||
// FreeRTOS 对象
|
||||
TaskHandle_t event_task_handle_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
QueueHandle_t event_queue_handle_;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
|
||||
std::string rx_buffer_;
|
||||
|
||||
// 回调函数
|
||||
std::list<UrcCallback> urc_callbacks_;
|
||||
|
||||
// 内部方法
|
||||
void EventTask();
|
||||
void ReceiveTask();
|
||||
bool ParseResponse();
|
||||
bool DetectBaudRate();
|
||||
// 处理 AT 命令
|
||||
void HandleCommand(const char* command);
|
||||
// 处理 URC
|
||||
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments);
|
||||
bool SendData(const char* data, size_t length);
|
||||
|
||||
// 静态任务函数
|
||||
static void EventTaskWrapper(void* arg);
|
||||
};
|
||||
|
||||
#endif // _AT_UART_H_
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef ESP_NETWORK_H
|
||||
#define ESP_NETWORK_H
|
||||
|
||||
#include "network_interface.h"
|
||||
|
||||
class EspNetwork : public NetworkInterface {
|
||||
public:
|
||||
EspNetwork();
|
||||
~EspNetwork();
|
||||
|
||||
std::unique_ptr<Http> CreateHttp(int connect_id = -1) override;
|
||||
std::unique_ptr<Tcp> CreateTcp(int connect_id = -1) override;
|
||||
std::unique_ptr<Tcp> CreateSsl(int connect_id = -1) override;
|
||||
std::unique_ptr<Udp> CreateUdp(int connect_id = -1) override;
|
||||
std::unique_ptr<Mqtt> CreateMqtt(int connect_id = -1) override;
|
||||
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id = -1) override;
|
||||
};
|
||||
|
||||
#endif // ESP_NETWORK_H
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef HTTP_H
|
||||
#define HTTP_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
class Http {
|
||||
public:
|
||||
virtual ~Http() = default;
|
||||
|
||||
// Set timeout
|
||||
virtual void SetTimeout(int timeout_ms) = 0;
|
||||
|
||||
// 设置 HTTP 请求头
|
||||
virtual void SetHeader(const std::string& key, const std::string& value) = 0;
|
||||
|
||||
// 设置 HTTP Content
|
||||
virtual void SetContent(std::string&& content) = 0;
|
||||
|
||||
// 打开 HTTP 连接并发送请求
|
||||
virtual bool Open(const std::string& method, const std::string& url) = 0;
|
||||
|
||||
// 关闭 HTTP 连接
|
||||
virtual void Close() = 0;
|
||||
|
||||
// 读取 HTTP 响应数据
|
||||
virtual int Read(char* buffer, size_t buffer_size) = 0;
|
||||
|
||||
// 写入 HTTP 请求数据
|
||||
virtual int Write(const char* buffer, size_t buffer_size) = 0;
|
||||
|
||||
// 获取 HTTP 响应状态码
|
||||
virtual int GetStatusCode() = 0;
|
||||
|
||||
// 获取指定 key 的 HTTP 响应头
|
||||
virtual std::string GetResponseHeader(const std::string& key) const = 0;
|
||||
|
||||
// 获取 HTTP 响应体长度
|
||||
virtual size_t GetBodyLength() = 0;
|
||||
|
||||
// 获取 HTTP 响应体
|
||||
virtual std::string ReadAll() = 0;
|
||||
};
|
||||
|
||||
#endif // HTTP_H
|
||||
@@ -1,156 +0,0 @@
|
||||
#ifndef HTTP_CLIENT_H
|
||||
#define HTTP_CLIENT_H
|
||||
|
||||
#include "http.h"
|
||||
#include "tcp.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <cstring>
|
||||
|
||||
#define EC801E_HTTP_EVENT_HEADERS_RECEIVED (1 << 0)
|
||||
#define EC801E_HTTP_EVENT_BODY_RECEIVED (1 << 1)
|
||||
#define EC801E_HTTP_EVENT_ERROR (1 << 2)
|
||||
#define EC801E_HTTP_EVENT_COMPLETE (1 << 3)
|
||||
|
||||
class NetworkInterface;
|
||||
|
||||
class HttpClient : public Http {
|
||||
public:
|
||||
HttpClient(NetworkInterface* network, int connect_id = 0);
|
||||
~HttpClient();
|
||||
|
||||
void SetTimeout(int timeout_ms) override;
|
||||
void SetHeader(const std::string& key, const std::string& value) override;
|
||||
void SetContent(std::string&& content) override;
|
||||
bool Open(const std::string& method, const std::string& url) override;
|
||||
void Close() override;
|
||||
int Read(char* buffer, size_t buffer_size) override;
|
||||
int Write(const char* buffer, size_t buffer_size) override;
|
||||
|
||||
int GetStatusCode() override;
|
||||
std::string GetResponseHeader(const std::string& key) const override;
|
||||
size_t GetBodyLength() override;
|
||||
std::string ReadAll() override;
|
||||
|
||||
private:
|
||||
// 数据块结构,用于队列缓冲
|
||||
struct DataChunk {
|
||||
std::string data;
|
||||
size_t offset = 0; // 当前读取偏移
|
||||
|
||||
// 使用移动构造函数避免拷贝
|
||||
DataChunk(std::string&& d) : data(std::move(d)), offset(0) {}
|
||||
DataChunk(const std::string& d) : data(d), offset(0) {}
|
||||
|
||||
size_t available() const {
|
||||
return data.size() - offset;
|
||||
}
|
||||
|
||||
size_t read(char* buffer, size_t size) {
|
||||
size_t bytes_to_read = std::min(size, available());
|
||||
if (bytes_to_read > 0) {
|
||||
memcpy(buffer, data.data() + offset, bytes_to_read);
|
||||
offset += bytes_to_read;
|
||||
}
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return offset >= data.size();
|
||||
}
|
||||
};
|
||||
|
||||
// 头部条目结构体,用于高效存储和查找
|
||||
struct HeaderEntry {
|
||||
std::string original_key; // 保留原始大小写的key(用于输出HTTP头部)
|
||||
std::string value; // 头部值
|
||||
|
||||
HeaderEntry() = default;
|
||||
HeaderEntry(const std::string& key, const std::string& val)
|
||||
: original_key(key), value(val) {}
|
||||
};
|
||||
|
||||
NetworkInterface* network_;
|
||||
int connect_id_;
|
||||
std::unique_ptr<Tcp> tcp_;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
|
||||
// 用于读取操作的专门锁和缓冲区队列
|
||||
std::mutex read_mutex_;
|
||||
std::deque<DataChunk> body_chunks_;
|
||||
std::condition_variable write_cv_;
|
||||
const size_t MAX_BODY_CHUNKS_SIZE = 8192;
|
||||
|
||||
int status_code_ = -1;
|
||||
int timeout_ms_ = 30000;
|
||||
std::string rx_buffer_;
|
||||
std::map<std::string, HeaderEntry> headers_; // key为小写,用于快速查找
|
||||
std::string url_;
|
||||
std::string method_;
|
||||
std::string protocol_;
|
||||
std::string host_;
|
||||
std::string path_;
|
||||
int port_ = 80;
|
||||
std::optional<std::string> content_ = std::nullopt;
|
||||
std::map<std::string, HeaderEntry> response_headers_; // key为小写,用于快速查找
|
||||
|
||||
// 移除原来的 body_ 变量,现在使用 body_chunks_ 队列
|
||||
size_t body_offset_ = 0;
|
||||
size_t content_length_ = 0;
|
||||
size_t total_body_received_ = 0; // 总共接收的响应体字节数
|
||||
bool eof_ = false;
|
||||
bool connected_ = false;
|
||||
bool headers_received_ = false;
|
||||
bool request_chunked_ = false;
|
||||
bool response_chunked_ = false;
|
||||
bool connection_error_ = false; // 新增:标记连接是否异常断开
|
||||
|
||||
// HTTP 协议解析状态
|
||||
enum class ParseState {
|
||||
STATUS_LINE,
|
||||
HEADERS,
|
||||
BODY,
|
||||
CHUNK_SIZE,
|
||||
CHUNK_DATA,
|
||||
CHUNK_TRAILER,
|
||||
COMPLETE
|
||||
};
|
||||
ParseState parse_state_ = ParseState::STATUS_LINE;
|
||||
size_t chunk_size_ = 0;
|
||||
size_t chunk_received_ = 0;
|
||||
|
||||
// 私有方法
|
||||
bool ParseUrl(const std::string& url);
|
||||
std::string BuildHttpRequest();
|
||||
void OnTcpData(const std::string& data);
|
||||
void OnTcpDisconnected();
|
||||
void ProcessReceivedData();
|
||||
bool ParseStatusLine(const std::string& line);
|
||||
bool ParseHeaderLine(const std::string& line);
|
||||
void ParseChunkedBody(const std::string& data);
|
||||
void ParseRegularBody(const std::string& data);
|
||||
size_t ParseChunkSize(const std::string& chunk_size_line);
|
||||
std::string GetNextLine(std::string& buffer);
|
||||
bool HasCompleteLine(const std::string& buffer);
|
||||
void SetError();
|
||||
|
||||
// 新增:向读取队列添加数据的方法
|
||||
void AddBodyData(const std::string& data);
|
||||
void AddBodyData(std::string&& data); // 移动版本
|
||||
|
||||
// 新增:检查数据是否完整接收
|
||||
bool IsDataComplete() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef MQTT_INTERFACE_H
|
||||
#define MQTT_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class Mqtt {
|
||||
public:
|
||||
virtual ~Mqtt() {}
|
||||
|
||||
void SetKeepAlive(int keep_alive_seconds) { keep_alive_seconds_ = keep_alive_seconds; }
|
||||
virtual bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) = 0;
|
||||
virtual void Disconnect() = 0;
|
||||
virtual bool Publish(const std::string topic, const std::string payload, int qos = 0) = 0;
|
||||
virtual bool Subscribe(const std::string topic, int qos = 0) = 0;
|
||||
virtual bool Unsubscribe(const std::string topic) = 0;
|
||||
virtual bool IsConnected() = 0;
|
||||
|
||||
virtual void OnConnected(std::function<void()> callback) { on_connected_callback_ = std::move(callback); }
|
||||
virtual void OnDisconnected(std::function<void()> callback) { on_disconnected_callback_ = std::move(callback); }
|
||||
virtual void OnMessage(std::function<void(const std::string& topic, const std::string& payload)> callback) { on_message_callback_ = std::move(callback); }
|
||||
virtual void OnError(std::function<void(const std::string& error)> callback) { on_error_callback_ = std::move(callback); }
|
||||
|
||||
protected:
|
||||
int keep_alive_seconds_ = 120;
|
||||
std::function<void(const std::string& topic, const std::string& payload)> on_message_callback_;
|
||||
std::function<void()> on_connected_callback_;
|
||||
std::function<void()> on_disconnected_callback_;
|
||||
std::function<void(const std::string& error)> on_error_callback_;
|
||||
};
|
||||
|
||||
#endif // MQTT_INTERFACE_H
|
||||
@@ -1,24 +0,0 @@
|
||||
#ifndef NETWORK_INTERFACE_H
|
||||
#define NETWORK_INTERFACE_H
|
||||
|
||||
#include <memory>
|
||||
#include "http.h"
|
||||
#include "tcp.h"
|
||||
#include "udp.h"
|
||||
#include "mqtt.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
class NetworkInterface {
|
||||
public:
|
||||
virtual ~NetworkInterface() = default;
|
||||
|
||||
// 连接创建接口(纯虚函数,由子类实现)
|
||||
virtual std::unique_ptr<Http> CreateHttp(int connect_id = -1) = 0;
|
||||
virtual std::unique_ptr<Tcp> CreateTcp(int connect_id = -1) = 0;
|
||||
virtual std::unique_ptr<Tcp> CreateSsl(int connect_id = -1) = 0;
|
||||
virtual std::unique_ptr<Udp> CreateUdp(int connect_id = -1) = 0;
|
||||
virtual std::unique_ptr<Mqtt> CreateMqtt(int connect_id = -1) = 0;
|
||||
virtual std::unique_ptr<WebSocket> CreateWebSocket(int connect_id = -1) = 0;
|
||||
};
|
||||
|
||||
#endif // NETWORK_INTERFACE_H
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef TCP_H
|
||||
#define TCP_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class Tcp {
|
||||
public:
|
||||
virtual ~Tcp() = default;
|
||||
virtual bool Connect(const std::string& host, int port) = 0;
|
||||
virtual void Disconnect() = 0;
|
||||
virtual int Send(const std::string& data) = 0;
|
||||
|
||||
virtual void OnStream(std::function<void(const std::string& data)> callback) {
|
||||
stream_callback_ = callback;
|
||||
}
|
||||
|
||||
virtual void OnDisconnected(std::function<void()> callback) {
|
||||
disconnect_callback_ = callback;
|
||||
}
|
||||
|
||||
// 连接状态查询
|
||||
bool connected() const { return connected_; }
|
||||
|
||||
protected:
|
||||
std::function<void(const std::string& data)> stream_callback_;
|
||||
std::function<void()> disconnect_callback_;
|
||||
|
||||
// 连接状态管理
|
||||
bool connected_ = false; // 是否可以正常读写数据
|
||||
};
|
||||
|
||||
#endif // TCP_H
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef UDP_H
|
||||
#define UDP_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class Udp {
|
||||
public:
|
||||
virtual ~Udp() = default;
|
||||
virtual bool Connect(const std::string& host, int port) = 0;
|
||||
virtual void Disconnect() = 0;
|
||||
virtual int Send(const std::string& data) = 0;
|
||||
|
||||
virtual void OnMessage(std::function<void(const std::string& data)> callback) {
|
||||
message_callback_ = std::move(callback);
|
||||
}
|
||||
bool connected() const { return connected_; }
|
||||
|
||||
protected:
|
||||
std::function<void(const std::string& data)> message_callback_;
|
||||
bool connected_ = false;
|
||||
};
|
||||
|
||||
#endif // UDP_H
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef WEBSOCKET_H
|
||||
#define WEBSOCKET_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
class NetworkInterface;
|
||||
|
||||
class WebSocket {
|
||||
public:
|
||||
WebSocket(NetworkInterface* network, int connect_id);
|
||||
~WebSocket();
|
||||
|
||||
void SetHeader(const char* key, const char* value);
|
||||
void SetReceiveBufferSize(size_t size);
|
||||
bool IsConnected() const;
|
||||
bool Connect(const char* uri);
|
||||
bool Send(const std::string& data);
|
||||
bool Send(const void* data, size_t len, bool binary = false, bool fin = true);
|
||||
void Ping();
|
||||
void Close();
|
||||
|
||||
void OnConnected(std::function<void()> callback);
|
||||
void OnDisconnected(std::function<void()> callback);
|
||||
void OnData(std::function<void(const char*, size_t, bool binary)> callback);
|
||||
void OnError(std::function<void(int)> callback);
|
||||
|
||||
private:
|
||||
NetworkInterface* network_;
|
||||
int connect_id_;
|
||||
std::unique_ptr<Tcp> tcp_;
|
||||
bool continuation_ = false;
|
||||
size_t receive_buffer_size_ = 2048;
|
||||
std::string receive_buffer_;
|
||||
bool handshake_completed_ = false;
|
||||
bool connected_ = false;
|
||||
|
||||
// Mutex for sending data and replying pong
|
||||
std::mutex send_mutex_;
|
||||
|
||||
EventGroupHandle_t handshake_event_group_;
|
||||
static const EventBits_t HANDSHAKE_SUCCESS_BIT = BIT0;
|
||||
static const EventBits_t HANDSHAKE_FAILED_BIT = BIT1;
|
||||
|
||||
std::map<std::string, std::string> headers_;
|
||||
std::function<void(const char*, size_t, bool binary)> on_data_;
|
||||
std::function<void(int)> on_error_;
|
||||
std::function<void()> on_connected_;
|
||||
std::function<void()> on_disconnected_;
|
||||
|
||||
void OnTcpData(const std::string& data);
|
||||
bool SendControlFrame(uint8_t opcode, const void* data, size_t len);
|
||||
};
|
||||
|
||||
#endif // WEBSOCKET_H
|
||||
@@ -1,207 +0,0 @@
|
||||
#include "at_modem.h"
|
||||
#include "ml307/ml307_at_modem.h"
|
||||
#include "ec801e/ec801e_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "AtModem";
|
||||
|
||||
std::unique_ptr<AtModem> AtModem::Detect(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, int baud_rate) {
|
||||
// 创建AtUart进行检测
|
||||
auto uart = std::make_shared<AtUart>(tx_pin, rx_pin, dtr_pin);
|
||||
uart->Initialize();
|
||||
|
||||
// 设置波特率
|
||||
if (!uart->SetBaudRate(baud_rate)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 发送AT+CGMR(或ATI)命令获取模组型号
|
||||
if (!uart->SendCommand("AT+CGMR", 3000)) {
|
||||
ESP_LOGE(TAG, "Failed to send AT+CGMR command");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string response = uart->GetResponse();
|
||||
ESP_LOGI(TAG, "Detected modem: %s", response.c_str());
|
||||
|
||||
// 检查响应中的模组型号
|
||||
if (response.find("EC801E") == 0) {
|
||||
return std::make_unique<Ec801EAtModem>(uart);
|
||||
} else if (response.find("NT26K") == 0) {
|
||||
return std::make_unique<Ec801EAtModem>(uart);
|
||||
} else if (response.find("ML307") == 0) {
|
||||
return std::make_unique<Ml307AtModem>(uart);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unrecognized modem type: %s, use ML307 AtModem as default", response.c_str());
|
||||
return std::make_unique<Ml307AtModem>(uart);
|
||||
}
|
||||
}
|
||||
|
||||
AtModem::AtModem(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
HandleUrc(command, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
AtModem::~AtModem() {
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
void AtModem::OnNetworkStateChanged(std::function<void(bool network_ready)> callback) {
|
||||
on_network_state_changed_ = callback;
|
||||
}
|
||||
|
||||
void AtModem::Reboot() {
|
||||
}
|
||||
|
||||
void AtModem::SetFlightMode(bool enable) {
|
||||
if (enable) {
|
||||
at_uart_->SendCommand("AT+CFUN=4"); // flight mode
|
||||
at_uart_->SetDtrPin(enable);
|
||||
network_ready_ = false;
|
||||
} else {
|
||||
at_uart_->SetDtrPin(enable);
|
||||
at_uart_->SendCommand("AT+CFUN=1"); // normal mode
|
||||
}
|
||||
}
|
||||
|
||||
bool AtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NetworkStatus AtModem::WaitForNetworkReady(int timeout_ms) {
|
||||
ESP_LOGI(TAG, "Waiting for network ready...");
|
||||
network_ready_ = false;
|
||||
cereg_state_ = CeregState{};
|
||||
xEventGroupClearBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR);
|
||||
|
||||
// 检查 SIM 卡是否准备好
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (at_uart_->SendCommand("AT+CPIN?")) {
|
||||
pin_ready_ = true;
|
||||
break;
|
||||
}
|
||||
if (at_uart_->GetCmeErrorCode() == 10) {
|
||||
pin_ready_ = false;
|
||||
return NetworkStatus::ErrorInsertPin;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
// 检查网络注册状态
|
||||
if (!at_uart_->SendCommand("AT+CEREG=2")) {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
if (!at_uart_->SendCommand("AT+CEREG?")) {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
|
||||
TickType_t timeout = portMAX_DELAY;
|
||||
if (timeout_ms > 0) {
|
||||
timeout = pdMS_TO_TICKS(timeout_ms);
|
||||
}
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR, pdTRUE, pdFALSE, timeout);
|
||||
if (bits & AT_EVENT_NETWORK_READY) {
|
||||
return NetworkStatus::Ready;
|
||||
} else if (bits & AT_EVENT_NETWORK_ERROR) {
|
||||
if (cereg_state_.stat == 3) {
|
||||
return NetworkStatus::ErrorRegistrationDenied;
|
||||
} else if (!pin_ready_) {
|
||||
return NetworkStatus::ErrorInsertPin;
|
||||
} else {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
}
|
||||
return NetworkStatus::ErrorTimeout;
|
||||
}
|
||||
|
||||
std::string AtModem::GetImei() {
|
||||
if (!imei_.empty()) {
|
||||
return imei_;
|
||||
}
|
||||
at_uart_->SendCommand("AT+CGSN=1");
|
||||
return imei_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetIccid() {
|
||||
at_uart_->SendCommand("AT+ICCID");
|
||||
return iccid_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetModuleRevision() {
|
||||
if (!module_revision_.empty()) {
|
||||
return module_revision_;
|
||||
}
|
||||
if (at_uart_->SendCommand("AT+CGMR")) {
|
||||
module_revision_ = at_uart_->GetResponse();
|
||||
}
|
||||
return module_revision_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetCarrierName() {
|
||||
at_uart_->SendCommand("AT+COPS?");
|
||||
return carrier_name_;
|
||||
}
|
||||
|
||||
int AtModem::GetCsq() {
|
||||
at_uart_->SendCommand("AT+CSQ", 10);
|
||||
return csq_;
|
||||
}
|
||||
|
||||
CeregState AtModem::GetRegistrationState() {
|
||||
at_uart_->SendCommand("AT+CEREG?");
|
||||
return cereg_state_;
|
||||
}
|
||||
|
||||
void AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "CGSN" && arguments.size() >= 1) {
|
||||
imei_ = arguments[0].string_value;
|
||||
} else if (command == "ICCID" && arguments.size() >= 1) {
|
||||
iccid_ = arguments[0].string_value;
|
||||
} else if (command == "COPS" && arguments.size() >= 4) {
|
||||
carrier_name_ = arguments[2].string_value;
|
||||
} else if (command == "CSQ" && arguments.size() >= 1) {
|
||||
csq_ = arguments[0].int_value;
|
||||
} else if (command == "CEREG" && arguments.size() >= 1) {
|
||||
cereg_state_ = CeregState{};
|
||||
if (arguments.size() == 1) {
|
||||
cereg_state_.stat = 0;
|
||||
} else if (arguments.size() >= 2) {
|
||||
int state_index = arguments[1].type == AtArgumentValue::Type::Int ? 1 : 0;
|
||||
cereg_state_.stat = arguments[state_index].int_value;
|
||||
if (arguments.size() >= state_index + 2) {
|
||||
cereg_state_.tac = arguments[state_index + 1].string_value;
|
||||
cereg_state_.ci = arguments[state_index + 2].string_value;
|
||||
if (arguments.size() >= state_index + 4) {
|
||||
cereg_state_.AcT = arguments[state_index + 3].int_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool new_network_ready = cereg_state_.stat == 1 || cereg_state_.stat == 5;
|
||||
if (new_network_ready != network_ready_) {
|
||||
network_ready_ = new_network_ready;
|
||||
if (on_network_state_changed_) {
|
||||
on_network_state_changed_(new_network_ready);
|
||||
}
|
||||
}
|
||||
if (new_network_ready) {
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
|
||||
} else if (cereg_state_.stat == 3) {
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_ERROR);
|
||||
}
|
||||
} else if (command == "CPIN" && arguments.size() >= 1) {
|
||||
if (arguments[0].string_value == "READY") {
|
||||
pin_ready_ = true;
|
||||
} else {
|
||||
pin_ready_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
#include "at_uart.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
#define TAG "AtUart"
|
||||
|
||||
|
||||
// AtUart 构造函数实现
|
||||
AtUart::AtUart(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin)
|
||||
: tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), uart_num_(UART_NUM),
|
||||
baud_rate_(115200), initialized_(false),
|
||||
event_task_handle_(nullptr), event_queue_handle_(nullptr), event_group_handle_(nullptr) {
|
||||
}
|
||||
|
||||
AtUart::~AtUart() {
|
||||
if (event_task_handle_) {
|
||||
vTaskDelete(event_task_handle_);
|
||||
}
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
if (initialized_) {
|
||||
uart_driver_delete(uart_num_);
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::Initialize() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
if (!event_group_handle_) {
|
||||
ESP_LOGE(TAG, "创建事件组失败");
|
||||
return;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = {};
|
||||
uart_config.baud_rate = baud_rate_;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(uart_num_, 8192, 0, 100, &event_queue_handle_, ESP_INTR_FLAG_IRAM));
|
||||
ESP_ERROR_CHECK(uart_param_config(uart_num_, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(uart_num_, tx_pin_, rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
if (dtr_pin_ != GPIO_NUM_NC) {
|
||||
gpio_config_t config = {};
|
||||
config.pin_bit_mask = (1ULL << dtr_pin_);
|
||||
config.mode = GPIO_MODE_OUTPUT;
|
||||
config.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&config);
|
||||
gpio_set_level(dtr_pin_, 0);
|
||||
}
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto ml307_at_modem = (AtUart*)arg;
|
||||
ml307_at_modem->EventTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "modem_event", 4096, this, 15, &event_task_handle_);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto ml307_at_modem = (AtUart*)arg;
|
||||
ml307_at_modem->ReceiveTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "modem_receive", 4096 * 2, this, 15, &receive_task_handle_);
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void AtUart::EventTaskWrapper(void* arg) {
|
||||
auto uart = static_cast<AtUart*>(arg);
|
||||
uart->EventTask();
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void AtUart::EventTask() {
|
||||
uart_event_t event;
|
||||
while (true) {
|
||||
if (xQueueReceive(event_queue_handle_, &event, portMAX_DELAY) == pdTRUE) {
|
||||
switch (event.type)
|
||||
{
|
||||
case UART_DATA:
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE);
|
||||
break;
|
||||
case UART_BREAK:
|
||||
ESP_LOGI(TAG, "break");
|
||||
break;
|
||||
case UART_BUFFER_FULL:
|
||||
ESP_LOGE(TAG, "buffer full");
|
||||
break;
|
||||
case UART_FIFO_OVF:
|
||||
ESP_LOGE(TAG, "FIFO overflow");
|
||||
HandleUrc("FIFO_OVERFLOW", {});
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "unknown event type: %d", event.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::ReceiveTask() {
|
||||
while (true) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
if (bits & AT_EVENT_DATA_AVAILABLE) {
|
||||
size_t available;
|
||||
uart_get_buffered_data_len(uart_num_, &available);
|
||||
if (available > 0) {
|
||||
// Extend rx_buffer_ and read into buffer
|
||||
rx_buffer_.resize(rx_buffer_.size() + available);
|
||||
char* rx_buffer_ptr = &rx_buffer_[rx_buffer_.size() - available];
|
||||
uart_read_bytes(uart_num_, rx_buffer_ptr, available, portMAX_DELAY);
|
||||
while (ParseResponse()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_number(const std::string& s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit) && s.length() < 10;
|
||||
}
|
||||
|
||||
bool AtUart::ParseResponse() {
|
||||
if (wait_for_response_ && rx_buffer_[0] == '>') {
|
||||
rx_buffer_.erase(0, 1);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto end_pos = rx_buffer_.find("\r\n");
|
||||
if (end_pos == std::string::npos) {
|
||||
// FIXME: for +MHTTPURC: "ind", missing newline
|
||||
if (rx_buffer_.size() >= 16 && memcmp(rx_buffer_.c_str(), "+MHTTPURC: \"ind\"", 16) == 0) {
|
||||
// Find the end of this line and add \r\n if missing
|
||||
auto next_plus = rx_buffer_.find("+", 1);
|
||||
if (next_plus != std::string::npos) {
|
||||
// Insert \r\n before the next + command
|
||||
rx_buffer_.insert(next_plus, "\r\n");
|
||||
} else {
|
||||
// Append \r\n at the end
|
||||
rx_buffer_.append("\r\n");
|
||||
}
|
||||
end_pos = rx_buffer_.find("\r\n");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore empty lines
|
||||
if (end_pos == 0) {
|
||||
rx_buffer_.erase(0, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "<< %.64s (%u bytes)", rx_buffer_.substr(0, end_pos).c_str(), end_pos);
|
||||
// print last 64 bytes before end_pos if available
|
||||
// if (end_pos > 64) {
|
||||
// ESP_LOGI(TAG, "<< LAST: %.64s", rx_buffer_.c_str() + end_pos - 64);
|
||||
// }
|
||||
|
||||
// Parse "+CME ERROR: 123,456,789"
|
||||
if (rx_buffer_[0] == '+') {
|
||||
std::string command, values;
|
||||
auto pos = rx_buffer_.find(": ");
|
||||
if (pos == std::string::npos || pos > end_pos) {
|
||||
command = rx_buffer_.substr(1, end_pos - 1);
|
||||
} else {
|
||||
command = rx_buffer_.substr(1, pos - 1);
|
||||
values = rx_buffer_.substr(pos + 2, end_pos - pos - 2);
|
||||
}
|
||||
rx_buffer_.erase(0, end_pos + 2);
|
||||
|
||||
// Parse "string", int, int, ... into AtArgumentValue
|
||||
std::vector<AtArgumentValue> arguments;
|
||||
std::istringstream iss(values);
|
||||
std::string item;
|
||||
while (std::getline(iss, item, ',')) {
|
||||
AtArgumentValue argument;
|
||||
if (item.front() == '"') {
|
||||
argument.type = AtArgumentValue::Type::String;
|
||||
argument.string_value = item.substr(1, item.size() - 2);
|
||||
} else if (item.find(".") != std::string::npos) {
|
||||
argument.type = AtArgumentValue::Type::Double;
|
||||
argument.double_value = std::stod(item);
|
||||
} else if (is_number(item)) {
|
||||
argument.type = AtArgumentValue::Type::Int;
|
||||
argument.int_value = std::stoi(item);
|
||||
argument.string_value = std::move(item);
|
||||
} else {
|
||||
argument.type = AtArgumentValue::Type::String;
|
||||
argument.string_value = std::move(item);
|
||||
}
|
||||
arguments.push_back(argument);
|
||||
}
|
||||
|
||||
HandleUrc(command, arguments);
|
||||
return true;
|
||||
} else if (rx_buffer_.size() >= 4 && rx_buffer_[0] == 'O' && rx_buffer_[1] == 'K' && rx_buffer_[2] == '\r' && rx_buffer_[3] == '\n') {
|
||||
rx_buffer_.erase(0, 4);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
|
||||
return true;
|
||||
} else if (rx_buffer_.size() >= 7 && rx_buffer_[0] == 'E' && rx_buffer_[1] == 'R' && rx_buffer_[2] == 'R' && rx_buffer_[3] == 'O' && rx_buffer_[4] == 'R' && rx_buffer_[5] == '\r' && rx_buffer_[6] == '\n') {
|
||||
rx_buffer_.erase(0, 7);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
|
||||
return true;
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
response_ = rx_buffer_.substr(0, end_pos);
|
||||
rx_buffer_.erase(0, end_pos + 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AtUart::HandleCommand(const char* command) {
|
||||
// 这个函数现在主要用于向后兼容,大部分处理逻辑已经移到 ParseLine 中
|
||||
if (wait_for_response_) {
|
||||
response_.append(command);
|
||||
response_.append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "CME ERROR") {
|
||||
cme_error_code_ = arguments[0].int_value;
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
for (auto& callback : urc_callbacks_) {
|
||||
callback(command, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
bool AtUart::DetectBaudRate() {
|
||||
int baud_rates[] = {115200, 921600, 460800, 230400, 57600, 38400, 19200, 9600};
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "Detecting baud rate...");
|
||||
for (size_t i = 0; i < sizeof(baud_rates) / sizeof(baud_rates[0]); i++) {
|
||||
int rate = baud_rates[i];
|
||||
uart_set_baudrate(uart_num_, rate);
|
||||
if (SendCommand("AT", 20)) {
|
||||
ESP_LOGI(TAG, "Detected baud rate: %d", rate);
|
||||
baud_rate_ = rate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtUart::SetBaudRate(int new_baud_rate) {
|
||||
if (!DetectBaudRate()) {
|
||||
ESP_LOGE(TAG, "Failed to detect baud rate");
|
||||
return false;
|
||||
}
|
||||
if (new_baud_rate == baud_rate_) {
|
||||
return true;
|
||||
}
|
||||
// Set new baud rate
|
||||
if (!SendCommand(std::string("AT+IPR=") + std::to_string(new_baud_rate))) {
|
||||
ESP_LOGI(TAG, "Failed to set baud rate to %d", new_baud_rate);
|
||||
return false;
|
||||
}
|
||||
uart_set_baudrate(uart_num_, new_baud_rate);
|
||||
baud_rate_ = new_baud_rate;
|
||||
ESP_LOGI(TAG, "Set baud rate to %d", new_baud_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendData(const char* data, size_t length) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "UART未初始化");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = uart_write_bytes(uart_num_, data, length);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "uart_write_bytes failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendCommandWithData(const std::string& command, size_t timeout_ms, bool add_crlf, const char* data, size_t data_length) {
|
||||
std::lock_guard<std::mutex> lock(command_mutex_);
|
||||
ESP_LOGD(TAG, ">> %.64s (%u bytes)", command.data(), command.length());
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR);
|
||||
wait_for_response_ = true;
|
||||
cme_error_code_ = 0;
|
||||
response_.clear();
|
||||
|
||||
if (add_crlf) {
|
||||
if (!SendData((command + "\r\n").data(), command.length() + 2)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!SendData(command.data(), command.length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (timeout_ms > 0) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
|
||||
wait_for_response_ = false;
|
||||
if (!(bits & AT_EVENT_COMMAND_DONE)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
wait_for_response_ = false;
|
||||
}
|
||||
|
||||
if (data && data_length > 0) {
|
||||
wait_for_response_ = true;
|
||||
if (!SendData(data, data_length)) {
|
||||
return false;
|
||||
}
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
|
||||
wait_for_response_ = false;
|
||||
if (!(bits & AT_EVENT_COMMAND_DONE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendCommand(const std::string& command, size_t timeout_ms, bool add_crlf) {
|
||||
return SendCommandWithData(command, timeout_ms, add_crlf, nullptr, 0);
|
||||
}
|
||||
|
||||
std::list<UrcCallback>::iterator AtUart::RegisterUrcCallback(UrcCallback callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return urc_callbacks_.insert(urc_callbacks_.end(), callback);
|
||||
}
|
||||
|
||||
void AtUart::UnregisterUrcCallback(std::list<UrcCallback>::iterator iterator) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
urc_callbacks_.erase(iterator);
|
||||
}
|
||||
|
||||
void AtUart::SetDtrPin(bool high) {
|
||||
if (dtr_pin_ != GPIO_NUM_NC) {
|
||||
ESP_LOGD(TAG, "Set DTR pin %d to %d", dtr_pin_, high ? 1 : 0);
|
||||
gpio_set_level(dtr_pin_, high ? 1 : 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
}
|
||||
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
// 辅助函数,将单个十六进制字符转换为对应的数值
|
||||
inline uint8_t CharToHex(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return 0; // 对于无效输入,返回0
|
||||
}
|
||||
|
||||
void AtUart::EncodeHexAppend(std::string& dest, const char* data, size_t length) {
|
||||
dest.reserve(dest.size() + length * 2 + 4); // 预分配空间,多分配4个字节用于\r\n\0
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
dest.push_back(hex_chars[(data[i] & 0xF0) >> 4]);
|
||||
dest.push_back(hex_chars[data[i] & 0x0F]);
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::DecodeHexAppend(std::string& dest, const char* data, size_t length) {
|
||||
dest.reserve(dest.size() + length / 2 + 4); // 预分配空间,多分配4个字节用于\r\n\0
|
||||
for (size_t i = 0; i < length; i += 2) {
|
||||
char byte = (CharToHex(data[i]) << 4) | CharToHex(data[i + 1]);
|
||||
dest.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
std::string AtUart::EncodeHex(const std::string& data) {
|
||||
std::string encoded;
|
||||
EncodeHexAppend(encoded, data.c_str(), data.size());
|
||||
return encoded;
|
||||
}
|
||||
|
||||
std::string AtUart::DecodeHex(const std::string& data) {
|
||||
std::string decoded;
|
||||
DecodeHexAppend(decoded, data.c_str(), data.size());
|
||||
return decoded;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "ec801e_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include "ec801e_ssl.h"
|
||||
#include "ec801e_tcp.h"
|
||||
#include "ec801e_udp.h"
|
||||
#include "ec801e_mqtt.h"
|
||||
#include "http_client.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
#define TAG "Ec801EAtModem"
|
||||
|
||||
|
||||
Ec801EAtModem::Ec801EAtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
|
||||
// 子类特定的初始化在这里
|
||||
// ATE0 关闭 echo
|
||||
at_uart_->SendCommand("ATE0");
|
||||
// 设置 URC 端口为 UART1
|
||||
at_uart_->SendCommand("AT+QURCCFG=\"urcport\",\"uart1\"");
|
||||
}
|
||||
|
||||
void Ec801EAtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
// Handle Common URC
|
||||
AtModem::HandleUrc(command, arguments);
|
||||
}
|
||||
|
||||
bool Ec801EAtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
if (enable) {
|
||||
if (delay_seconds > 0) {
|
||||
at_uart_->SendCommand("AT+QSCLKEX=1," + std::to_string(delay_seconds) + ",30");
|
||||
}
|
||||
return at_uart_->SendCommand("AT+QSCLK=1");
|
||||
} else {
|
||||
return at_uart_->SendCommand("AT+QSCLK=0");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> Ec801EAtModem::CreateHttp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<HttpClient>(this, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ec801EAtModem::CreateTcp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801ETcp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ec801EAtModem::CreateSsl(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801ESsl>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> Ec801EAtModem::CreateUdp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801EUdp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> Ec801EAtModem::CreateMqtt(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801EMqtt>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> Ec801EAtModem::CreateWebSocket(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef _EC801E_AT_MODEM_H_
|
||||
#define _EC801E_AT_MODEM_H_
|
||||
|
||||
#include "at_modem.h"
|
||||
|
||||
class Ec801EAtModem : public AtModem {
|
||||
public:
|
||||
Ec801EAtModem(std::shared_ptr<AtUart> at_uart);
|
||||
~Ec801EAtModem() override = default;
|
||||
|
||||
bool SetSleepMode(bool enable, int delay_seconds=0) override;
|
||||
|
||||
// 实现基类的纯虚函数
|
||||
std::unique_ptr<Http> CreateHttp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
|
||||
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
|
||||
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
|
||||
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
|
||||
|
||||
protected:
|
||||
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // _EC801E_AT_MODEM_H_
|
||||
@@ -1,244 +0,0 @@
|
||||
#include "ec801e_mqtt.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801EMqtt"
|
||||
|
||||
Ec801EMqtt::Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QMTRECV" && arguments.size() >= 4) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
auto topic = arguments[2].string_value;
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTSTAT" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
auto error_code = arguments[1].int_value;
|
||||
if (error_code != 0) {
|
||||
auto error_message = ErrorToString(error_code);
|
||||
ESP_LOGE(TAG, "MQTT error occurred: %s", error_message.c_str());
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_message);
|
||||
}
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTCONN" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
error_code_ = arguments[2].int_value;
|
||||
if (error_code_ == 0) {
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT);
|
||||
} else {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
error_code_ = arguments[1].int_value;
|
||||
if (error_code_ == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTDISC" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from MQTT broker");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801EMqtt::~Ec801EMqtt() {
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
EventBits_t bits;
|
||||
|
||||
if (broker_port == 8883) {
|
||||
// Config SSL Context
|
||||
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",2,4;+QSSLCFG=\"ciphersuite\",2,0xFFFF;+QSSLCFG=\"seclevel\",2,0");
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1,2")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set version
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"version\",") + std::to_string(mqtt_id_) + ",4")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT version to 3.1.1");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set clean session
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"session\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT clean session");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set keep alive
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT keep alive");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set HEX encoding (ASCII for sending, HEX for receiving)
|
||||
if (!at_uart_->SendCommand("AT+QMTCFG=\"dataformat\"," + std::to_string(mqtt_id_) + ",0,1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED);
|
||||
std::string command = "AT+QMTOPEN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port);
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open MQTT connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_MQTT_OPEN_FAILED) {
|
||||
const char* error_code_str[] = {
|
||||
"Connected",
|
||||
"Parameter error",
|
||||
"MQTT identifier occupied",
|
||||
"PDP activation failed",
|
||||
"Domain name resolution failed",
|
||||
"Server disconnected"
|
||||
};
|
||||
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
|
||||
ESP_LOGE(TAG, "Failed to open MQTT connection: %s", message);
|
||||
|
||||
if (error_code_ == 2) { // MQTT 标识符被占用
|
||||
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & EC801E_MQTT_DISCONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
|
||||
return false;
|
||||
}
|
||||
return Connect(broker_address, broker_port, client_id, username, password);
|
||||
}
|
||||
return false;
|
||||
} else if (!(bits & EC801E_MQTT_OPEN_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "MQTT connection timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
command = "AT+QMTCONN=" + std::to_string(mqtt_id_) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_MQTT_DISCONNECTED_EVENT) {
|
||||
const char* error_code_str[] = {
|
||||
"Accepted",
|
||||
"Rejected: Unacceptable protocol version",
|
||||
"Rejected: Identifier rejected",
|
||||
"Rejected: Server unavailable",
|
||||
"Rejected: Wrong username or password",
|
||||
"Rejected: Unauthorized"
|
||||
};
|
||||
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker: %s", message);
|
||||
return false;
|
||||
} else if (!(bits & EC801E_MQTT_CONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "MQTT connection timeout");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::IsConnected() {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void Ec801EMqtt::Disconnect() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
|
||||
std::string command = "AT+QMTPUBEX=" + std::to_string(mqtt_id_) + ",0,0,0,\"" + topic + "\",";
|
||||
command += std::to_string(payload.size());
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, payload.data(), payload.size())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+QMTSUB=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"," + std::to_string(qos);
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+QMTUNS=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"";
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
std::string Ec801EMqtt::ErrorToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 0:
|
||||
return "Connected";
|
||||
case 1:
|
||||
return "Server disconnected or reset";
|
||||
case 2:
|
||||
return "Ping timeout or failed";
|
||||
case 3:
|
||||
return "Connect timeout or failed";
|
||||
case 4:
|
||||
return "Receive CONNACK timeout or failed";
|
||||
case 5:
|
||||
return "Client sends DISCONNECT packet, but server actively disconnects MQTT connection";
|
||||
case 6:
|
||||
return "Client actively disconnects MQTT connection because sending data packets always fails";
|
||||
case 7:
|
||||
return "Link does not work or server is unavailable";
|
||||
case 8:
|
||||
return "Client actively disconnects MQTT connection";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef EC801E_MQTT_H
|
||||
#define EC801E_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include "at_uart.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define EC801E_MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define EC801E_MQTT_CONNECTED_EVENT BIT1
|
||||
#define EC801E_MQTT_DISCONNECTED_EVENT BIT2
|
||||
#define EC801E_MQTT_OPEN_COMPLETE BIT5
|
||||
#define EC801E_MQTT_OPEN_FAILED BIT6
|
||||
|
||||
class Ec801EMqtt : public Mqtt {
|
||||
public:
|
||||
Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
|
||||
~Ec801EMqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int mqtt_id_;
|
||||
bool connected_ = false;
|
||||
int error_code_ = 0;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
std::string ErrorToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "ec801e_ssl.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801ESsl"
|
||||
|
||||
|
||||
Ec801ESsl::Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id) : at_uart_(at_uart), ssl_id_(ssl_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QSSLOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == ssl_id_ && !instance_active_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
connected_ = true;
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_CONNECTED);
|
||||
} else {
|
||||
connected_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == ssl_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
// instance_active_ 保持 true,需要发送 QICLOSE 清理
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLSTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801ESsl::~Ec801ESsl() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
}
|
||||
|
||||
bool Ec801ESsl::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// Config SSL Context
|
||||
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",1,4;+QSSLCFG=\"ciphersuite\",1,0xFFFF;+QSSLCFG=\"seclevel\",1,0");
|
||||
// at_uart_->SendCommand("AT+QSSLCFG=\"cacert\",1,\"UFS:cacert.pem\"");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QSSLSTATE=1," + std::to_string(ssl_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_SSL_DISCONNECTED, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+QSSLOPEN=1,1," + std::to_string(ssl_id_) + ",\"" + host + "\"," + std::to_string(port) + ",1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_ERROR, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_SSL_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ec801ESsl::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801ESsl::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
std::string command = "AT+QSSLSEND=" + std::to_string(ssl_id_) + "," + std::to_string(chunk_size);
|
||||
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
|
||||
ESP_LOGE(TAG, "Send command failed");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE | EC801E_SSL_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(SSL_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_SSL_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Send failed, retry later");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
} else if (!(bits & EC801E_SSL_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef EC801E_SSL_H
|
||||
#define EC801E_SSL_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define EC801E_SSL_CONNECTED BIT0
|
||||
#define EC801E_SSL_DISCONNECTED BIT1
|
||||
#define EC801E_SSL_ERROR BIT2
|
||||
#define EC801E_SSL_SEND_COMPLETE BIT3
|
||||
#define EC801E_SSL_SEND_FAILED BIT4
|
||||
#define EC801E_SSL_INITIALIZED BIT5
|
||||
|
||||
#define SSL_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801ESsl : public Tcp {
|
||||
public:
|
||||
Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id);
|
||||
~Ec801ESsl();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int ssl_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_SSL_H
|
||||
@@ -1,159 +0,0 @@
|
||||
#include "ec801e_tcp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801ETcp"
|
||||
|
||||
|
||||
Ec801ETcp::Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QIOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
connected_ = true;
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_CONNECTED);
|
||||
} else {
|
||||
connected_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QIURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == tcp_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (connected_ && stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
// instance_active_ 保持 true,需要发送 QICLOSE 清理
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QISTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801ETcp::~Ec801ETcp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ec801ETcp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QISTATE=1," + std::to_string(tcp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_TCP_DISCONNECTED, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+QIOPEN=1," + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_TCP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ec801ETcp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_))) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801ETcp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
std::string command = "AT+QISEND=" + std::to_string(tcp_id_) + "," + std::to_string(chunk_size);
|
||||
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
|
||||
ESP_LOGE(TAG, "Send command failed");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE | EC801E_TCP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_TCP_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Send failed, retry later");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
} else if (!(bits & EC801E_TCP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef EC801E_TCP_H
|
||||
#define EC801E_TCP_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
|
||||
#define EC801E_TCP_CONNECTED BIT0
|
||||
#define EC801E_TCP_DISCONNECTED BIT1
|
||||
#define EC801E_TCP_ERROR BIT2
|
||||
#define EC801E_TCP_SEND_COMPLETE BIT3
|
||||
#define EC801E_TCP_SEND_FAILED BIT4
|
||||
#define EC801E_TCP_INITIALIZED BIT5
|
||||
|
||||
#define TCP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801ETcp : public Tcp {
|
||||
public:
|
||||
Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
~Ec801ETcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int tcp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_TCP_H
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "ec801e_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801EUdp"
|
||||
|
||||
|
||||
Ec801EUdp::Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QIOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QIURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == udp_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (connected_ && message_callback_) {
|
||||
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
connected_ = false;
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QISTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801EUdp::~Ec801EUdp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ec801EUdp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QISTATE=1," + std::to_string(udp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_UDP_DISCONNECTED, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 UDP 连接
|
||||
command = "AT+QIOPEN=1," + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open UDP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_UDP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ec801EUdp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_))) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801EUdp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.size() > MAX_PACKET_SIZE) {
|
||||
ESP_LOGE(TAG, "Data block exceeds maximum limit");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command = "AT+QISEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size());
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data(), data.size())) {
|
||||
ESP_LOGE(TAG, "Failed to send command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE | EC801E_UDP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_UDP_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Failed to send data");
|
||||
return -1;
|
||||
} else if (!(bits & EC801E_UDP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return data.size();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef EC801E_UDP_H
|
||||
#define EC801E_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define EC801E_UDP_CONNECTED BIT0
|
||||
#define EC801E_UDP_DISCONNECTED BIT1
|
||||
#define EC801E_UDP_ERROR BIT2
|
||||
#define EC801E_UDP_SEND_COMPLETE BIT3
|
||||
#define EC801E_UDP_SEND_FAILED BIT4
|
||||
#define EC801E_UDP_INITIALIZED BIT5
|
||||
|
||||
#define UDP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801EUdp : public Udp {
|
||||
public:
|
||||
Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id);
|
||||
~Ec801EUdp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int udp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_UDP_H
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "esp_mqtt.h"
|
||||
#include <esp_crt_bundle.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "esp_mqtt";
|
||||
|
||||
EspMqtt::EspMqtt() {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspMqtt::~EspMqtt() {
|
||||
Disconnect();
|
||||
if (event_group_handle_ != nullptr) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool EspMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
if (mqtt_client_handle_ != nullptr) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
esp_mqtt_client_config_t mqtt_config = {};
|
||||
mqtt_config.broker.address.hostname = broker_address.c_str();
|
||||
mqtt_config.broker.address.port = broker_port;
|
||||
if (broker_port == 8883) {
|
||||
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||
mqtt_config.broker.verification.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
} else {
|
||||
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||
}
|
||||
mqtt_config.credentials.client_id = client_id.c_str();
|
||||
mqtt_config.credentials.username = username.c_str();
|
||||
mqtt_config.credentials.authentication.password = password.c_str();
|
||||
mqtt_config.session.keepalive = keep_alive_seconds_;
|
||||
|
||||
mqtt_client_handle_ = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client_handle_, MQTT_EVENT_ANY, [](void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
((EspMqtt*)handler_args)->MqttEventCallback(base, event_id, event_data);
|
||||
}, this);
|
||||
esp_mqtt_client_start(mqtt_client_handle_);
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT,
|
||||
pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
return bits & MQTT_CONNECTED_EVENT;
|
||||
}
|
||||
|
||||
void EspMqtt::MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
auto event = (esp_mqtt_event_t*)event_data;
|
||||
switch (event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
|
||||
break;
|
||||
case MQTT_EVENT_DATA: {
|
||||
auto topic = std::string(event->topic, event->topic_len);
|
||||
auto payload = std::string(event->data, event->data_len);
|
||||
if (event->data_len == event->total_data_len) {
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, payload);
|
||||
}
|
||||
} else {
|
||||
message_payload_.append(payload);
|
||||
if (message_payload_.size() >= event->total_data_len && on_message_callback_) {
|
||||
on_message_callback_(topic, message_payload_);
|
||||
message_payload_.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
break;
|
||||
case MQTT_EVENT_ERROR: {
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_ERROR_EVENT);
|
||||
const char* error_name = esp_err_to_name(event->error_handle->esp_tls_last_esp_err);
|
||||
ESP_LOGI(TAG, "MQTT error occurred: %s", error_name);
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_name ? error_name : "MQTT error");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGI(TAG, "Unhandled event id %ld", event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EspMqtt::Disconnect() {
|
||||
if (mqtt_client_handle_ != nullptr) {
|
||||
esp_mqtt_client_stop(mqtt_client_handle_);
|
||||
esp_mqtt_client_destroy(mqtt_client_handle_);
|
||||
mqtt_client_handle_ = nullptr;
|
||||
}
|
||||
connected_ = false;
|
||||
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT);
|
||||
}
|
||||
|
||||
bool EspMqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_publish(mqtt_client_handle_, topic.c_str(), payload.data(), payload.size(), qos, 0) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_subscribe_single(mqtt_client_handle_, topic.c_str(), qos) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_unsubscribe(mqtt_client_handle_, topic.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::IsConnected() {
|
||||
return connected_;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef ESP_MQTT_H
|
||||
#define ESP_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <mqtt_client.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define MQTT_INITIALIZED_EVENT BIT0
|
||||
#define MQTT_CONNECTED_EVENT BIT1
|
||||
#define MQTT_DISCONNECTED_EVENT BIT2
|
||||
#define MQTT_ERROR_EVENT BIT3
|
||||
|
||||
class EspMqtt : public Mqtt {
|
||||
public:
|
||||
EspMqtt();
|
||||
~EspMqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
bool connected_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
esp_mqtt_client_handle_t mqtt_client_handle_ = nullptr;
|
||||
|
||||
void MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,41 +0,0 @@
|
||||
#include "esp_network.h"
|
||||
|
||||
#include "esp_tcp.h"
|
||||
#include "esp_ssl.h"
|
||||
#include "esp_udp.h"
|
||||
#include "esp_mqtt.h"
|
||||
#include "http_client.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
|
||||
EspNetwork::EspNetwork() {
|
||||
|
||||
}
|
||||
|
||||
EspNetwork::~EspNetwork() {
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> EspNetwork::CreateHttp(int connect_id) {
|
||||
return std::make_unique<HttpClient>(this, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> EspNetwork::CreateTcp(int connect_id) {
|
||||
return std::make_unique<EspTcp>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> EspNetwork::CreateSsl(int connect_id) {
|
||||
return std::make_unique<EspSsl>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> EspNetwork::CreateUdp(int connect_id) {
|
||||
return std::make_unique<EspUdp>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> EspNetwork::CreateMqtt(int connect_id) {
|
||||
return std::make_unique<EspMqtt>();
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> EspNetwork::CreateWebSocket(int connect_id) {
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "esp_ssl.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_crt_bundle.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *TAG = "EspSsl";
|
||||
|
||||
EspSsl::EspSsl() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspSsl::~EspSsl() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspSsl::Connect(const std::string& host, int port) {
|
||||
if (tls_client_ != nullptr) {
|
||||
ESP_LOGE(TAG, "tls client has been initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
tls_client_ = esp_tls_init();
|
||||
if (tls_client_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TLS");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_tls_cfg_t cfg = {};
|
||||
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
|
||||
int ret = esp_tls_conn_new_sync(host.c_str(), host.length(), port, &cfg, tls_client_);
|
||||
if (ret != 1) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
esp_tls_conn_destroy(tls_client_);
|
||||
tls_client_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspSsl* ssl = (EspSsl*)arg;
|
||||
ssl->ReceiveTask();
|
||||
xEventGroupSetBits(ssl->event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "ssl_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspSsl::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
// Close socket if it is open
|
||||
if (tls_client_ != nullptr) {
|
||||
int sockfd;
|
||||
ESP_ERROR_CHECK(esp_tls_get_conn_sockfd(tls_client_, &sockfd));
|
||||
if (sockfd >= 0) {
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_SSL_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
|
||||
esp_tls_conn_destroy(tls_client_);
|
||||
tls_client_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* CONFIG_MBEDTLS_SSL_RENEGOTIATION should be disabled in sdkconfig.
|
||||
* Otherwise, invalid memory access may be triggered.
|
||||
*/
|
||||
int EspSsl::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
size_t data_size = data.size();
|
||||
const char* data_ptr = data.data();
|
||||
|
||||
while (total_sent < data_size) {
|
||||
int ret = esp_tls_conn_write(tls_client_, data_ptr + total_sent, data_size - total_sent);
|
||||
|
||||
if (ret == ESP_TLS_ERR_SSL_WANT_WRITE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "SSL send failed: ret=%d, errno=%d", ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
total_sent += ret;
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
void EspSsl::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = esp_tls_conn_read(tls_client_, data.data(), data.size());
|
||||
|
||||
if (ret == ESP_TLS_ERR_SSL_WANT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "SSL receive failed: %d", ret);
|
||||
}
|
||||
connected_ = false;
|
||||
// 接收失败或连接断开时调用断连回调
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream_callback_) {
|
||||
data.resize(ret);
|
||||
stream_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef _ESP_SSL_H_
|
||||
#define _ESP_SSL_H_
|
||||
|
||||
#include "tcp.h"
|
||||
#include <esp_tls.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_SSL_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspSsl : public Tcp {
|
||||
public:
|
||||
EspSsl();
|
||||
~EspSsl();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
esp_tls_t* tls_client_ = nullptr;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // _ESP_SSL_H_
|
||||
@@ -1,130 +0,0 @@
|
||||
#include "esp_tcp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
|
||||
static const char *TAG = "EspTcp";
|
||||
|
||||
EspTcp::EspTcp() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspTcp::~EspTcp() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspTcp::Connect(const std::string& host, int port) {
|
||||
// 确保先断开已有连接
|
||||
if (connected_) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
struct sockaddr_in server_addr;
|
||||
bzero(&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
// host is domain
|
||||
struct hostent *server = gethostbyname(host.c_str());
|
||||
if (server == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to get host by name");
|
||||
return false;
|
||||
}
|
||||
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
|
||||
|
||||
tcp_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (tcp_fd_ < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = connect(tcp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
close(tcp_fd_);
|
||||
tcp_fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspTcp* tcp = (EspTcp*)arg;
|
||||
tcp->ReceiveTask();
|
||||
xEventGroupSetBits(tcp->event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "tcp_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspTcp::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
if (tcp_fd_ != -1) {
|
||||
close(tcp_fd_);
|
||||
tcp_fd_ = -1;
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_TCP_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EspTcp::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
size_t data_size = data.size();
|
||||
const char* data_ptr = data.data();
|
||||
|
||||
while (total_sent < data_size) {
|
||||
int ret = send(tcp_fd_, data_ptr + total_sent, data_size - total_sent, 0);
|
||||
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
total_sent += ret;
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
void EspTcp::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = recv(tcp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "TCP receive failed: %d", ret);
|
||||
}
|
||||
connected_ = false;
|
||||
// 接收失败或连接断开时调用断连回调
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream_callback_) {
|
||||
data.resize(ret);
|
||||
stream_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef _ESP_TCP_H_
|
||||
#define _ESP_TCP_H_
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_TCP_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspTcp : public Tcp {
|
||||
public:
|
||||
EspTcp();
|
||||
~EspTcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
int tcp_fd_ = -1;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // _ESP_TCP_H_
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "esp_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
static const char *TAG = "EspUdp";
|
||||
|
||||
EspUdp::EspUdp() : udp_fd_(-1) {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspUdp::~EspUdp() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspUdp::Connect(const std::string& host, int port) {
|
||||
// 确保先断开已有连接
|
||||
if (connected_) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
struct sockaddr_in server_addr;
|
||||
bzero(&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
// host is domain
|
||||
struct hostent *server = gethostbyname(host.c_str());
|
||||
if (server == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to get host by name");
|
||||
return false;
|
||||
}
|
||||
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
|
||||
|
||||
udp_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_fd_ < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = connect(udp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
close(udp_fd_);
|
||||
udp_fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspUdp* udp = (EspUdp*)arg;
|
||||
udp->ReceiveTask();
|
||||
xEventGroupSetBits(udp->event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "udp_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspUdp::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
if (udp_fd_ != -1) {
|
||||
close(udp_fd_);
|
||||
udp_fd_ = -1;
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_UDP_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EspUdp::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = send(udp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EspUdp::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = recv(udp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
connected_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message_callback_) {
|
||||
data.resize(ret);
|
||||
message_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef ESP_UDP_H
|
||||
#define ESP_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_UDP_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspUdp : public Udp {
|
||||
public:
|
||||
EspUdp();
|
||||
~EspUdp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
int udp_fd_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // ESP_UDP_H
|
||||
@@ -1,723 +0,0 @@
|
||||
#include "http_client.h"
|
||||
#include "network_interface.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
static const char *TAG = "HttpClient";
|
||||
|
||||
HttpClient::HttpClient(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient() {
|
||||
if (connected_) {
|
||||
Close();
|
||||
}
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
void HttpClient::SetTimeout(int timeout_ms) {
|
||||
timeout_ms_ = timeout_ms;
|
||||
}
|
||||
|
||||
void HttpClient::SetHeader(const std::string& key, const std::string& value) {
|
||||
// 转换key为小写用于存储和查找,但保留原始key用于输出
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
headers_[lower_key] = HeaderEntry(key, value);
|
||||
}
|
||||
|
||||
void HttpClient::SetContent(std::string&& content) {
|
||||
content_ = std::move(content);
|
||||
}
|
||||
|
||||
bool HttpClient::ParseUrl(const std::string& url) {
|
||||
// 解析 URL: protocol://host:port/path
|
||||
size_t protocol_end = url.find("://");
|
||||
if (protocol_end == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid URL format: %s", url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
protocol_ = url.substr(0, protocol_end);
|
||||
std::transform(protocol_.begin(), protocol_.end(), protocol_.begin(), ::tolower);
|
||||
|
||||
size_t host_start = protocol_end + 3;
|
||||
size_t path_start = url.find("/", host_start);
|
||||
size_t port_start = url.find(":", host_start);
|
||||
|
||||
// 默认端口
|
||||
if (protocol_ == "https") {
|
||||
port_ = 443;
|
||||
} else {
|
||||
port_ = 80;
|
||||
}
|
||||
|
||||
if (path_start == std::string::npos) {
|
||||
path_ = "/";
|
||||
if (port_start != std::string::npos) {
|
||||
host_ = url.substr(host_start, port_start - host_start);
|
||||
std::string port_str = url.substr(port_start + 1);
|
||||
char* endptr;
|
||||
long port = strtol(port_str.c_str(), &endptr, 10);
|
||||
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
|
||||
port_ = static_cast<int>(port);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
host_ = url.substr(host_start);
|
||||
}
|
||||
} else {
|
||||
path_ = url.substr(path_start);
|
||||
if (port_start != std::string::npos && port_start < path_start) {
|
||||
host_ = url.substr(host_start, port_start - host_start);
|
||||
std::string port_str = url.substr(port_start + 1, path_start - port_start - 1);
|
||||
char* endptr;
|
||||
long port = strtol(port_str.c_str(), &endptr, 10);
|
||||
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
|
||||
port_ = static_cast<int>(port);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
host_ = url.substr(host_start, path_start - host_start);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Parsed URL: protocol=%s, host=%s, port=%d, path=%s",
|
||||
protocol_.c_str(), host_.c_str(), port_, path_.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HttpClient::BuildHttpRequest() {
|
||||
std::ostringstream request;
|
||||
|
||||
// 请求行
|
||||
request << method_ << " " << path_ << " HTTP/1.1\r\n";
|
||||
|
||||
// Host 头
|
||||
request << "Host: " << host_;
|
||||
if ((protocol_ == "http" && port_ != 80) || (protocol_ == "https" && port_ != 443)) {
|
||||
request << ":" << port_;
|
||||
}
|
||||
request << "\r\n";
|
||||
|
||||
// 用户自定义头部(使用原始key输出)
|
||||
for (const auto& [lower_key, header_entry] : headers_) {
|
||||
request << header_entry.original_key << ": " << header_entry.value << "\r\n";
|
||||
}
|
||||
|
||||
// 内容相关头部(仅在用户未设置时添加)
|
||||
bool user_set_content_length = headers_.find("content-length") != headers_.end();
|
||||
bool user_set_transfer_encoding = headers_.find("transfer-encoding") != headers_.end();
|
||||
bool has_content = content_.has_value() && !content_->empty();
|
||||
if (has_content && !user_set_content_length) {
|
||||
request << "Content-Length: " << content_->size() << "\r\n";
|
||||
} else if ((method_ == "POST" || method_ == "PUT") && !user_set_content_length && !user_set_transfer_encoding) {
|
||||
if (request_chunked_) {
|
||||
request << "Transfer-Encoding: chunked\r\n";
|
||||
} else {
|
||||
request << "Content-Length: 0\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 连接控制(仅在用户未设置时添加)
|
||||
if (headers_.find("connection") == headers_.end()) {
|
||||
request << "Connection: close\r\n";
|
||||
}
|
||||
|
||||
// 结束头部
|
||||
request << "\r\n";
|
||||
ESP_LOGD(TAG, "HTTP request headers:\n%s", request.str().c_str());
|
||||
|
||||
// 请求体
|
||||
if (has_content) {
|
||||
request << *content_;
|
||||
}
|
||||
|
||||
return request.str();
|
||||
}
|
||||
|
||||
bool HttpClient::Open(const std::string& method, const std::string& url) {
|
||||
method_ = method;
|
||||
url_ = url;
|
||||
|
||||
// 重置状态
|
||||
status_code_ = -1;
|
||||
response_headers_.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.clear();
|
||||
}
|
||||
body_offset_ = 0;
|
||||
content_length_ = 0;
|
||||
total_body_received_ = 0;
|
||||
eof_ = false;
|
||||
headers_received_ = false;
|
||||
response_chunked_ = false;
|
||||
connection_error_ = false; // 重置连接错误状态
|
||||
parse_state_ = ParseState::STATUS_LINE;
|
||||
chunk_size_ = 0;
|
||||
chunk_received_ = 0;
|
||||
rx_buffer_.clear();
|
||||
|
||||
xEventGroupClearBits(event_group_handle_,
|
||||
EC801E_HTTP_EVENT_HEADERS_RECEIVED |
|
||||
EC801E_HTTP_EVENT_BODY_RECEIVED |
|
||||
EC801E_HTTP_EVENT_ERROR |
|
||||
EC801E_HTTP_EVENT_COMPLETE);
|
||||
|
||||
// 解析 URL
|
||||
if (!ParseUrl(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 建立 TCP 连接
|
||||
if (protocol_ == "https") {
|
||||
tcp_ = network_->CreateSsl(connect_id_);
|
||||
} else {
|
||||
tcp_ = network_->CreateTcp(connect_id_);
|
||||
}
|
||||
|
||||
// 设置 TCP 数据接收回调
|
||||
tcp_->OnStream([this](const std::string& data) {
|
||||
OnTcpData(data);
|
||||
});
|
||||
|
||||
// 设置 TCP 断开连接回调
|
||||
tcp_->OnDisconnected([this]() {
|
||||
OnTcpDisconnected();
|
||||
});
|
||||
if (!tcp_->Connect(host_, port_)) {
|
||||
ESP_LOGE(TAG, "TCP connection failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
request_chunked_ = (method_ == "POST" || method_ == "PUT") && !content_.has_value();
|
||||
|
||||
// 构建并发送 HTTP 请求
|
||||
std::string http_request = BuildHttpRequest();
|
||||
if (tcp_->Send(http_request) <= 0) {
|
||||
ESP_LOGE(TAG, "Send HTTP request failed");
|
||||
tcp_->Disconnect();
|
||||
connected_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpClient::Close() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
connected_ = false;
|
||||
write_cv_.notify_all();
|
||||
tcp_->Disconnect();
|
||||
|
||||
eof_ = true;
|
||||
cv_.notify_all();
|
||||
ESP_LOGD(TAG, "HTTP connection closed");
|
||||
}
|
||||
|
||||
void HttpClient::OnTcpData(const std::string& data) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查 body_chunks_ 大小,如果超过 8KB
|
||||
{
|
||||
std::unique_lock<std::mutex> read_lock(read_mutex_);
|
||||
write_cv_.wait(read_lock, [this, size=data.size()] {
|
||||
size_t total_size = size;
|
||||
for (const auto& chunk : body_chunks_) {
|
||||
total_size += chunk.data.size();
|
||||
}
|
||||
return total_size < MAX_BODY_CHUNKS_SIZE || !connected_;
|
||||
});
|
||||
}
|
||||
|
||||
rx_buffer_.append(data);
|
||||
ProcessReceivedData();
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
void HttpClient::OnTcpDisconnected() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
connected_ = false;
|
||||
|
||||
// 检查数据是否完整接收
|
||||
if (headers_received_ && !IsDataComplete()) {
|
||||
// 如果已接收头部但数据不完整,标记为连接错误
|
||||
connection_error_ = true;
|
||||
ESP_LOGE(TAG, "Connection closed prematurely, expected %u bytes but only received %u bytes",
|
||||
content_length_, total_body_received_);
|
||||
} else {
|
||||
// 数据完整或还未开始接收响应体,正常结束
|
||||
eof_ = true;
|
||||
}
|
||||
|
||||
cv_.notify_all(); // 通知所有等待的读取操作
|
||||
}
|
||||
|
||||
void HttpClient::ProcessReceivedData() {
|
||||
while (!rx_buffer_.empty() && parse_state_ != ParseState::COMPLETE) {
|
||||
switch (parse_state_) {
|
||||
case ParseState::STATUS_LINE: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
if (ParseStatusLine(line)) {
|
||||
parse_state_ = ParseState::HEADERS;
|
||||
} else {
|
||||
SetError();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::HEADERS: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
// 检查是否为空行(头部结束标记)
|
||||
// GetNextLine 已经移除了 \r,所以空行就是 empty()
|
||||
if (line.empty()) {
|
||||
// 检查是否为 chunked 编码
|
||||
auto it = response_headers_.find("transfer-encoding");
|
||||
if (it != response_headers_.end() &&
|
||||
it->second.value.find("chunked") != std::string::npos) {
|
||||
response_chunked_ = true;
|
||||
parse_state_ = ParseState::CHUNK_SIZE;
|
||||
} else {
|
||||
parse_state_ = ParseState::BODY;
|
||||
auto cl_it = response_headers_.find("content-length");
|
||||
if (cl_it != response_headers_.end()) {
|
||||
char* endptr;
|
||||
unsigned long length = strtoul(cl_it->second.value.c_str(), &endptr, 10);
|
||||
if (endptr != cl_it->second.value.c_str() && *endptr == '\0') {
|
||||
content_length_ = static_cast<size_t>(length);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid Content-Length: %s", cl_it->second.value.c_str());
|
||||
content_length_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 头部结束
|
||||
headers_received_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_HEADERS_RECEIVED);
|
||||
} else {
|
||||
if (!ParseHeaderLine(line)) {
|
||||
SetError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::BODY: {
|
||||
if (response_chunked_) {
|
||||
ParseChunkedBody(rx_buffer_);
|
||||
} else {
|
||||
ParseRegularBody(rx_buffer_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_SIZE: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
chunk_size_ = ParseChunkSize(line);
|
||||
chunk_received_ = 0;
|
||||
|
||||
if (chunk_size_ == 0) {
|
||||
parse_state_ = ParseState::CHUNK_TRAILER;
|
||||
} else {
|
||||
parse_state_ = ParseState::CHUNK_DATA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_DATA: {
|
||||
size_t available = std::min(rx_buffer_.size(), chunk_size_ - chunk_received_);
|
||||
if (available > 0) {
|
||||
std::string chunk_data = rx_buffer_.substr(0, available);
|
||||
AddBodyData(std::move(chunk_data));
|
||||
total_body_received_ += available;
|
||||
rx_buffer_.erase(0, available);
|
||||
chunk_received_ += available;
|
||||
|
||||
if (chunk_received_ == chunk_size_) {
|
||||
// 跳过 chunk 后的 CRLF
|
||||
if (rx_buffer_.size() >= 2 && rx_buffer_.substr(0, 2) == "\r\n") {
|
||||
rx_buffer_.erase(0, 2);
|
||||
}
|
||||
parse_state_ = ParseState::CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
if (available == 0) return; // 需要更多数据
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_TRAILER: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
if (line.empty()) {
|
||||
parse_state_ = ParseState::COMPLETE;
|
||||
eof_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
|
||||
}
|
||||
// 忽略 trailer 头部
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::COMPLETE:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成(非 chunked 模式)
|
||||
if (parse_state_ == ParseState::BODY && !response_chunked_ &&
|
||||
content_length_ > 0 && total_body_received_ >= content_length_) {
|
||||
parse_state_ = ParseState::COMPLETE;
|
||||
eof_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
|
||||
ESP_LOGD(TAG, "HTTP response body received: %u/%u bytes", total_body_received_, content_length_);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::ParseStatusLine(const std::string& line) {
|
||||
// HTTP/1.1 200 OK
|
||||
std::istringstream iss(line);
|
||||
std::string version, status_str, reason;
|
||||
|
||||
if (!(iss >> version >> status_str)) {
|
||||
ESP_LOGE(TAG, "Invalid status line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::getline(iss, reason); // 获取剩余部分作为原因短语
|
||||
|
||||
// 安全地解析状态码
|
||||
char* endptr;
|
||||
long status = strtol(status_str.c_str(), &endptr, 10);
|
||||
|
||||
if (endptr == status_str.c_str() || *endptr != '\0' || status < 100 || status > 999) {
|
||||
ESP_LOGE(TAG, "Parse status code failed: %s", status_str.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
status_code_ = static_cast<int>(status);
|
||||
ESP_LOGD(TAG, "HTTP status code: %d", status_code_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpClient::ParseHeaderLine(const std::string& line) {
|
||||
size_t colon_pos = line.find(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid header line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, colon_pos);
|
||||
std::string value = line.substr(colon_pos + 1);
|
||||
|
||||
// 去除前后空格
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
// 转换为小写键名用于存储和查找,同时保存原始key
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
|
||||
response_headers_[lower_key] = HeaderEntry(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpClient::ParseChunkedBody(const std::string& data) {
|
||||
// Chunked body 在 ProcessReceivedData 中的状态机中处理
|
||||
}
|
||||
|
||||
void HttpClient::ParseRegularBody(const std::string& data) {
|
||||
if (!data.empty()) {
|
||||
AddBodyData(data); // 使用新的方法添加数据
|
||||
total_body_received_ += data.size(); // 累加接收的字节数
|
||||
rx_buffer_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t HttpClient::ParseChunkSize(const std::string& chunk_size_line) {
|
||||
// 解析 chunk size(十六进制)
|
||||
std::string size_str = chunk_size_line;
|
||||
|
||||
// 移除 CRLF 和任何扩展
|
||||
size_t semi_pos = size_str.find(';');
|
||||
if (semi_pos != std::string::npos) {
|
||||
size_str = size_str.substr(0, semi_pos);
|
||||
}
|
||||
|
||||
size_str.erase(size_str.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
// 安全地解析十六进制 chunk 大小
|
||||
char* endptr;
|
||||
unsigned long chunk_size = strtoul(size_str.c_str(), &endptr, 16);
|
||||
|
||||
if (endptr == size_str.c_str() || *endptr != '\0') {
|
||||
ESP_LOGE(TAG, "Parse chunk size failed: %s", size_str.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<size_t>(chunk_size);
|
||||
}
|
||||
|
||||
std::string HttpClient::GetNextLine(std::string& buffer) {
|
||||
size_t pos = buffer.find('\n');
|
||||
if (pos == std::string::npos) {
|
||||
return ""; // 没有完整的行
|
||||
}
|
||||
|
||||
std::string line = buffer.substr(0, pos);
|
||||
buffer.erase(0, pos + 1);
|
||||
|
||||
// 移除 CR
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
bool HttpClient::HasCompleteLine(const std::string& buffer) {
|
||||
return buffer.find('\n') != std::string::npos;
|
||||
}
|
||||
|
||||
void HttpClient::SetError() {
|
||||
ESP_LOGE(TAG, "HTTP parse error");
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_ERROR);
|
||||
}
|
||||
|
||||
int HttpClient::Read(char* buffer, size_t buffer_size) {
|
||||
std::unique_lock<std::mutex> read_lock(read_mutex_);
|
||||
|
||||
// 如果连接异常断开,返回错误
|
||||
if (connection_error_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 如果已经到达文件末尾且没有更多数据,返回0
|
||||
if (eof_ && body_chunks_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果有数据可读,直接返回
|
||||
while (!body_chunks_.empty()) {
|
||||
auto& front_chunk = body_chunks_.front();
|
||||
size_t bytes_read = front_chunk.read(buffer, buffer_size);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
// 如果当前chunk已读完,移除它
|
||||
if (front_chunk.empty()) {
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
// 通知等待的写入操作
|
||||
write_cv_.notify_one();
|
||||
return static_cast<int>(bytes_read);
|
||||
}
|
||||
|
||||
// 如果chunk为空,移除它并继续下一个
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
|
||||
// 如果连接已断开,检查是否有错误
|
||||
if (!connected_) {
|
||||
if (connection_error_) {
|
||||
return -1; // 连接异常断开
|
||||
}
|
||||
return 0; // 正常结束
|
||||
}
|
||||
|
||||
// 等待数据或连接关闭
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(read_lock, timeout, [this] {
|
||||
return !body_chunks_.empty() || eof_ || !connected_ || connection_error_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP content receive timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 再次检查连接错误状态
|
||||
if (connection_error_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 再次检查是否有数据可读
|
||||
while (!body_chunks_.empty()) {
|
||||
auto& front_chunk = body_chunks_.front();
|
||||
size_t bytes_read = front_chunk.read(buffer, buffer_size);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
// 如果当前chunk已读完,移除它
|
||||
if (front_chunk.empty()) {
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
// 通知等待的写入操作
|
||||
write_cv_.notify_one();
|
||||
return static_cast<int>(bytes_read);
|
||||
}
|
||||
|
||||
// 如果chunk为空,移除它并继续下一个
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
|
||||
// 连接已关闭或到达EOF,返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpClient::Write(const char* buffer, size_t buffer_size) {
|
||||
if (!connected_ || !request_chunked_) {
|
||||
ESP_LOGE(TAG, "Cannot write: connection closed or not chunked mode");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer_size == 0) {
|
||||
// 发送结束 chunk
|
||||
std::string end_chunk = "0\r\n\r\n";
|
||||
return tcp_->Send(end_chunk);
|
||||
}
|
||||
|
||||
// 发送 chunk
|
||||
std::ostringstream chunk;
|
||||
chunk << std::hex << buffer_size << "\r\n";
|
||||
chunk.write(buffer, buffer_size);
|
||||
chunk << "\r\n";
|
||||
|
||||
std::string chunk_data = chunk.str();
|
||||
return tcp_->Send(chunk_data);
|
||||
}
|
||||
|
||||
int HttpClient::GetStatusCode() {
|
||||
if (!headers_received_) {
|
||||
// 等待头部接收
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_,
|
||||
EC801E_HTTP_EVENT_HEADERS_RECEIVED | EC801E_HTTP_EVENT_ERROR,
|
||||
pdFALSE, pdFALSE,
|
||||
pdMS_TO_TICKS(timeout_ms_));
|
||||
|
||||
if (bits & EC801E_HTTP_EVENT_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
if (!(bits & EC801E_HTTP_EVENT_HEADERS_RECEIVED)) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP headers receive timeout");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return status_code_;
|
||||
}
|
||||
|
||||
std::string HttpClient::GetResponseHeader(const std::string& key) const {
|
||||
// 转换为小写进行查找
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
|
||||
auto it = response_headers_.find(lower_key);
|
||||
if (it != response_headers_.end()) {
|
||||
return it->second.value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t HttpClient::GetBodyLength() {
|
||||
if (!headers_received_) {
|
||||
GetStatusCode(); // 这会等待头部接收
|
||||
}
|
||||
|
||||
if (response_chunked_) {
|
||||
return 0; // Chunked 模式下长度未知
|
||||
}
|
||||
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
void HttpClient::AddBodyData(const std::string& data) {
|
||||
if (data.empty()) return;
|
||||
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.emplace_back(data); // 使用构造函数,避免额外的拷贝
|
||||
cv_.notify_one(); // 通知有新数据
|
||||
write_cv_.notify_one(); // 通知写入操作
|
||||
}
|
||||
|
||||
void HttpClient::AddBodyData(std::string&& data) {
|
||||
if (data.empty()) return;
|
||||
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.emplace_back(std::move(data)); // 使用移动语义,避免拷贝
|
||||
cv_.notify_one(); // 通知有新数据
|
||||
write_cv_.notify_one(); // 通知写入操作
|
||||
}
|
||||
|
||||
std::string HttpClient::ReadAll() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
// 等待完成或出错
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool completed = cv_.wait_for(lock, timeout, [this] {
|
||||
return eof_ || connection_error_;
|
||||
});
|
||||
|
||||
if (!completed) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP content receive complete timeout");
|
||||
return ""; // 超时返回空字符串
|
||||
}
|
||||
|
||||
// 如果连接异常断开,返回空字符串并记录错误
|
||||
if (connection_error_) {
|
||||
ESP_LOGE(TAG, "Cannot read all data: connection closed prematurely");
|
||||
return "";
|
||||
}
|
||||
|
||||
// 收集所有数据
|
||||
std::string result;
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
for (const auto& chunk : body_chunks_) {
|
||||
result.append(chunk.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HttpClient::IsDataComplete() const {
|
||||
// 对于chunked编码,如果parse_state_是COMPLETE,说明接收完整
|
||||
if (response_chunked_) {
|
||||
return parse_state_ == ParseState::COMPLETE;
|
||||
}
|
||||
|
||||
// 对于固定长度,检查是否接收了完整的content-length
|
||||
if (content_length_ > 0) {
|
||||
return total_body_received_ >= content_length_;
|
||||
}
|
||||
|
||||
// 如果没有content-length且不是chunked,当连接关闭时认为完整
|
||||
// 这种情况通常用于HTTP/1.0或者content-length为0的响应
|
||||
return true;
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "ml307_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include "ml307_tcp.h"
|
||||
#include "ml307_ssl.h"
|
||||
#include "ml307_udp.h"
|
||||
#include "ml307_mqtt.h"
|
||||
#include "ml307_http.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
#define TAG "Ml307AtModem"
|
||||
|
||||
|
||||
Ml307AtModem::Ml307AtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
|
||||
// 子类特定的初始化在这里
|
||||
// Reset HTTP instances
|
||||
ResetConnections();
|
||||
}
|
||||
|
||||
void Ml307AtModem::ResetConnections() {
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=0");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=1");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=2");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=3");
|
||||
}
|
||||
|
||||
void Ml307AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
// Handle Common URC
|
||||
AtModem::HandleUrc(command, arguments);
|
||||
// Handle ML307 URC
|
||||
if (command == "MIPCALL" && arguments.size() >= 3) {
|
||||
if (arguments[1].int_value == 1) {
|
||||
auto ip = arguments[2].string_value;
|
||||
ESP_LOGI(TAG, "PDP Context %d IP: %s", arguments[0].int_value, ip.c_str());
|
||||
network_ready_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
|
||||
}
|
||||
} else if (command == "MATREADY") {
|
||||
if (network_ready_) {
|
||||
network_ready_ = false;
|
||||
if (on_network_state_changed_) {
|
||||
on_network_state_changed_(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ml307AtModem::Reboot() {
|
||||
at_uart_->SendCommand("AT+MREBOOT=0");
|
||||
}
|
||||
|
||||
bool Ml307AtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
if (enable) {
|
||||
if (delay_seconds > 0) {
|
||||
at_uart_->SendCommand("AT+MLPMCFG=\"delaysleep\"," + std::to_string(delay_seconds));
|
||||
}
|
||||
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",2,0");
|
||||
} else {
|
||||
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",0,0");
|
||||
}
|
||||
}
|
||||
|
||||
NetworkStatus Ml307AtModem::WaitForNetworkReady(int timeout_ms) {
|
||||
NetworkStatus status = AtModem::WaitForNetworkReady(timeout_ms);
|
||||
if (status == NetworkStatus::Ready) {
|
||||
// Wait for IP address, maximum total wait time is 4270ms
|
||||
int delay_ms = 10;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
at_uart_->SendCommand("AT+MIPCALL?");
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY, pdFALSE, pdTRUE, pdMS_TO_TICKS(delay_ms));
|
||||
if (bits & AT_EVENT_NETWORK_READY) {
|
||||
return NetworkStatus::Ready;
|
||||
}
|
||||
delay_ms = std::min(delay_ms * 2, 1000);
|
||||
}
|
||||
ESP_LOGE(TAG, "Network ready but no IP address");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> Ml307AtModem::CreateHttp(int connect_id) {
|
||||
return std::make_unique<Ml307Http>(at_uart_);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ml307AtModem::CreateTcp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Tcp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ml307AtModem::CreateSsl(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Ssl>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> Ml307AtModem::CreateUdp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Udp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> Ml307AtModem::CreateMqtt(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Mqtt>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> Ml307AtModem::CreateWebSocket(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef _ML307_AT_MODEM_H_
|
||||
#define _ML307_AT_MODEM_H_
|
||||
|
||||
#include "at_modem.h"
|
||||
#include "tcp.h"
|
||||
#include "udp.h"
|
||||
#include "http.h"
|
||||
#include "mqtt.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
class Ml307AtModem : public AtModem {
|
||||
public:
|
||||
Ml307AtModem(std::shared_ptr<AtUart> at_uart);
|
||||
~Ml307AtModem() override = default;
|
||||
|
||||
void Reboot() override;
|
||||
bool SetSleepMode(bool enable, int delay_seconds=0) override;
|
||||
NetworkStatus WaitForNetworkReady(int timeout_ms=-1) override;
|
||||
|
||||
// 实现基类的纯虚函数
|
||||
std::unique_ptr<Http> CreateHttp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
|
||||
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
|
||||
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
|
||||
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
|
||||
|
||||
protected:
|
||||
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
|
||||
void ResetConnections();
|
||||
};
|
||||
|
||||
|
||||
#endif // _ML307_AT_MODEM_H_
|
||||
@@ -1,353 +0,0 @@
|
||||
#include "ml307_http.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
|
||||
static const char *TAG = "Ml307Http";
|
||||
|
||||
Ml307Http::Ml307Http(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MHTTPURC") {
|
||||
if (arguments[1].int_value == http_id_) {
|
||||
auto& type = arguments[0].string_value;
|
||||
if (type == "header") {
|
||||
eof_ = false;
|
||||
body_offset_ = 0;
|
||||
body_.clear();
|
||||
status_code_ = arguments[2].int_value;
|
||||
if (arguments.size() >= 5) {
|
||||
ParseResponseHeaders(at_uart_->DecodeHex(arguments[4].string_value));
|
||||
} else {
|
||||
// FIXME: <header> 被分包发送
|
||||
ESP_LOGE(TAG, "Missing header");
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED);
|
||||
} else if (type == "content") {
|
||||
// +MHTTPURC: "content",<httpid>,<content_len>,<sum_len>,<cur_len>,<data>
|
||||
std::string decoded_data;
|
||||
if (arguments.size() >= 6) {
|
||||
at_uart_->DecodeHexAppend(decoded_data, arguments[5].string_value.c_str(), arguments[5].string_value.length());
|
||||
} else {
|
||||
// FIXME: <data> 被分包发送
|
||||
ESP_LOGE(TAG, "Missing content");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
body_.append(decoded_data);
|
||||
|
||||
// chunked传输时,EOF由cur_len == 0判断,非 chunked传输时,EOF由content_len判断
|
||||
if (response_chunked_) {
|
||||
eof_ = arguments[4].int_value == 0;
|
||||
} else {
|
||||
eof_ = arguments[3].int_value >= arguments[2].int_value;
|
||||
}
|
||||
|
||||
body_offset_ += arguments[4].int_value;
|
||||
if (arguments[3].int_value > body_offset_) {
|
||||
ESP_LOGE(TAG, "body_offset_: %u, arguments[3].int_value: %d", body_offset_, arguments[3].int_value);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
cv_.notify_one(); // 使用条件变量通知
|
||||
} else if (type == "err") {
|
||||
error_code_ = arguments[2].int_value;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
|
||||
} else if (type == "ind") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_IND);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown HTTP event: %s", type.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MHTTPCREATE") {
|
||||
http_id_ = arguments[0].int_value;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED);
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
|
||||
Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int Ml307Http::Read(char* buffer, size_t buffer_size) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
if (eof_ && body_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 使用条件变量等待数据
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(lock, timeout, [this] {
|
||||
return !body_.empty() || eof_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t bytes_to_read = std::min(body_.size(), buffer_size);
|
||||
std::memcpy(buffer, body_.data(), bytes_to_read);
|
||||
body_.erase(0, bytes_to_read);
|
||||
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
int Ml307Http::Write(const char* buffer, size_t buffer_size) {
|
||||
if (buffer_size == 0) { // FIXME: 模组好像不支持发送空数据
|
||||
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0,2,\"0D0A\"";
|
||||
at_uart_->SendCommand(command);
|
||||
return 0;
|
||||
}
|
||||
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",1," + std::to_string(buffer_size);
|
||||
at_uart_->SendCommand(command);
|
||||
at_uart_->SendCommand(std::string(buffer, buffer_size));
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
Ml307Http::~Ml307Http() {
|
||||
if (instance_active_) {
|
||||
Close();
|
||||
}
|
||||
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
void Ml307Http::SetHeader(const std::string& key, const std::string& value) {
|
||||
headers_[key] = value;
|
||||
}
|
||||
|
||||
void Ml307Http::SetContent(std::string&& content) {
|
||||
content_ = std::make_optional(std::move(content));
|
||||
}
|
||||
|
||||
void Ml307Http::SetTimeout(int timeout_ms) {
|
||||
timeout_ms_ = timeout_ms;
|
||||
}
|
||||
|
||||
void Ml307Http::ParseResponseHeaders(const std::string& headers) {
|
||||
std::istringstream iss(headers);
|
||||
std::string line;
|
||||
while (std::getline(iss, line)) {
|
||||
std::istringstream line_iss(line);
|
||||
std::string key, value;
|
||||
std::getline(line_iss, key, ':');
|
||||
std::getline(line_iss, value);
|
||||
|
||||
// 去除前后空格
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
response_headers_[key] = value;
|
||||
|
||||
// 检查是否为chunked传输编码
|
||||
if (key == "Transfer-Encoding" && value.find("chunked") != std::string::npos) {
|
||||
response_chunked_ = true;
|
||||
ESP_LOGI(TAG, "Found chunked transfer encoding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Http::Open(const std::string& method, const std::string& url) {
|
||||
method_ = method;
|
||||
url_ = url;
|
||||
|
||||
// 判断是否为需要发送内容的HTTP方法
|
||||
bool method_supports_content = (method_ == "POST" || method_ == "PUT");
|
||||
|
||||
// 解析URL
|
||||
size_t protocol_end = url.find("://");
|
||||
if (protocol_end != std::string::npos) {
|
||||
protocol_ = url.substr(0, protocol_end);
|
||||
size_t host_start = protocol_end + 3;
|
||||
size_t path_start = url.find("/", host_start);
|
||||
if (path_start != std::string::npos) {
|
||||
host_ = url.substr(host_start, path_start - host_start);
|
||||
path_ = url.substr(path_start);
|
||||
} else {
|
||||
host_ = url.substr(host_start);
|
||||
path_ = "/";
|
||||
}
|
||||
} else {
|
||||
// URL格式不正确
|
||||
ESP_LOGE(TAG, "Invalid URL format");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建HTTP连接
|
||||
std::string command = "AT+MHTTPCREATE=\"" + protocol_ + "://" + host_ + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to create HTTP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (!(bits & ML307_HTTP_EVENT_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP connection to be created");
|
||||
return false;
|
||||
}
|
||||
request_chunked_ = method_supports_content && !content_.has_value();
|
||||
ESP_LOGI(TAG, "HTTP connection created, ID: %d, protocol: %s, host: %s", http_id_, protocol_.c_str(), host_.c_str());
|
||||
|
||||
if (protocol_ == "https") {
|
||||
command = "AT+MHTTPCFG=\"ssl\"," + std::to_string(http_id_) + ",1,0";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
if (request_chunked_) {
|
||||
command = "AT+MHTTPCFG=\"chunked\"," + std::to_string(http_id_) + ",1";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
// Set HEX encoding OFF
|
||||
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",0,0";
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// Set timeout (seconds): connect timeout, response timeout, input timeout
|
||||
// sprintf(command, "AT+MHTTPCFG=\"timeout\",%d,%d,%d,%d", http_id_, timeout_ms_ / 1000, timeout_ms_ / 1000, timeout_ms_ / 1000);
|
||||
// modem_.Command(command);
|
||||
|
||||
// Set headers
|
||||
for (auto it = headers_.begin(); it != headers_.end(); it++) {
|
||||
auto line = it->first + ": " + it->second;
|
||||
bool is_last = std::next(it) == headers_.end();
|
||||
command = "AT+MHTTPHEADER=" + std::to_string(http_id_) + "," + std::to_string(is_last ? 0 : 1) + "," + std::to_string(line.size()) + ",\"" + line + "\"";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
if (method_supports_content && content_.has_value()) {
|
||||
command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0," + std::to_string(content_.value().size());
|
||||
at_uart_->SendCommand(command);
|
||||
at_uart_->SendCommand(content_.value());
|
||||
content_ = std::nullopt;
|
||||
}
|
||||
|
||||
// Set HEX encoding ON
|
||||
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",1,1";
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// Send request
|
||||
// method to value: 1. GET 2. POST 3. PUT 4. DELETE 5. HEAD
|
||||
const char* methods[6] = {"UNKNOWN", "GET", "POST", "PUT", "DELETE", "HEAD"};
|
||||
int method_value = 1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (strcmp(methods[i], method_.c_str()) == 0) {
|
||||
method_value = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
command = "AT+MHTTPREQUEST=" + std::to_string(http_id_) + "," + std::to_string(method_value) + ",0,";
|
||||
if (!at_uart_->SendCommand(command + at_uart_->EncodeHex(path_))) {
|
||||
ESP_LOGE(TAG, "Failed to send HTTP request");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request_chunked_) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_IND, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (!(bits & ML307_HTTP_EVENT_IND)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP IND");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ml307Http::FetchHeaders() {
|
||||
// Wait for headers
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED | ML307_HTTP_EVENT_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (bits & ML307_HTTP_EVENT_ERROR) {
|
||||
ESP_LOGE(TAG, "HTTP request error: %s", ErrorCodeToString(error_code_).c_str());
|
||||
return false;
|
||||
}
|
||||
if (!(bits & ML307_HTTP_EVENT_HEADERS_RECEIVED)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP headers to be received");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = response_headers_.find("Content-Length");
|
||||
if (it != response_headers_.end()) {
|
||||
content_length_ = std::stoul(it->second);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "HTTP request successful, status code: %d", status_code_);
|
||||
return true;
|
||||
}
|
||||
|
||||
int Ml307Http::GetStatusCode() {
|
||||
if (status_code_ == -1) {
|
||||
if (!FetchHeaders()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return status_code_;
|
||||
}
|
||||
|
||||
size_t Ml307Http::GetBodyLength() {
|
||||
if (status_code_ == -1) {
|
||||
if (!FetchHeaders()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
std::string Ml307Http::ReadAll() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(lock, timeout, [this] {
|
||||
return eof_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
|
||||
return body_;
|
||||
}
|
||||
|
||||
return body_;
|
||||
}
|
||||
|
||||
void Ml307Http::Close() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
std::string command = "AT+MHTTPDEL=" + std::to_string(http_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
instance_active_ = false;
|
||||
eof_ = true;
|
||||
cv_.notify_one();
|
||||
ESP_LOGI(TAG, "HTTP connection closed, ID: %d", http_id_);
|
||||
}
|
||||
|
||||
std::string Ml307Http::ErrorCodeToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 1: return "Domain name resolution failed";
|
||||
case 2: return "Connection to server failed";
|
||||
case 3: return "Connection to server timeout";
|
||||
case 4: return "SSL handshake failed";
|
||||
case 5: return "Connection abnormal disconnection";
|
||||
case 6: return "Request response timeout";
|
||||
case 7: return "Data reception parsing failed";
|
||||
case 8: return "Cache space insufficient";
|
||||
case 9: return "Data packet loss";
|
||||
case 10: return "File write failed";
|
||||
case 255: return "Unknown error";
|
||||
default: return "Undefined error";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Ml307Http::GetResponseHeader(const std::string& key) const {
|
||||
auto it = response_headers_.find(key);
|
||||
if (it != response_headers_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#ifndef ML307_HTTP_TRANSPORT_H
|
||||
#define ML307_HTTP_TRANSPORT_H
|
||||
|
||||
#include "at_uart.h"
|
||||
#include "http.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
|
||||
#define ML307_HTTP_EVENT_INITIALIZED (1 << 0)
|
||||
#define ML307_HTTP_EVENT_ERROR (1 << 2)
|
||||
#define ML307_HTTP_EVENT_HEADERS_RECEIVED (1 << 3)
|
||||
#define ML307_HTTP_EVENT_IND (1 << 4)
|
||||
|
||||
class Ml307Http : public Http {
|
||||
public:
|
||||
Ml307Http(std::shared_ptr<AtUart> at_uart);
|
||||
~Ml307Http();
|
||||
|
||||
void SetTimeout(int timeout_ms) override;
|
||||
void SetHeader(const std::string& key, const std::string& value) override;
|
||||
void SetContent(std::string&& content) override;
|
||||
bool Open(const std::string& method, const std::string& url) override;
|
||||
void Close() override;
|
||||
int Read(char* buffer, size_t buffer_size) override;
|
||||
int Write(const char* buffer, size_t buffer_size) override;
|
||||
|
||||
int GetStatusCode() override;
|
||||
std::string GetResponseHeader(const std::string& key) const override;
|
||||
size_t GetBodyLength() override;
|
||||
std::string ReadAll() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
|
||||
int http_id_ = -1;
|
||||
int status_code_ = -1;
|
||||
int error_code_ = -1;
|
||||
int timeout_ms_ = 30000;
|
||||
std::string rx_buffer_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
std::map<std::string, std::string> headers_;
|
||||
std::string url_;
|
||||
std::string method_;
|
||||
std::string protocol_;
|
||||
std::string host_;
|
||||
std::string path_;
|
||||
std::optional<std::string> content_ = std::nullopt;
|
||||
std::map<std::string, std::string> response_headers_;
|
||||
std::string body_;
|
||||
size_t body_offset_ = 0;
|
||||
size_t content_length_ = 0;
|
||||
bool eof_ = false;
|
||||
bool instance_active_ = false;
|
||||
bool request_chunked_ = false;
|
||||
bool response_chunked_ = false;
|
||||
|
||||
bool FetchHeaders();
|
||||
void ParseResponseHeaders(const std::string& headers);
|
||||
std::string ErrorCodeToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,196 +0,0 @@
|
||||
#include "ml307_mqtt.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "Ml307Mqtt";
|
||||
|
||||
Ml307Mqtt::Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MQTTURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == mqtt_id_) {
|
||||
auto type = arguments[0].string_value;
|
||||
if (type == "conn") {
|
||||
int error_code = arguments[2].int_value;
|
||||
if (error_code == 0) {
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
|
||||
} else {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
if (error_code == 5 || error_code == 6) {
|
||||
auto error_message = ErrorToString(error_code);
|
||||
ESP_LOGW(TAG, "MQTT error occurred: %s", error_message.c_str());
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_message);
|
||||
}
|
||||
}
|
||||
} else if (type == "suback") {
|
||||
} else if (type == "publish" && arguments.size() >= 7) {
|
||||
auto topic = arguments[3].string_value;
|
||||
if (arguments[4].int_value == arguments[5].int_value) {
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, at_uart_->DecodeHex(arguments[6].string_value));
|
||||
}
|
||||
} else {
|
||||
message_payload_.append(at_uart_->DecodeHex(arguments[6].string_value));
|
||||
if (message_payload_.size() >= arguments[4].int_value && on_message_callback_) {
|
||||
on_message_callback_(topic, message_payload_);
|
||||
message_payload_.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "unhandled MQTT event: %s", type.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MQTTSTATE" && arguments.size() == 1) {
|
||||
connected_ = arguments[0].int_value != 3;
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_INITIALIZED_EVENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Mqtt::~Ml307Mqtt() {
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
EventBits_t bits;
|
||||
if (IsConnected()) {
|
||||
// 断开之前的连接
|
||||
Disconnect();
|
||||
bits = xEventGroupWaitBits(event_group_handle_, MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_DISCONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (broker_port == 8883) {
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set clean session
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"clean\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT clean session");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set keep alive and ping interval both to the same value
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT keepalive interval");
|
||||
return false;
|
||||
}
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"pingreq\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT ping interval");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set HEX encoding (ASCII for sending, HEX for receiving)
|
||||
if (!at_uart_->SendCommand("AT+MQTTCFG=\"encoding\"," + std::to_string(mqtt_id_) + ",0,1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT);
|
||||
// 创建MQTT连接
|
||||
std::string command = "AT+MQTTCONN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to create MQTT connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_CONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::IsConnected() {
|
||||
// 检查这个 id 是否已经连接
|
||||
at_uart_->SendCommand(std::string("AT+MQTTSTATE=") + std::to_string(mqtt_id_));
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_INITIALIZED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_INITIALIZED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MQTT connection");
|
||||
return false;
|
||||
}
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void Ml307Mqtt::Disconnect() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
at_uart_->SendCommand(std::string("AT+MQTTDISC=") + std::to_string(mqtt_id_));
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
|
||||
std::string command = "AT+MQTTPUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\",";
|
||||
command += std::to_string(qos) + ",0,0,";
|
||||
command += std::to_string(payload.size());
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
return false;
|
||||
}
|
||||
return at_uart_->SendCommand(payload);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+MQTTSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"," + std::to_string(qos);
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+MQTTUNSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"";
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
std::string Ml307Mqtt::ErrorToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 0:
|
||||
return "Connected";
|
||||
case 1:
|
||||
return "Reconnecting";
|
||||
case 2:
|
||||
return "Disconnected: User initiated";
|
||||
case 3:
|
||||
return "Disconnected: Rejected (protocol version, identifier, username or password error)";
|
||||
case 4:
|
||||
return "Disconnected: Server disconnected";
|
||||
case 5:
|
||||
return "Disconnected: Ping timeout";
|
||||
case 6:
|
||||
return "Disconnected: Network error";
|
||||
case 255:
|
||||
return "Disconnected: Unknown error";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#ifndef ML307_MQTT_H
|
||||
#define ML307_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include "at_uart.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define MQTT_INITIALIZED_EVENT BIT0
|
||||
#define MQTT_CONNECTED_EVENT BIT1
|
||||
#define MQTT_DISCONNECTED_EVENT BIT2
|
||||
|
||||
class Ml307Mqtt : public Mqtt {
|
||||
public:
|
||||
Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
|
||||
~Ml307Mqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int mqtt_id_;
|
||||
bool connected_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
std::string ErrorToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "ml307_ssl.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "Ml307Ssl";
|
||||
|
||||
Ml307Ssl::Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id) : Ml307Tcp(at_uart, tcp_id) {
|
||||
}
|
||||
|
||||
bool Ml307Ssl::ConfigureSsl(int port) {
|
||||
// 设置 SSL 配置
|
||||
std::string command = "AT+MSSLCFG=\"auth\",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 强制启用 SSL
|
||||
command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",1,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef ML307_SSL_H
|
||||
#define ML307_SSL_H
|
||||
|
||||
#include "ml307_tcp.h"
|
||||
|
||||
class Ml307Ssl : public Ml307Tcp {
|
||||
public:
|
||||
Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
~Ml307Ssl() = default;
|
||||
|
||||
protected:
|
||||
// 重写SSL配置方法
|
||||
bool ConfigureSsl(int port) override;
|
||||
};
|
||||
|
||||
#endif // ML307_SSL_H
|
||||
@@ -1,192 +0,0 @@
|
||||
#include "ml307_tcp.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "Ml307Tcp"
|
||||
|
||||
Ml307Tcp::Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MIPOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
|
||||
}
|
||||
} else if (command == "MIPSEND" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_SEND_COMPLETE);
|
||||
}
|
||||
} else if (command == "MIPURC" && arguments.size() >= 3) {
|
||||
if (arguments[1].int_value == tcp_id_) {
|
||||
if (arguments[0].string_value == "rtcp") {
|
||||
if (connected_ && stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "disconn") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPSTATE" && arguments.size() >= 5) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[4].string_value == "CONNECTED";
|
||||
instance_active_ = arguments[4].string_value != "INITIAL";
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Tcp::~Ml307Tcp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Tcp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+MIPSTATE=" + std::to_string(tcp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_TCP_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 断开之前的连接
|
||||
if (instance_active_) {
|
||||
command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
// 等待断开完成
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
// 配置SSL(子类可以重写)
|
||||
if (!ConfigureSsl(port)) {
|
||||
ESP_LOGE(TAG, "Failed to configure SSL");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 HEX 编码
|
||||
command = "AT+MIPCFG=\"encoding\"," + std::to_string(tcp_id_) + ",1,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+MIPOPEN=" + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection, error=%d", at_uart_->GetCmeErrorCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & ML307_TCP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ml307Tcp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Tcp::ConfigureSsl(int port) {
|
||||
std::string command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Ml307Tcp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460 / 2;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command;
|
||||
command.reserve(32 + MAX_PACKET_SIZE * 2); // 预分配最大可能需要的空间
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
// 重置command并构建新的命令,利用预分配的容量
|
||||
command.clear();
|
||||
command += "AT+MIPSEND=";
|
||||
command += std::to_string(tcp_id_);
|
||||
command += ",";
|
||||
command += std::to_string(chunk_size);
|
||||
command += ",";
|
||||
|
||||
// 直接在command字符串上进行十六进制编码
|
||||
at_uart_->EncodeHexAppend(command, data.data() + total_sent, chunk_size);
|
||||
command += "\r\n";
|
||||
|
||||
if (!at_uart_->SendCommand(command, 100, false)) {
|
||||
ESP_LOGE(TAG, "Failed to send data chunk");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_SEND_COMPLETE, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_TCP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "No send confirmation received");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef ML307_TCP_H
|
||||
#define ML307_TCP_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
|
||||
#define ML307_TCP_CONNECTED BIT0
|
||||
#define ML307_TCP_DISCONNECTED BIT1
|
||||
#define ML307_TCP_ERROR BIT2
|
||||
#define ML307_TCP_SEND_COMPLETE BIT4
|
||||
#define ML307_TCP_INITIALIZED BIT5
|
||||
|
||||
#define TCP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ml307Tcp : public Tcp {
|
||||
public:
|
||||
Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
virtual ~Ml307Tcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int tcp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
// 虚函数允许子类自定义SSL配置
|
||||
virtual bool ConfigureSsl(int port);
|
||||
};
|
||||
|
||||
#endif // ML307_TCP_H
|
||||
@@ -1,152 +0,0 @@
|
||||
#include "ml307_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ml307Udp"
|
||||
|
||||
|
||||
Ml307Udp::Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MIPOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
|
||||
}
|
||||
} else if (command == "MIPSEND" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_SEND_COMPLETE);
|
||||
}
|
||||
} else if (command == "MIPURC" && arguments.size() == 4) {
|
||||
if (arguments[1].int_value == udp_id_) {
|
||||
if (arguments[0].string_value == "rudp") {
|
||||
if (connected_ && message_callback_) {
|
||||
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "disconn") {
|
||||
connected_ = false;
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPSTATE" && arguments.size() == 5) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[4].string_value == "CONNECTED";
|
||||
instance_active_ = arguments[4].string_value != "INITIAL";
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Udp::~Ml307Udp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Udp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+MIPSTATE=" + std::to_string(udp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_UDP_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 断开之前的连接
|
||||
if (instance_active_) {
|
||||
command = "AT+MIPCLOSE=" + std::to_string(udp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
// 等待断开完成
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_UDP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 HEX 编码
|
||||
command = "AT+MIPCFG=\"encoding\"," + std::to_string(udp_id_) + ",1,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set HEX encoding");
|
||||
return false;
|
||||
}
|
||||
command = "AT+MIPCFG=\"ssl\"," + std::to_string(udp_id_) + ",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开 UDP 连接
|
||||
command = "AT+MIPOPEN=" + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open UDP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & ML307_UDP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ml307Udp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
at_uart_->SendCommand("AT+MIPCLOSE=" + std::to_string(udp_id_));
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
int Ml307Udp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460 / 2;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.size() > MAX_PACKET_SIZE) {
|
||||
ESP_LOGE(TAG, "Data chunk exceeds maximum limit");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command = "AT+MIPSEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size()) + ",";
|
||||
|
||||
// 直接在command字符串上进行十六进制编码
|
||||
at_uart_->EncodeHexAppend(command, data.data(), data.size());
|
||||
command += "\r\n";
|
||||
|
||||
if (!at_uart_->SendCommand(command, 100, false)) {
|
||||
ESP_LOGE(TAG, "Failed to send data chunk");
|
||||
return -1;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef ML307_UDP_H
|
||||
#define ML307_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define ML307_UDP_CONNECTED BIT0
|
||||
#define ML307_UDP_DISCONNECTED BIT1
|
||||
#define ML307_UDP_ERROR BIT2
|
||||
#define ML307_UDP_RECEIVE BIT3
|
||||
#define ML307_UDP_SEND_COMPLETE BIT4
|
||||
#define ML307_UDP_INITIALIZED BIT5
|
||||
|
||||
#define UDP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ml307Udp : public Udp {
|
||||
public:
|
||||
Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id);
|
||||
~Ml307Udp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int udp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // ML307_UDP_H
|
||||
@@ -1,438 +0,0 @@
|
||||
#include "web_socket.h"
|
||||
#include "network_interface.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <esp_pthread.h>
|
||||
|
||||
|
||||
#define TAG "WebSocket"
|
||||
|
||||
static std::string base64_encode(const unsigned char *data, size_t len) {
|
||||
const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string encoded;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
size_t chunk_size = std::min((size_t)3, len - i);
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
char_array_3[j] = (j < chunk_size) ? data[i + j] : 0;
|
||||
}
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
if (j <= chunk_size) {
|
||||
encoded.push_back(base64_chars[char_array_4[j]]);
|
||||
} else {
|
||||
encoded.push_back('=');
|
||||
}
|
||||
}
|
||||
|
||||
i += chunk_size;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
|
||||
WebSocket::WebSocket(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
|
||||
handshake_event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
WebSocket::~WebSocket() {
|
||||
if (connected_) {
|
||||
tcp_->Disconnect();
|
||||
}
|
||||
if (handshake_event_group_) {
|
||||
vEventGroupDelete(handshake_event_group_);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::SetHeader(const char* key, const char* value) {
|
||||
headers_[key] = value;
|
||||
}
|
||||
|
||||
void WebSocket::SetReceiveBufferSize(size_t size) {
|
||||
receive_buffer_size_ = size;
|
||||
}
|
||||
|
||||
bool WebSocket::IsConnected() const {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
bool WebSocket::Connect(const char* uri) {
|
||||
std::string uri_str(uri);
|
||||
std::string protocol, host, port, path;
|
||||
size_t pos = 0;
|
||||
size_t next_pos = 0;
|
||||
|
||||
// 解析协议
|
||||
next_pos = uri_str.find("://");
|
||||
if (next_pos == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid URI format");
|
||||
return false;
|
||||
}
|
||||
protocol = uri_str.substr(0, next_pos);
|
||||
pos = next_pos + 3;
|
||||
|
||||
// 解析主机
|
||||
next_pos = uri_str.find(':', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
next_pos = uri_str.find('/', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
host = uri_str.substr(pos);
|
||||
path = "/";
|
||||
} else {
|
||||
host = uri_str.substr(pos, next_pos - pos);
|
||||
path = uri_str.substr(next_pos);
|
||||
}
|
||||
port = (protocol == "wss") ? "443" : "80";
|
||||
} else {
|
||||
host = uri_str.substr(pos, next_pos - pos);
|
||||
pos = next_pos + 1;
|
||||
|
||||
// 解析端口
|
||||
next_pos = uri_str.find('/', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
port = uri_str.substr(pos);
|
||||
path = "/";
|
||||
} else {
|
||||
port = uri_str.substr(pos, next_pos - pos);
|
||||
path = uri_str.substr(next_pos);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Connecting to %s://%s:%s%s", protocol.c_str(), host.c_str(), port.c_str(), path.c_str());
|
||||
|
||||
// 设置 WebSocket 特定的头部
|
||||
SetHeader("Upgrade", "websocket");
|
||||
SetHeader("Connection", "Upgrade");
|
||||
SetHeader("Sec-WebSocket-Version", "13");
|
||||
|
||||
// 生成随机的 Sec-WebSocket-Key
|
||||
char key[25];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
key[i] = rand() % 256;
|
||||
}
|
||||
std::string base64_key = base64_encode(reinterpret_cast<const unsigned char*>(key), 16);
|
||||
SetHeader("Sec-WebSocket-Key", base64_key.c_str());
|
||||
|
||||
if (protocol == "wss" || protocol == "https") {
|
||||
tcp_ = network_->CreateSsl(connect_id_);
|
||||
} else {
|
||||
tcp_ = network_->CreateTcp(connect_id_);
|
||||
}
|
||||
|
||||
connected_ = false;
|
||||
// 使用 tcp 建立连接
|
||||
if (!tcp_->Connect(host, std::stoi(port))) {
|
||||
ESP_LOGE(TAG, "Failed to connect to server");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 发送 WebSocket 握手请求
|
||||
std::string request = "GET " + path + " HTTP/1.1\r\n";
|
||||
if (headers_.find("Host") == headers_.end()) {
|
||||
request += "Host: " + host + "\r\n";
|
||||
}
|
||||
for (const auto& header : headers_) {
|
||||
request += header.first + ": " + header.second + "\r\n";
|
||||
}
|
||||
request += "\r\n";
|
||||
|
||||
if (tcp_->Send(request) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to send WebSocket handshake request");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除事件位
|
||||
xEventGroupClearBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT);
|
||||
|
||||
// 设置数据接收回调来处理握手和后续的WebSocket帧
|
||||
tcp_->OnStream([this](const std::string& data) {
|
||||
this->OnTcpData(data);
|
||||
});
|
||||
|
||||
// 设置断开连接回调
|
||||
tcp_->OnDisconnected([this]() {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_) {
|
||||
on_disconnected_();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 等待握手完成,超时时间10秒
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
handshake_event_group_,
|
||||
HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT,
|
||||
pdFALSE, // 不清除事件位
|
||||
pdFALSE, // 等待任意一个事件位
|
||||
pdMS_TO_TICKS(10000) // 10秒超时
|
||||
);
|
||||
|
||||
if (bits & HANDSHAKE_SUCCESS_BIT) {
|
||||
connected_ = true;
|
||||
if (on_connected_) {
|
||||
on_connected_();
|
||||
}
|
||||
return true;
|
||||
} else if (bits & HANDSHAKE_FAILED_BIT) {
|
||||
ESP_LOGE(TAG, "WebSocket handshake failed");
|
||||
if (on_error_) {
|
||||
on_error_(-1);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WebSocket handshake timeout");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool WebSocket::Send(const std::string& data) {
|
||||
return Send(data.data(), data.size(), false);
|
||||
}
|
||||
|
||||
bool WebSocket::Send(const void* data, size_t len, bool binary, bool fin) {
|
||||
if (len > 65535) {
|
||||
ESP_LOGE(TAG, "Data too large, maximum supported size is 65535 bytes");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string frame;
|
||||
frame.reserve(len + 8); // 最大可能的帧大小(2字节帧头 + 2字节长度 + 4字节mask)
|
||||
|
||||
// 第一个字节:FIN 位 + 操作码
|
||||
uint8_t first_byte = (fin ? 0x80 : 0x00);
|
||||
if (binary) {
|
||||
first_byte |= 0x02; // 二进制帧
|
||||
} else if (!continuation_) {
|
||||
first_byte |= 0x01; // 文本帧
|
||||
} // 否则,操作码为0(延续帧)
|
||||
|
||||
frame.push_back(static_cast<char>(first_byte));
|
||||
|
||||
// 第二个字节:MASK 位 + 有效载荷长度
|
||||
if (len < 126) {
|
||||
frame.push_back(static_cast<char>(0x80 | len)); // 设置MASK位
|
||||
} else {
|
||||
frame.push_back(static_cast<char>(0x80 | 126)); // 设置MASK位
|
||||
frame.push_back(static_cast<char>((len >> 8) & 0xFF));
|
||||
frame.push_back(static_cast<char>(len & 0xFF));
|
||||
}
|
||||
|
||||
// 生成随机的4字节mask
|
||||
uint8_t mask[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
mask[i] = rand() & 0xFF;
|
||||
}
|
||||
frame.append(reinterpret_cast<const char*>(mask), 4);
|
||||
|
||||
// 添加并mask处理有效载荷
|
||||
const uint8_t* payload = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
|
||||
}
|
||||
|
||||
// 更新continuation_状态
|
||||
continuation_ = !fin;
|
||||
|
||||
// 发送帧
|
||||
std::lock_guard<std::mutex> lock(send_mutex_);
|
||||
return tcp_->Send(frame) >= 0;
|
||||
}
|
||||
|
||||
void WebSocket::Ping() {
|
||||
SendControlFrame(0x9, nullptr, 0);
|
||||
}
|
||||
|
||||
void WebSocket::Close() {
|
||||
if (connected_) {
|
||||
SendControlFrame(0x8, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::OnConnected(std::function<void()> callback) {
|
||||
on_connected_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnDisconnected(std::function<void()> callback) {
|
||||
on_disconnected_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnData(std::function<void(const char*, size_t, bool binary)> callback) {
|
||||
on_data_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnError(std::function<void(int)> callback) {
|
||||
on_error_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnTcpData(const std::string& data) {
|
||||
// 将新数据追加到接收缓冲区
|
||||
receive_buffer_.append(data);
|
||||
|
||||
if (!handshake_completed_) {
|
||||
// 检查握手响应
|
||||
size_t pos = receive_buffer_.find("\r\n\r\n");
|
||||
if (pos != std::string::npos) {
|
||||
std::string handshake_response = receive_buffer_.substr(0, pos + 4);
|
||||
receive_buffer_ = receive_buffer_.substr(pos + 4);
|
||||
|
||||
if (handshake_response.find("HTTP/1.1 101") != std::string::npos) {
|
||||
handshake_completed_ = true;
|
||||
// 设置握手成功事件
|
||||
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WebSocket handshake failed");
|
||||
// 设置握手失败事件
|
||||
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_FAILED_BIT);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 握手响应未完整接收
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理WebSocket帧
|
||||
static std::vector<char> current_message;
|
||||
static bool is_fragmented = false;
|
||||
static bool is_binary = false;
|
||||
|
||||
size_t buffer_offset = 0;
|
||||
const char* buffer = receive_buffer_.c_str();
|
||||
size_t buffer_size = receive_buffer_.size();
|
||||
|
||||
while (buffer_offset < buffer_size) {
|
||||
if (buffer_size - buffer_offset < 2) break; // 需要更多数据
|
||||
|
||||
uint8_t opcode = buffer[buffer_offset] & 0x0F;
|
||||
bool fin = (buffer[buffer_offset] & 0x80) != 0;
|
||||
uint8_t mask = buffer[buffer_offset + 1] & 0x80;
|
||||
uint64_t payload_length = buffer[buffer_offset + 1] & 0x7F;
|
||||
|
||||
size_t header_length = 2;
|
||||
if (payload_length == 126) {
|
||||
if (buffer_size - buffer_offset < 4) break; // 需要更多数据
|
||||
payload_length = (buffer[buffer_offset + 2] << 8) | buffer[buffer_offset + 3];
|
||||
header_length += 2;
|
||||
} else if (payload_length == 127) {
|
||||
if (buffer_size - buffer_offset < 10) break; // 需要更多数据
|
||||
payload_length = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
payload_length = (payload_length << 8) | buffer[buffer_offset + 2 + i];
|
||||
}
|
||||
header_length += 8;
|
||||
}
|
||||
|
||||
uint8_t mask_key[4] = {0};
|
||||
if (mask) {
|
||||
if (buffer_size - buffer_offset < header_length + 4) break; // 需要更多数据
|
||||
memcpy(mask_key, buffer + buffer_offset + header_length, 4);
|
||||
header_length += 4;
|
||||
}
|
||||
|
||||
if (buffer_size - buffer_offset < header_length + payload_length) break; // 需要更多数据
|
||||
|
||||
// 解码有效载荷
|
||||
std::vector<char> payload(payload_length);
|
||||
memcpy(payload.data(), buffer + buffer_offset + header_length, payload_length);
|
||||
if (mask) {
|
||||
for (size_t i = 0; i < payload_length; ++i) {
|
||||
payload[i] ^= mask_key[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理帧
|
||||
switch (opcode) {
|
||||
case 0x0: // 延续帧
|
||||
case 0x1: // 文本帧
|
||||
case 0x2: // 二进制帧
|
||||
if (opcode != 0x0 && is_fragmented) {
|
||||
ESP_LOGE(TAG, "Received new message frame while still fragmenting");
|
||||
break;
|
||||
}
|
||||
if (opcode != 0x0) {
|
||||
is_fragmented = !fin;
|
||||
is_binary = (opcode == 0x2);
|
||||
current_message.clear();
|
||||
}
|
||||
current_message.insert(current_message.end(), payload.begin(), payload.end());
|
||||
if (fin) {
|
||||
if (on_data_) {
|
||||
on_data_(current_message.data(), current_message.size(), is_binary);
|
||||
}
|
||||
current_message.clear();
|
||||
is_fragmented = false;
|
||||
}
|
||||
break;
|
||||
case 0x8: // 关闭帧
|
||||
connected_ = false;
|
||||
if (on_disconnected_) {
|
||||
on_disconnected_();
|
||||
}
|
||||
break;
|
||||
case 0x9: // Ping
|
||||
std::thread([this, payload, payload_length]() {
|
||||
SendControlFrame(0xA, payload.data(), payload_length);
|
||||
}).detach();
|
||||
break;
|
||||
case 0xA: // Pong
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown opcode: %d", opcode);
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_offset += header_length + payload_length;
|
||||
}
|
||||
|
||||
// 保留未处理的数据
|
||||
if (buffer_offset > 0) {
|
||||
receive_buffer_ = receive_buffer_.substr(buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool WebSocket::SendControlFrame(uint8_t opcode, const void* data, size_t len) {
|
||||
if (len > 125) {
|
||||
ESP_LOGE(TAG, "控制帧有效载荷过大");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string frame;
|
||||
frame.reserve(len + 6); // 帧头 + 掩码 + 有效载荷
|
||||
|
||||
// 第一个字节:FIN 位 + 操作码
|
||||
frame.push_back(static_cast<char>(0x80 | opcode));
|
||||
|
||||
// 第二个字节:MASK 位 + 有效载荷长度
|
||||
frame.push_back(static_cast<char>(0x80 | len));
|
||||
|
||||
// 生成随机的4字节掩码
|
||||
uint8_t mask[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
mask[i] = rand() & 0xFF;
|
||||
}
|
||||
frame.append(reinterpret_cast<const char*>(mask), 4);
|
||||
|
||||
// 添加并掩码处理有效载荷
|
||||
const uint8_t* payload = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
|
||||
}
|
||||
|
||||
// 发送帧
|
||||
std::lock_guard<std::mutex> lock(send_mutex_);
|
||||
return tcp_->Send(frame) >= 0;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
b80e5b6d6dc4bf6b0bf1a3729f52b80500e9e6b1003b3e827b78ba738283a296
|
||||
@@ -1 +0,0 @@
|
||||
dist/
|
||||
@@ -1 +0,0 @@
|
||||
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-08-09T06:36:59.752264+00:00", "files": [{"path": ".gitignore", "size": 5, "hash": "887f42eeae4276a8ba8ed3e14ec6567107ed2760d18ea7303cc715a38670fbea"}, {"path": "CMakeLists.txt", "size": 183, "hash": "64803247577ebe4b56fb98a7fcf26ab8de7b6c1853e8b684a525df6070e5e5fc"}, {"path": "idf_component.yml", "size": 245, "hash": "6eb7903c5edaf878003897ad251e69c2119d8a69394e9b5b62f6074b3b5b4e09"}, {"path": "opus_decoder.cc", "size": 1470, "hash": "e9a55ca543ac144a8e906a5c91f397335b22ca1fd183cf45d0476cbf52c9c324"}, {"path": "opus_encoder.cc", "size": 3281, "hash": "8e8d92d9040b85a29c79d00071e0d9b6fb955641269b624574d59b722497cfe2"}, {"path": "opus_resampler.cc", "size": 1139, "hash": "9303c8e3fc5bd28ed63302114eaca33b51a3634f43c278b1856223846302b77c"}, {"path": "silk_resampler.h", "size": 1361, "hash": "afac70e0c296c93cf1b24710255f96b99d44df3d8de82ef72a5a4ead59c1ecbe"}, {"path": "include/opus_decoder.h", "size": 729, "hash": "7ea7d09e2aef14b6affd38470ddb1909fd16f6d4f0ee492c29b211374ff3f982"}, {"path": "include/opus_encoder.h", "size": 1046, "hash": "c4858a454c2bca4a2ba89cbd9bf8a04f3de6fa60167a09d5a73844cbaad32d51"}, {"path": "include/opus_resampler.h", "size": 651, "hash": "89b4d2f5e0d7b626b9c40b326072d80941b71a3448ca3f50394f1694c9d64b7d"}, {"path": "include/resampler_structs.h", "size": 2615, "hash": "b038e84c7d79bcd31bbf524dba64103d17ab66c5006cd2b6ad7ba3289226cd18"}]}
|
||||
@@ -1,10 +0,0 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"opus_encoder.cc"
|
||||
"opus_decoder.cc"
|
||||
"opus_resampler.cc"
|
||||
INCLUDE_DIRS
|
||||
"include"
|
||||
PRIV_INCLUDE_DIRS
|
||||
"."
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
dependencies:
|
||||
78/esp-opus: ^1.0.5
|
||||
idf: '>=5.3'
|
||||
description: ESP32 Opus Encoder C++ wrapper
|
||||
files:
|
||||
exclude:
|
||||
- .git
|
||||
license: MIT
|
||||
repository: https://github.com/78/esp-opus-encoder
|
||||
url: https://github.com/78/esp-opus-encoder
|
||||
version: 2.4.1
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef _OPUS_DECODER_WRAPPER_H_
|
||||
#define _OPUS_DECODER_WRAPPER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "opus.h"
|
||||
|
||||
|
||||
class OpusDecoderWrapper {
|
||||
public:
|
||||
OpusDecoderWrapper(int sample_rate, int channels, int duration_ms = 60);
|
||||
~OpusDecoderWrapper();
|
||||
|
||||
bool Decode(std::vector<uint8_t>&& opus, std::vector<int16_t>& pcm);
|
||||
void ResetState();
|
||||
|
||||
inline int sample_rate() const {
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
inline int duration_ms() const {
|
||||
return duration_ms_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
struct OpusDecoder* audio_dec_ = nullptr;
|
||||
int frame_size_;
|
||||
int sample_rate_;
|
||||
int duration_ms_;
|
||||
};
|
||||
|
||||
#endif // _OPUS_DECODER_WRAPPER_H_
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef _OPUS_ENCODER_WRAPPER_H_
|
||||
#define _OPUS_ENCODER_WRAPPER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "opus.h"
|
||||
|
||||
#define MAX_OPUS_PACKET_SIZE 1000
|
||||
|
||||
|
||||
class OpusEncoderWrapper {
|
||||
public:
|
||||
OpusEncoderWrapper(int sample_rate, int channels, int duration_ms = 60);
|
||||
~OpusEncoderWrapper();
|
||||
|
||||
inline int sample_rate() const {
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
inline int duration_ms() const {
|
||||
return duration_ms_;
|
||||
}
|
||||
|
||||
void SetDtx(bool enable);
|
||||
void SetComplexity(int complexity);
|
||||
bool Encode(std::vector<int16_t>&& pcm, std::vector<uint8_t>& opus);
|
||||
void Encode(std::vector<int16_t>&& pcm, std::function<void(std::vector<uint8_t>&& opus)> handler);
|
||||
bool IsBufferEmpty() const { return in_buffer_.empty(); }
|
||||
void ResetState();
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
struct OpusEncoder* audio_enc_ = nullptr;
|
||||
int sample_rate_;
|
||||
int duration_ms_;
|
||||
int frame_size_;
|
||||
std::vector<int16_t> in_buffer_;
|
||||
};
|
||||
|
||||
#endif // _OPUS_ENCODER_H_
|
||||
@@ -1,28 +0,0 @@
|
||||
#ifndef OPUS_RESAMPLER_H
|
||||
#define OPUS_RESAMPLER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "opus.h"
|
||||
#include "resampler_structs.h"
|
||||
|
||||
class OpusResampler {
|
||||
public:
|
||||
OpusResampler();
|
||||
~OpusResampler();
|
||||
|
||||
void Configure(int input_sample_rate, int output_sample_rate);
|
||||
void Process(const int16_t *input, int input_samples, int16_t *output);
|
||||
int GetOutputSamples(int input_samples) const;
|
||||
|
||||
int input_sample_rate() const { return input_sample_rate_; }
|
||||
int output_sample_rate() const { return output_sample_rate_; }
|
||||
|
||||
private:
|
||||
silk_resampler_state_struct resampler_state_;
|
||||
int input_sample_rate_;
|
||||
int output_sample_rate_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/***********************************************************************
|
||||
Copyright (c) 2006-2011, Skype Limited. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
- Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
||||
names of specific contributors, may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SILK_RESAMPLER_STRUCTS_H
|
||||
#define SILK_RESAMPLER_STRUCTS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SILK_RESAMPLER_MAX_FIR_ORDER 36
|
||||
#define SILK_RESAMPLER_MAX_IIR_ORDER 6
|
||||
|
||||
typedef struct _silk_resampler_state_struct{
|
||||
opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */
|
||||
union{
|
||||
opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ];
|
||||
opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ];
|
||||
} sFIR;
|
||||
opus_int16 delayBuf[ 48 ];
|
||||
opus_int resampler_function;
|
||||
opus_int batchSize;
|
||||
opus_int32 invRatio_Q16;
|
||||
opus_int FIR_Order;
|
||||
opus_int FIR_Fracs;
|
||||
opus_int Fs_in_kHz;
|
||||
opus_int Fs_out_kHz;
|
||||
opus_int inputDelay;
|
||||
const opus_int16 *Coefs;
|
||||
} silk_resampler_state_struct;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SILK_RESAMPLER_STRUCTS_H */
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#include "opus_decoder.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "OpusDecoderWrapper"
|
||||
|
||||
OpusDecoderWrapper::OpusDecoderWrapper(int sample_rate, int channels, int duration_ms)
|
||||
: sample_rate_(sample_rate), duration_ms_(duration_ms) {
|
||||
int error;
|
||||
audio_dec_ = opus_decoder_create(sample_rate, channels, &error);
|
||||
if (audio_dec_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", error);
|
||||
return;
|
||||
}
|
||||
|
||||
frame_size_ = sample_rate / 1000 * channels * duration_ms;
|
||||
}
|
||||
|
||||
OpusDecoderWrapper::~OpusDecoderWrapper() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_dec_ != nullptr) {
|
||||
opus_decoder_destroy(audio_dec_);
|
||||
}
|
||||
}
|
||||
|
||||
bool OpusDecoderWrapper::Decode(std::vector<uint8_t>&& opus, std::vector<int16_t>& pcm) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_dec_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Audio decoder is not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
pcm.resize(frame_size_);
|
||||
auto ret = opus_decode(audio_dec_, opus.data(), opus.size(), pcm.data(), pcm.size(), 0);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to decode audio, error code: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resize the pcm vector to the actual decoded samples
|
||||
pcm.resize(ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpusDecoderWrapper::ResetState() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_dec_ != nullptr) {
|
||||
opus_decoder_ctl(audio_dec_, OPUS_RESET_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#include "opus_encoder.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "OpusEncoderWrapper"
|
||||
|
||||
OpusEncoderWrapper::OpusEncoderWrapper(int sample_rate, int channels, int duration_ms)
|
||||
: sample_rate_(sample_rate), duration_ms_(duration_ms) {
|
||||
int error;
|
||||
audio_enc_ = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
|
||||
if (audio_enc_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default DTX enabled
|
||||
SetDtx(true);
|
||||
// Complexity 5 almost uses up all CPU of ESP32C3 while complexity 0 uses the least
|
||||
SetComplexity(0);
|
||||
|
||||
frame_size_ = sample_rate / 1000 * channels * duration_ms;
|
||||
}
|
||||
|
||||
OpusEncoderWrapper::~OpusEncoderWrapper() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_enc_ != nullptr) {
|
||||
opus_encoder_destroy(audio_enc_);
|
||||
}
|
||||
}
|
||||
|
||||
void OpusEncoderWrapper::Encode(std::vector<int16_t>&& pcm, std::function<void(std::vector<uint8_t>&& opus)> handler) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_enc_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Audio encoder is not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_buffer_.empty()) {
|
||||
in_buffer_ = std::move(pcm);
|
||||
} else {
|
||||
/* ISSUE: https://github.com/78/esp-opus-encoder/issues/1 */
|
||||
in_buffer_.reserve(in_buffer_.size() + pcm.size());
|
||||
in_buffer_.insert(in_buffer_.end(), pcm.begin(), pcm.end());
|
||||
}
|
||||
|
||||
while (in_buffer_.size() >= frame_size_) {
|
||||
uint8_t opus[MAX_OPUS_PACKET_SIZE];
|
||||
auto ret = opus_encode(audio_enc_, in_buffer_.data(), frame_size_, opus, MAX_OPUS_PACKET_SIZE);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler != nullptr) {
|
||||
handler(std::vector<uint8_t>(opus, opus + ret));
|
||||
}
|
||||
|
||||
in_buffer_.erase(in_buffer_.begin(), in_buffer_.begin() + frame_size_);
|
||||
}
|
||||
}
|
||||
|
||||
bool OpusEncoderWrapper::Encode(std::vector<int16_t>&& pcm, std::vector<uint8_t>& opus) {
|
||||
if (audio_enc_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Audio encoder is not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pcm.size() != frame_size_) {
|
||||
ESP_LOGE(TAG, "Audio data size is not equal to frame size, size: %u, frame size: %u", pcm.size(), frame_size_);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf[MAX_OPUS_PACKET_SIZE];
|
||||
auto ret = opus_encode(audio_enc_, pcm.data(), frame_size_, buf, MAX_OPUS_PACKET_SIZE);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret);
|
||||
return false;
|
||||
}
|
||||
opus.assign(buf, buf + ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpusEncoderWrapper::ResetState() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_enc_ != nullptr) {
|
||||
opus_encoder_ctl(audio_enc_, OPUS_RESET_STATE);
|
||||
in_buffer_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void OpusEncoderWrapper::SetDtx(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_enc_ != nullptr) {
|
||||
opus_encoder_ctl(audio_enc_, OPUS_SET_DTX(enable ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
void OpusEncoderWrapper::SetComplexity(int complexity) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (audio_enc_ != nullptr) {
|
||||
opus_encoder_ctl(audio_enc_, OPUS_SET_COMPLEXITY(complexity));
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "opus_resampler.h"
|
||||
#include "silk_resampler.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define TAG "OpusResampler"
|
||||
|
||||
OpusResampler::OpusResampler() {
|
||||
}
|
||||
|
||||
OpusResampler::~OpusResampler() {
|
||||
}
|
||||
|
||||
void OpusResampler::Configure(int input_sample_rate, int output_sample_rate) {
|
||||
int encode = input_sample_rate > output_sample_rate ? 1 : 0;
|
||||
auto ret = silk_resampler_init(&resampler_state_, input_sample_rate, output_sample_rate, encode);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "Failed to initialize resampler");
|
||||
return;
|
||||
}
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
ESP_LOGI(TAG, "Resampler configured with input sample rate %d and output sample rate %d", input_sample_rate_, output_sample_rate_);
|
||||
}
|
||||
|
||||
void OpusResampler::Process(const int16_t *input, int input_samples, int16_t *output) {
|
||||
auto ret = silk_resampler(&resampler_state_, output, input, input_samples);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "Failed to process resampler");
|
||||
}
|
||||
}
|
||||
|
||||
int OpusResampler::GetOutputSamples(int input_samples) const {
|
||||
return input_samples * output_sample_rate_ / input_sample_rate_;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef _SILK_RESAMPLER_H_
|
||||
#define _SILK_RESAMPLER_H_
|
||||
|
||||
#include "opus.h"
|
||||
#include "resampler_structs.h"
|
||||
/*!
|
||||
* Initialize/reset the resampler state for a given pair of input/output sampling rates
|
||||
*/
|
||||
extern "C" opus_int silk_resampler_init(
|
||||
silk_resampler_state_struct *S, /* I/O Resampler state */
|
||||
opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */
|
||||
opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */
|
||||
opus_int forEnc /* I If 1: encoder; if 0: decoder */
|
||||
);
|
||||
|
||||
/*!
|
||||
* Resampler: convert from one sampling rate to another
|
||||
*/
|
||||
extern "C" opus_int silk_resampler(
|
||||
silk_resampler_state_struct *S, /* I/O Resampler state */
|
||||
opus_int16 out[], /* O Output signal */
|
||||
const opus_int16 in[], /* I Input signal */
|
||||
opus_int32 inLen /* I Number of input samples */
|
||||
);
|
||||
|
||||
#endif // _SILK_RESAMPLER_H_
|
||||
@@ -1 +0,0 @@
|
||||
8182b733f071d7bfe1e837f4c9f8649a63e4c937177f089e65772880c02f2e17
|
||||
10
managed_components/78__esp-opus/.gitattributes
vendored
10
managed_components/78__esp-opus/.gitattributes
vendored
@@ -1,10 +0,0 @@
|
||||
.gitignore export-ignore
|
||||
.gitattributes export-ignore
|
||||
|
||||
update_version export-ignore
|
||||
|
||||
*.bat eol=crlf
|
||||
*.sln eol=crlf
|
||||
*.vcxproj eol=crlf
|
||||
*.vcxproj.filters eol=crlf
|
||||
common.props eol=crlf
|
||||
92
managed_components/78__esp-opus/.gitignore
vendored
92
managed_components/78__esp-opus/.gitignore
vendored
@@ -1,92 +0,0 @@
|
||||
Doxyfile
|
||||
Makefile
|
||||
Makefile.in
|
||||
TAGS
|
||||
aclocal.m4
|
||||
autom4te.cache
|
||||
*.kdevelop.pcs
|
||||
*.kdevses
|
||||
compile
|
||||
config.guess
|
||||
config.h
|
||||
config.h.in
|
||||
config.log
|
||||
config.status
|
||||
config.sub
|
||||
configure
|
||||
depcomp
|
||||
INSTALL
|
||||
install-sh
|
||||
.deps
|
||||
.libs
|
||||
.dirstamp
|
||||
*.a
|
||||
*.exe
|
||||
*.la
|
||||
*-gnu.S
|
||||
testcelt
|
||||
libtool
|
||||
ltmain.sh
|
||||
missing
|
||||
m4/libtool.m4
|
||||
m4/ltoptions.m4
|
||||
m4/ltsugar.m4
|
||||
m4/ltversion.m4
|
||||
m4/lt~obsolete.m4
|
||||
opus_compare
|
||||
opus_demo
|
||||
repacketizer_demo
|
||||
stamp-h1
|
||||
test-driver
|
||||
trivial_example
|
||||
*.sw*
|
||||
*.o
|
||||
*.lo
|
||||
*.pc
|
||||
*.tar.gz
|
||||
*~
|
||||
tests/*test
|
||||
tests/test_opus_api
|
||||
tests/test_opus_decode
|
||||
tests/test_opus_encode
|
||||
tests/test_opus_extensions
|
||||
tests/test_opus_padding
|
||||
tests/test_opus_projection
|
||||
celt/arm/armopts.s
|
||||
celt/dump_modes/dump_modes
|
||||
celt/tests/test_unit_cwrs32
|
||||
celt/tests/test_unit_dft
|
||||
celt/tests/test_unit_entropy
|
||||
celt/tests/test_unit_laplace
|
||||
celt/tests/test_unit_mathops
|
||||
celt/tests/test_unit_mdct
|
||||
celt/tests/test_unit_rotation
|
||||
celt/tests/test_unit_types
|
||||
doc/doxygen_sqlite3.db
|
||||
doc/doxygen-build.stamp
|
||||
doc/html
|
||||
doc/latex
|
||||
doc/man
|
||||
package_version
|
||||
version.h
|
||||
celt/Debug
|
||||
celt/Release
|
||||
celt/x64
|
||||
silk/Debug
|
||||
silk/Release
|
||||
silk/x64
|
||||
silk/fixed/Debug
|
||||
silk/fixed/Release
|
||||
silk/fixed/x64
|
||||
silk/float/Debug
|
||||
silk/float/Release
|
||||
silk/float/x64
|
||||
silk/tests/test_unit_LPC_inv_pred_gain
|
||||
src/Debug
|
||||
src/Release
|
||||
src/x64
|
||||
/*[Bb]uild*/
|
||||
.vs/
|
||||
.vscode/
|
||||
CMakeSettings.json
|
||||
dist/
|
||||
@@ -1,6 +0,0 @@
|
||||
Jean-Marc Valin (jmvalin@jmvalin.ca)
|
||||
Koen Vos (koenvos74@gmail.com)
|
||||
Timothy Terriberry (tterribe@xiph.org)
|
||||
Karsten Vandborg Sorensen (karsten.vandborg.sorensen@skype.net)
|
||||
Soren Skak Jensen (ssjensen@gn.com)
|
||||
Gregory Maxwell (greg@xiph.org)
|
||||
File diff suppressed because one or more lines are too long
@@ -1,195 +0,0 @@
|
||||
set(CELT_SOURCES
|
||||
celt/bands.c
|
||||
celt/celt.c
|
||||
celt/celt_encoder.c
|
||||
celt/celt_decoder.c
|
||||
celt/cwrs.c
|
||||
celt/entcode.c
|
||||
celt/entdec.c
|
||||
celt/entenc.c
|
||||
celt/kiss_fft.c
|
||||
celt/laplace.c
|
||||
celt/mathops.c
|
||||
celt/mdct.c
|
||||
celt/modes.c
|
||||
celt/pitch.c
|
||||
celt/celt_lpc.c
|
||||
celt/quant_bands.c
|
||||
celt/rate.c
|
||||
celt/vq.c
|
||||
)
|
||||
|
||||
set(OPUS_SOURCES
|
||||
src/opus.c
|
||||
src/opus_decoder.c
|
||||
src/opus_encoder.c
|
||||
src/extensions.c
|
||||
src/opus_multistream.c
|
||||
src/opus_multistream_encoder.c
|
||||
src/opus_multistream_decoder.c
|
||||
src/repacketizer.c
|
||||
src/opus_projection_encoder.c
|
||||
src/opus_projection_decoder.c
|
||||
src/mapping_matrix.c
|
||||
)
|
||||
|
||||
# 定义SILK源文件列表
|
||||
set(SILK_SOURCES
|
||||
silk/CNG.c
|
||||
silk/code_signs.c
|
||||
silk/init_decoder.c
|
||||
silk/decode_core.c
|
||||
silk/decode_frame.c
|
||||
silk/decode_parameters.c
|
||||
silk/decode_indices.c
|
||||
silk/decode_pulses.c
|
||||
silk/decoder_set_fs.c
|
||||
silk/dec_API.c
|
||||
silk/enc_API.c
|
||||
silk/encode_indices.c
|
||||
silk/encode_pulses.c
|
||||
silk/gain_quant.c
|
||||
silk/interpolate.c
|
||||
silk/LP_variable_cutoff.c
|
||||
silk/NLSF_decode.c
|
||||
silk/NSQ.c
|
||||
silk/NSQ_del_dec.c
|
||||
silk/PLC.c
|
||||
silk/shell_coder.c
|
||||
silk/tables_gain.c
|
||||
silk/tables_LTP.c
|
||||
silk/tables_NLSF_CB_NB_MB.c
|
||||
silk/tables_NLSF_CB_WB.c
|
||||
silk/tables_other.c
|
||||
silk/tables_pitch_lag.c
|
||||
silk/tables_pulses_per_block.c
|
||||
silk/VAD.c
|
||||
silk/control_audio_bandwidth.c
|
||||
silk/quant_LTP_gains.c
|
||||
silk/VQ_WMat_EC.c
|
||||
silk/HP_variable_cutoff.c
|
||||
silk/NLSF_encode.c
|
||||
silk/NLSF_VQ.c
|
||||
silk/NLSF_unpack.c
|
||||
silk/NLSF_del_dec_quant.c
|
||||
silk/process_NLSFs.c
|
||||
silk/stereo_LR_to_MS.c
|
||||
silk/stereo_MS_to_LR.c
|
||||
silk/check_control_input.c
|
||||
silk/control_SNR.c
|
||||
silk/init_encoder.c
|
||||
silk/control_codec.c
|
||||
silk/A2NLSF.c
|
||||
silk/ana_filt_bank_1.c
|
||||
silk/biquad_alt.c
|
||||
silk/bwexpander_32.c
|
||||
silk/bwexpander.c
|
||||
silk/debug.c
|
||||
silk/decode_pitch.c
|
||||
silk/inner_prod_aligned.c
|
||||
silk/lin2log.c
|
||||
silk/log2lin.c
|
||||
silk/LPC_analysis_filter.c
|
||||
silk/LPC_inv_pred_gain.c
|
||||
silk/table_LSF_cos.c
|
||||
silk/NLSF2A.c
|
||||
silk/NLSF_stabilize.c
|
||||
silk/NLSF_VQ_weights_laroia.c
|
||||
silk/pitch_est_tables.c
|
||||
silk/resampler.c
|
||||
silk/resampler_down2_3.c
|
||||
silk/resampler_down2.c
|
||||
silk/resampler_private_AR2.c
|
||||
silk/resampler_private_down_FIR.c
|
||||
silk/resampler_private_IIR_FIR.c
|
||||
silk/resampler_private_up2_HQ.c
|
||||
silk/resampler_rom.c
|
||||
silk/sigm_Q15.c
|
||||
silk/sort.c
|
||||
silk/sum_sqr_shift.c
|
||||
silk/stereo_decode_pred.c
|
||||
silk/stereo_encode_pred.c
|
||||
silk/stereo_find_predictor.c
|
||||
silk/stereo_quant_pred.c
|
||||
silk/LPC_fit.c
|
||||
)
|
||||
|
||||
# 定义SILK ARM RTCD源文件列表
|
||||
set(SILK_SOURCES_ARM_RTCD
|
||||
silk/arm/arm_silk_map.c
|
||||
)
|
||||
|
||||
# 定义SILK ARM NEON Intrinsics源文件列表
|
||||
set(SILK_SOURCES_ARM_NEON_INTR
|
||||
silk/arm/biquad_alt_neon_intr.c
|
||||
silk/arm/LPC_inv_pred_gain_neon_intr.c
|
||||
silk/arm/NSQ_del_dec_neon_intr.c
|
||||
silk/arm/NSQ_neon.c
|
||||
)
|
||||
|
||||
# 定义SILK固定点源文件列表
|
||||
set(SILK_SOURCES_FIXED
|
||||
silk/fixed/LTP_analysis_filter_FIX.c
|
||||
silk/fixed/LTP_scale_ctrl_FIX.c
|
||||
silk/fixed/corrMatrix_FIX.c
|
||||
silk/fixed/encode_frame_FIX.c
|
||||
silk/fixed/find_LPC_FIX.c
|
||||
silk/fixed/find_LTP_FIX.c
|
||||
silk/fixed/find_pitch_lags_FIX.c
|
||||
silk/fixed/find_pred_coefs_FIX.c
|
||||
silk/fixed/noise_shape_analysis_FIX.c
|
||||
silk/fixed/process_gains_FIX.c
|
||||
silk/fixed/regularize_correlations_FIX.c
|
||||
silk/fixed/residual_energy16_FIX.c
|
||||
silk/fixed/residual_energy_FIX.c
|
||||
silk/fixed/warped_autocorrelation_FIX.c
|
||||
silk/fixed/apply_sine_window_FIX.c
|
||||
silk/fixed/autocorr_FIX.c
|
||||
silk/fixed/burg_modified_FIX.c
|
||||
silk/fixed/k2a_FIX.c
|
||||
silk/fixed/k2a_Q16_FIX.c
|
||||
silk/fixed/pitch_analysis_core_FIX.c
|
||||
silk/fixed/vector_ops_FIX.c
|
||||
silk/fixed/schur64_FIX.c
|
||||
silk/fixed/schur_FIX.c
|
||||
)
|
||||
|
||||
# 定义SILK固定点ARM NEON Intrinsics源文件列表
|
||||
set(SILK_SOURCES_FIXED_ARM_NEON_INTR
|
||||
silk/fixed/arm/warped_autocorrelation_FIX_neon_intr.c
|
||||
)
|
||||
|
||||
list(APPEND srcs ${CELT_SOURCES}
|
||||
${OPUS_SOURCES}
|
||||
${SILK_SOURCES}
|
||||
${SILK_SOURCES_FIXED}
|
||||
${SILK_SOURCES_ARM_RTCD}
|
||||
)
|
||||
|
||||
set(PRIV_INCLUDES "celt/" "silk/" "silk/fixed" "src/" ".")
|
||||
|
||||
# 定义编译选项
|
||||
set(OPUS_COMPILE_OPTIONS
|
||||
-DHAVE_ALLOCA_H
|
||||
-DHAVE_LRINT
|
||||
-DHAVE_LRINTF
|
||||
-DFIXED_POINT=1
|
||||
-DDISABLE_FLOAT_API
|
||||
-DHAVE_MEMORY_H
|
||||
-DUSE_ALLOCA
|
||||
-DOPUS_BUILD
|
||||
-O2
|
||||
-Wno-maybe-uninitialized
|
||||
-Wno-unused-variable
|
||||
)
|
||||
|
||||
# 根据芯片类型添加特定选项
|
||||
if(${IDF_TARGET} STREQUAL "esp32s2" OR ${IDF_TARGET} STREQUAL "esp32s3")
|
||||
list(APPEND OPUS_COMPILE_OPTIONS -DOPUS_XTENSA_LX7)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${PRIV_INCLUDES})
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${OPUS_COMPILE_OPTIONS})
|
||||
@@ -1,44 +0,0 @@
|
||||
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
||||
Jean-Marc Valin, Timothy B. Terriberry,
|
||||
CSIRO, Gregory Maxwell, Mark Borgerding,
|
||||
Erik de Castro Lopo, Mozilla, Amazon
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
||||
names of specific contributors, may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Opus is subject to the royalty-free patent licenses which are
|
||||
specified at:
|
||||
|
||||
Xiph.Org Foundation:
|
||||
https://datatracker.ietf.org/ipr/1524/
|
||||
|
||||
Microsoft Corporation:
|
||||
https://datatracker.ietf.org/ipr/1914/
|
||||
|
||||
Broadcom Corporation:
|
||||
https://datatracker.ietf.org/ipr/1526/
|
||||
@@ -1,22 +0,0 @@
|
||||
Contributions to the collaboration shall not be considered confidential.
|
||||
|
||||
Each contributor represents and warrants that it has the right and
|
||||
authority to license copyright in its contributions to the collaboration.
|
||||
|
||||
Each contributor agrees to license the copyright in the contributions
|
||||
under the Modified (2-clause or 3-clause) BSD License or the Clear BSD License.
|
||||
|
||||
Please see the IPR statements submitted to the IETF for the complete
|
||||
patent licensing details:
|
||||
|
||||
Xiph.Org Foundation:
|
||||
https://datatracker.ietf.org/ipr/1524/
|
||||
|
||||
Microsoft Corporation:
|
||||
https://datatracker.ietf.org/ipr/1914/
|
||||
|
||||
Skype Limited:
|
||||
https://datatracker.ietf.org/ipr/1602/
|
||||
|
||||
Broadcom Corporation:
|
||||
https://datatracker.ietf.org/ipr/1526/
|
||||
@@ -1,497 +0,0 @@
|
||||
# Provide the full test output for failed tests when using the parallel
|
||||
# test suite (which is enabled by default with automake 1.13+).
|
||||
export VERBOSE = yes
|
||||
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
lib_LTLIBRARIES = libopus.la
|
||||
|
||||
DIST_SUBDIRS = doc
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/celt -I$(top_srcdir)/silk \
|
||||
-I$(top_srcdir)/silk/float -I$(top_srcdir)/silk/fixed $(NE10_CFLAGS) \
|
||||
-I$(top_srcdir)/dnn
|
||||
|
||||
include celt_sources.mk
|
||||
include lpcnet_sources.mk
|
||||
include silk_sources.mk
|
||||
include opus_sources.mk
|
||||
|
||||
LPCNET_SOURCES =
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DEEP_PLC_SOURCES)
|
||||
endif
|
||||
if ENABLE_DRED
|
||||
LPCNET_SOURCES += $(DRED_SOURCES)
|
||||
endif
|
||||
if ENABLE_OSCE
|
||||
LPCNET_SOURCES += $(OSCE_SOURCES)
|
||||
endif
|
||||
|
||||
if FIXED_POINT
|
||||
SILK_SOURCES += $(SILK_SOURCES_FIXED)
|
||||
if HAVE_SSE4_1
|
||||
SILK_SOURCES += $(SILK_SOURCES_SSE4_1) $(SILK_SOURCES_FIXED_SSE4_1)
|
||||
endif
|
||||
if HAVE_ARM_NEON_INTR
|
||||
SILK_SOURCES += $(SILK_SOURCES_FIXED_ARM_NEON_INTR)
|
||||
endif
|
||||
else
|
||||
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
|
||||
if HAVE_SSE4_1
|
||||
SILK_SOURCES += $(SILK_SOURCES_SSE4_1)
|
||||
endif
|
||||
if HAVE_AVX2
|
||||
SILK_SOURCES += $(SILK_SOURCES_FLOAT_AVX2)
|
||||
endif
|
||||
endif
|
||||
|
||||
if DISABLE_FLOAT_API
|
||||
else
|
||||
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
|
||||
endif
|
||||
|
||||
if CPU_X86
|
||||
if HAVE_RTCD
|
||||
CELT_SOURCES += $(CELT_SOURCES_X86_RTCD)
|
||||
SILK_SOURCES += $(SILK_SOURCES_X86_RTCD)
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_X86_RTCD)
|
||||
endif
|
||||
endif
|
||||
if HAVE_SSE
|
||||
CELT_SOURCES += $(CELT_SOURCES_SSE)
|
||||
endif
|
||||
if HAVE_SSE2
|
||||
CELT_SOURCES += $(CELT_SOURCES_SSE2)
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_SSE2)
|
||||
endif
|
||||
endif
|
||||
if HAVE_SSE4_1
|
||||
CELT_SOURCES += $(CELT_SOURCES_SSE4_1)
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_SSE4_1)
|
||||
endif
|
||||
endif
|
||||
if HAVE_AVX2
|
||||
SILK_SOURCES += $(SILK_SOURCES_AVX2)
|
||||
CELT_SOURCES += $(CELT_SOURCES_AVX2)
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_AVX2)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
if CPU_ARM
|
||||
if HAVE_RTCD
|
||||
CELT_SOURCES += $(CELT_SOURCES_ARM_RTCD)
|
||||
SILK_SOURCES += $(SILK_SOURCES_ARM_RTCD)
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_ARM_RTCD)
|
||||
endif
|
||||
endif
|
||||
|
||||
if ENABLE_DEEP_PLC
|
||||
if HAVE_ARM_DOTPROD
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_DOTPROD)
|
||||
endif
|
||||
if HAVE_ARM_NEON_INTR
|
||||
LPCNET_SOURCES += $(DNN_SOURCES_NEON)
|
||||
endif
|
||||
endif
|
||||
|
||||
if HAVE_ARM_NEON_INTR
|
||||
CELT_SOURCES += $(CELT_SOURCES_ARM_NEON_INTR)
|
||||
SILK_SOURCES += $(SILK_SOURCES_ARM_NEON_INTR)
|
||||
endif
|
||||
|
||||
if HAVE_ARM_NE10
|
||||
CELT_SOURCES += $(CELT_SOURCES_ARM_NE10)
|
||||
endif
|
||||
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
noinst_LTLIBRARIES = libarmasm.la
|
||||
libarmasm_la_SOURCES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S)
|
||||
BUILT_SOURCES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S) \
|
||||
$(CELT_AM_SOURCES_ARM_ASM:.s.in=.s) \
|
||||
$(CELT_AM_SOURCES_ARM_ASM:.s.in=-gnu.S)
|
||||
endif
|
||||
endif
|
||||
|
||||
CLEANFILES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S) \
|
||||
$(CELT_AM_SOURCES_ARM_ASM:.s.in=-gnu.S)
|
||||
|
||||
include celt_headers.mk
|
||||
include lpcnet_headers.mk
|
||||
include silk_headers.mk
|
||||
include opus_headers.mk
|
||||
|
||||
LPCNET_HEAD =
|
||||
if ENABLE_DEEP_PLC
|
||||
LPCNET_HEAD += $(DEEP_PLC_HEAD)
|
||||
endif
|
||||
if ENABLE_DRED
|
||||
LPCNET_HEAD += $(DRED_HEAD)
|
||||
endif
|
||||
if ENABLE_OSCE
|
||||
LPCNET_HEAD += $(OSCE_HEAD)
|
||||
endif
|
||||
if ENABLE_LOSSGEN
|
||||
LPCNET_HEAD += $(LOSSGEN_HEAD)
|
||||
endif
|
||||
|
||||
libopus_la_SOURCES = $(CELT_SOURCES) $(SILK_SOURCES) $(LPCNET_SOURCES) $(OPUS_SOURCES)
|
||||
libopus_la_LDFLAGS = -no-undefined -version-info @OPUS_LT_CURRENT@:@OPUS_LT_REVISION@:@OPUS_LT_AGE@
|
||||
libopus_la_LIBADD = $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
libopus_la_LIBADD += libarmasm.la
|
||||
endif
|
||||
|
||||
pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h include/opus_projection.h
|
||||
|
||||
noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD) $(LPCNET_HEAD)
|
||||
|
||||
if EXTRA_PROGRAMS
|
||||
noinst_PROGRAMS = celt/tests/test_unit_cwrs32 \
|
||||
celt/tests/test_unit_dft \
|
||||
celt/tests/test_unit_entropy \
|
||||
celt/tests/test_unit_laplace \
|
||||
celt/tests/test_unit_mathops \
|
||||
celt/tests/test_unit_mdct \
|
||||
celt/tests/test_unit_rotation \
|
||||
celt/tests/test_unit_types \
|
||||
opus_compare \
|
||||
opus_demo \
|
||||
repacketizer_demo \
|
||||
silk/tests/test_unit_LPC_inv_pred_gain \
|
||||
tests/test_opus_api \
|
||||
tests/test_opus_decode \
|
||||
tests/test_opus_dred \
|
||||
tests/test_opus_encode \
|
||||
tests/test_opus_extensions \
|
||||
tests/test_opus_padding \
|
||||
tests/test_opus_projection \
|
||||
trivial_example
|
||||
|
||||
TESTS = celt/tests/test_unit_cwrs32 \
|
||||
celt/tests/test_unit_dft \
|
||||
celt/tests/test_unit_entropy \
|
||||
celt/tests/test_unit_laplace \
|
||||
celt/tests/test_unit_mathops \
|
||||
celt/tests/test_unit_mdct \
|
||||
celt/tests/test_unit_rotation \
|
||||
celt/tests/test_unit_types \
|
||||
silk/tests/test_unit_LPC_inv_pred_gain \
|
||||
tests/test_opus_api \
|
||||
tests/test_opus_decode \
|
||||
tests/test_opus_encode \
|
||||
tests/test_opus_extensions \
|
||||
tests/test_opus_padding \
|
||||
tests/test_opus_projection
|
||||
|
||||
opus_demo_SOURCES = src/opus_demo.c
|
||||
if ENABLE_LOSSGEN
|
||||
opus_demo_SOURCES += $(LOSSGEN_SOURCES)
|
||||
endif
|
||||
|
||||
opus_demo_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
repacketizer_demo_SOURCES = src/repacketizer_demo.c
|
||||
|
||||
repacketizer_demo_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
opus_compare_SOURCES = src/opus_compare.c
|
||||
opus_compare_LDADD = $(LIBM)
|
||||
|
||||
trivial_example_SOURCES = doc/trivial_example.c
|
||||
trivial_example_LDADD = libopus.la $(LIBM)
|
||||
|
||||
tests_test_opus_api_SOURCES = tests/test_opus_api.c tests/test_opus_common.h
|
||||
tests_test_opus_api_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
tests_test_opus_encode_SOURCES = tests/test_opus_encode.c tests/opus_encode_regressions.c tests/test_opus_common.h
|
||||
tests_test_opus_encode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
tests_test_opus_decode_SOURCES = tests/test_opus_decode.c tests/test_opus_common.h
|
||||
tests_test_opus_decode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
tests_test_opus_padding_SOURCES = tests/test_opus_padding.c tests/test_opus_common.h
|
||||
tests_test_opus_padding_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
tests_test_opus_dred_SOURCES = tests/test_opus_dred.c tests/test_opus_common.h
|
||||
tests_test_opus_dred_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
|
||||
if CUSTOM_MODES
|
||||
tests_test_opus_custom_SOURCES = tests/test_opus_custom.c tests/test_opus_common.h
|
||||
tests_test_opus_custom_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
|
||||
endif
|
||||
|
||||
CELT_OBJ = $(CELT_SOURCES:.c=.lo)
|
||||
SILK_OBJ = $(SILK_SOURCES:.c=.lo)
|
||||
LPCNET_OBJ = $(LPCNET_SOURCES:.c=.lo)
|
||||
OPUS_OBJ = $(OPUS_SOURCES:.c=.lo)
|
||||
|
||||
tests_test_opus_extensions_SOURCES = tests/test_opus_extensions.c tests/test_opus_common.h
|
||||
tests_test_opus_extensions_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
tests_test_opus_extensions_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
tests_test_opus_projection_SOURCES = tests/test_opus_projection.c tests/test_opus_common.h
|
||||
tests_test_opus_projection_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
tests_test_opus_projection_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
silk_tests_test_unit_LPC_inv_pred_gain_SOURCES = silk/tests/test_unit_LPC_inv_pred_gain.c
|
||||
silk_tests_test_unit_LPC_inv_pred_gain_LDADD = $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
silk_tests_test_unit_LPC_inv_pred_gain_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
celt_tests_test_unit_cwrs32_SOURCES = celt/tests/test_unit_cwrs32.c
|
||||
celt_tests_test_unit_cwrs32_LDADD = $(LIBM)
|
||||
|
||||
celt_tests_test_unit_dft_SOURCES = celt/tests/test_unit_dft.c
|
||||
celt_tests_test_unit_dft_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
celt_tests_test_unit_dft_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
celt_tests_test_unit_entropy_SOURCES = celt/tests/test_unit_entropy.c
|
||||
celt_tests_test_unit_entropy_LDADD = $(LIBM)
|
||||
|
||||
celt_tests_test_unit_laplace_SOURCES = celt/tests/test_unit_laplace.c
|
||||
celt_tests_test_unit_laplace_LDADD = $(LIBM)
|
||||
|
||||
celt_tests_test_unit_mathops_SOURCES = celt/tests/test_unit_mathops.c
|
||||
celt_tests_test_unit_mathops_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
celt_tests_test_unit_mathops_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
celt_tests_test_unit_mdct_SOURCES = celt/tests/test_unit_mdct.c
|
||||
celt_tests_test_unit_mdct_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
celt_tests_test_unit_mdct_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
celt_tests_test_unit_rotation_SOURCES = celt/tests/test_unit_rotation.c
|
||||
celt_tests_test_unit_rotation_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
|
||||
if OPUS_ARM_EXTERNAL_ASM
|
||||
celt_tests_test_unit_rotation_LDADD += libarmasm.la
|
||||
endif
|
||||
|
||||
celt_tests_test_unit_types_SOURCES = celt/tests/test_unit_types.c
|
||||
celt_tests_test_unit_types_LDADD = $(LIBM)
|
||||
endif
|
||||
|
||||
if CUSTOM_MODES
|
||||
pkginclude_HEADERS += include/opus_custom.h
|
||||
if EXTRA_PROGRAMS
|
||||
noinst_PROGRAMS += opus_custom_demo
|
||||
opus_custom_demo_SOURCES = celt/opus_custom_demo.c
|
||||
opus_custom_demo_LDADD = libopus.la $(LIBM)
|
||||
|
||||
TESTS += tests/test_opus_custom
|
||||
noinst_PROGRAMS += tests/test_opus_custom
|
||||
endif
|
||||
endif
|
||||
|
||||
if EXTRA_PROGRAMS
|
||||
if ENABLE_DEEP_PLC
|
||||
noinst_PROGRAMS += fargan_demo dump_data dump_weights_blob
|
||||
fargan_demo_SOURCES = dnn/fargan_demo.c
|
||||
fargan_demo_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
|
||||
|
||||
dump_data_SOURCES = dnn/dump_data.c
|
||||
dump_data_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
|
||||
|
||||
dump_weights_blob_SOURCES = dnn/write_lpcnet_weights.c
|
||||
dump_weights_blob_LDADD = $(LIBM)
|
||||
dump_weights_blob_CFLAGS = $(AM_CFLAGS) -DDUMP_BINARY_WEIGHTS
|
||||
endif
|
||||
if ENABLE_DRED
|
||||
TESTS += tests/test_opus_dred
|
||||
endif
|
||||
|
||||
if ENABLE_LOSSGEN
|
||||
noinst_PROGRAMS += lossgen_demo
|
||||
lossgen_demo_SOURCES = dnn/lossgen_demo.c $(LOSSGEN_SOURCES)
|
||||
lossgen_demo_LDADD = $(LIBM)
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
|
||||
EXTRA_DIST = opus.pc.in \
|
||||
opus-uninstalled.pc.in \
|
||||
opus.m4 \
|
||||
Makefile.mips \
|
||||
Makefile.unix \
|
||||
CMakeLists.txt \
|
||||
cmake/CFeatureCheck.cmake \
|
||||
cmake/OpusBuildtype.cmake \
|
||||
cmake/OpusConfig.cmake \
|
||||
cmake/OpusConfig.cmake.in \
|
||||
cmake/OpusFunctions.cmake \
|
||||
cmake/OpusPackageVersion.cmake \
|
||||
cmake/OpusSources.cmake \
|
||||
cmake/README.md \
|
||||
cmake/RunTest.cmake \
|
||||
cmake/config.h.cmake.in \
|
||||
cmake/vla.c \
|
||||
cmake/cpu_info_by_asm.c \
|
||||
cmake/cpu_info_by_c.c \
|
||||
meson/get-version.py \
|
||||
meson/read-sources-list.py \
|
||||
meson/README.md \
|
||||
meson.build \
|
||||
meson_options.txt \
|
||||
include/meson.build \
|
||||
celt/meson.build \
|
||||
celt/tests/meson.build \
|
||||
dnn/meson.build \
|
||||
dnn/README.md \
|
||||
silk/meson.build \
|
||||
silk/tests/meson.build \
|
||||
src/meson.build \
|
||||
tests/meson.build \
|
||||
doc/meson.build \
|
||||
tests/run_vectors.sh \
|
||||
celt/arm/arm2gnu.pl \
|
||||
celt/arm/celt_pitch_xcorr_arm.s
|
||||
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = opus.pc
|
||||
|
||||
m4datadir = $(datadir)/aclocal
|
||||
m4data_DATA = opus.m4
|
||||
|
||||
# Targets to build and install just the library without the docs
|
||||
opus check-opus install-opus: export NO_DOXYGEN = 1
|
||||
|
||||
opus: all
|
||||
check-opus: check
|
||||
install-opus: install
|
||||
|
||||
|
||||
# Or just the docs
|
||||
docs:
|
||||
( cd doc && $(MAKE) $(AM_MAKEFLAGS) )
|
||||
|
||||
install-docs:
|
||||
( cd doc && $(MAKE) $(AM_MAKEFLAGS) install )
|
||||
|
||||
|
||||
# Or everything (by default)
|
||||
all-local:
|
||||
@[ -n "$(NO_DOXYGEN)" ] || ( cd doc && $(MAKE) $(AM_MAKEFLAGS) )
|
||||
|
||||
install-data-local:
|
||||
@[ -n "$(NO_DOXYGEN)" ] || ( cd doc && $(MAKE) $(AM_MAKEFLAGS) install )
|
||||
|
||||
clean-local:
|
||||
-( cd doc && $(MAKE) $(AM_MAKEFLAGS) clean )
|
||||
|
||||
uninstall-local:
|
||||
( cd doc && $(MAKE) $(AM_MAKEFLAGS) uninstall )
|
||||
|
||||
|
||||
# We check this every time make is run, with configure.ac being touched to
|
||||
# trigger an update of the build system files if update_version changes the
|
||||
# current PACKAGE_VERSION (or if package_version was modified manually by a
|
||||
# user with either AUTO_UPDATE=no or no update_version script present - the
|
||||
# latter being the normal case for tarball releases).
|
||||
#
|
||||
# We can't just add the package_version file to CONFIGURE_DEPENDENCIES since
|
||||
# simply running autoconf will not actually regenerate configure for us when
|
||||
# the content of that file changes (due to autoconf dependency checking not
|
||||
# knowing about that without us creating yet another file for it to include).
|
||||
#
|
||||
# The MAKECMDGOALS check is a gnu-make'ism, but will degrade 'gracefully' for
|
||||
# makes that don't support it. The only loss of functionality is not forcing
|
||||
# an update of package_version for `make dist` if AUTO_UPDATE=no, but that is
|
||||
# unlikely to be a real problem for any real user.
|
||||
$(top_srcdir)/configure.ac: force
|
||||
@case "$(MAKECMDGOALS)" in \
|
||||
dist-hook) exit 0 ;; \
|
||||
dist-* | dist | distcheck | distclean) _arg=release ;; \
|
||||
esac; \
|
||||
if ! $(top_srcdir)/update_version $$_arg 2> /dev/null; then \
|
||||
if [ ! -e $(top_srcdir)/package_version ]; then \
|
||||
echo 'PACKAGE_VERSION="unknown"' > $(top_srcdir)/package_version; \
|
||||
fi; \
|
||||
. $(top_srcdir)/package_version || exit 1; \
|
||||
[ "$(PACKAGE_VERSION)" != "$$PACKAGE_VERSION" ] || exit 0; \
|
||||
fi; \
|
||||
touch $@
|
||||
|
||||
force:
|
||||
|
||||
# Create a minimal package_version file when make dist is run.
|
||||
dist-hook:
|
||||
echo 'PACKAGE_VERSION="$(PACKAGE_VERSION)"' > $(top_distdir)/package_version
|
||||
|
||||
|
||||
.PHONY: opus check-opus install-opus docs install-docs
|
||||
|
||||
# automake doesn't do dependency tracking for asm files, that I can tell
|
||||
$(CELT_SOURCES_ARM_ASM:%.s=%-gnu.S): celt/arm/armopts-gnu.S
|
||||
$(CELT_SOURCES_ARM_ASM:%.s=%-gnu.S): $(top_srcdir)/celt/arm/arm2gnu.pl
|
||||
|
||||
# convert ARM asm to GNU as format
|
||||
%-gnu.S: $(top_srcdir)/%.s
|
||||
$(top_srcdir)/celt/arm/arm2gnu.pl @ARM2GNU_PARAMS@ < $< > $@
|
||||
# For autoconf-modified sources (e.g., armopts.s)
|
||||
%-gnu.S: %.s
|
||||
$(top_srcdir)/celt/arm/arm2gnu.pl @ARM2GNU_PARAMS@ < $< > $@
|
||||
|
||||
OPT_UNIT_TEST_OBJ = $(celt_tests_test_unit_mathops_SOURCES:.c=.o) \
|
||||
$(celt_tests_test_unit_rotation_SOURCES:.c=.o) \
|
||||
$(celt_tests_test_unit_mdct_SOURCES:.c=.o) \
|
||||
$(celt_tests_test_unit_dft_SOURCES:.c=.o) \
|
||||
$(silk_tests_test_unit_LPC_inv_pred_gain_SOURCES:.c=.o)
|
||||
|
||||
if HAVE_SSE
|
||||
SSE_OBJ = $(CELT_SOURCES_SSE:.c=.lo)
|
||||
$(SSE_OBJ): CFLAGS += $(OPUS_X86_SSE_CFLAGS)
|
||||
endif
|
||||
|
||||
if HAVE_SSE2
|
||||
SSE2_OBJ = $(CELT_SOURCES_SSE2:.c=.lo) \
|
||||
$(DNN_SOURCES_SSE2:.c=.lo)
|
||||
$(SSE2_OBJ): CFLAGS += $(OPUS_X86_SSE2_CFLAGS)
|
||||
endif
|
||||
|
||||
if HAVE_SSE4_1
|
||||
SSE4_1_OBJ = $(CELT_SOURCES_SSE4_1:.c=.lo) \
|
||||
$(DNN_SOURCES_SSE4_1:.c=.lo) \
|
||||
$(SILK_SOURCES_SSE4_1:.c=.lo) \
|
||||
$(SILK_SOURCES_FIXED_SSE4_1:.c=.lo)
|
||||
$(SSE4_1_OBJ): CFLAGS += $(OPUS_X86_SSE4_1_CFLAGS)
|
||||
endif
|
||||
|
||||
if HAVE_AVX2
|
||||
AVX2_OBJ = $(CELT_SOURCES_AVX2:.c=.lo) \
|
||||
$(SILK_SOURCES_AVX2:.c=.lo) \
|
||||
$(SILK_SOURCES_FLOAT_AVX2:.c=.lo) \
|
||||
$(DNN_SOURCES_AVX2:.c=.lo)
|
||||
$(AVX2_OBJ): CFLAGS += $(OPUS_X86_AVX2_CFLAGS)
|
||||
endif
|
||||
|
||||
if HAVE_ARM_NEON_INTR
|
||||
ARM_NEON_INTR_OBJ = $(CELT_SOURCES_ARM_NEON_INTR:.c=.lo) \
|
||||
$(SILK_SOURCES_ARM_NEON_INTR:.c=.lo) \
|
||||
$(DNN_SOURCES_NEON:.c=.lo) \
|
||||
$(SILK_SOURCES_FIXED_ARM_NEON_INTR:.c=.lo)
|
||||
$(ARM_NEON_INTR_OBJ): CFLAGS += \
|
||||
$(OPUS_ARM_NEON_INTR_CFLAGS) $(NE10_CFLAGS)
|
||||
endif
|
||||
|
||||
if HAVE_ARM_DOTPROD
|
||||
ARM_DOTPROD_OBJ = $(DNN_SOURCES_DOTPROD:.c=.lo)
|
||||
$(ARM_DOTPROD_OBJ): CFLAGS += $(ARM_DOTPROD_INTR_CFLAGS)
|
||||
endif
|
||||
@@ -1,169 +0,0 @@
|
||||
#################### COMPILE OPTIONS #######################
|
||||
|
||||
# Uncomment this for fixed-point build
|
||||
FIXED_POINT=1
|
||||
|
||||
# It is strongly recommended to uncomment one of these
|
||||
# VAR_ARRAYS: Use C99 variable-length arrays for stack allocation
|
||||
# USE_ALLOCA: Use alloca() for stack allocation
|
||||
# If none is defined, then the fallback is a non-threadsafe global array
|
||||
CFLAGS := -DUSE_ALLOCA $(CFLAGS)
|
||||
#CFLAGS := -DVAR_ARRAYS $(CFLAGS)
|
||||
|
||||
# These options affect performance
|
||||
# HAVE_LRINTF: Use C99 intrinsics to speed up float-to-int conversion
|
||||
CFLAGS := -DHAVE_LRINTF $(CFLAGS)
|
||||
|
||||
###################### END OF OPTIONS ######################
|
||||
|
||||
-include package_version
|
||||
|
||||
include silk_sources.mk
|
||||
include celt_sources.mk
|
||||
include opus_sources.mk
|
||||
|
||||
ifdef FIXED_POINT
|
||||
SILK_SOURCES += $(SILK_SOURCES_FIXED)
|
||||
else
|
||||
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
|
||||
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
|
||||
endif
|
||||
|
||||
EXESUFFIX =
|
||||
LIBPREFIX = lib
|
||||
LIBSUFFIX = .a
|
||||
OBJSUFFIX = .o
|
||||
|
||||
CC = $(TOOLCHAIN_PREFIX)cc$(TOOLCHAIN_SUFFIX)
|
||||
AR = $(TOOLCHAIN_PREFIX)ar
|
||||
RANLIB = $(TOOLCHAIN_PREFIX)ranlib
|
||||
CP = $(TOOLCHAIN_PREFIX)cp
|
||||
|
||||
cppflags-from-defines = $(addprefix -D,$(1))
|
||||
cppflags-from-includes = $(addprefix -I,$(1))
|
||||
ldflags-from-ldlibdirs = $(addprefix -L,$(1))
|
||||
ldlibs-from-libs = $(addprefix -l,$(1))
|
||||
|
||||
WARNINGS = -Wall -W -Wstrict-prototypes -Wextra -Wcast-align -Wnested-externs -Wshadow
|
||||
|
||||
CFLAGS += -mips32r2 -mno-mips16 -std=gnu99 -O2 -g $(WARNINGS) -DENABLE_ASSERTIONS -DMIPSr1_ASM -DOPUS_BUILD -mdspr2 -march=74kc -mtune=74kc -mmt -mgp32
|
||||
|
||||
CINCLUDES = include silk celt
|
||||
|
||||
ifdef FIXED_POINT
|
||||
CFLAGS += -DFIXED_POINT=1 -DDISABLE_FLOAT_API
|
||||
CINCLUDES += silk/fixed
|
||||
else
|
||||
CINCLUDES += silk/float
|
||||
endif
|
||||
|
||||
|
||||
LIBS = m
|
||||
|
||||
LDLIBDIRS = ./
|
||||
|
||||
CFLAGS += $(call cppflags-from-defines,$(CDEFINES))
|
||||
CFLAGS += $(call cppflags-from-includes,$(CINCLUDES))
|
||||
LDFLAGS += $(call ldflags-from-ldlibdirs,$(LDLIBDIRS))
|
||||
LDLIBS += $(call ldlibs-from-libs,$(LIBS))
|
||||
|
||||
COMPILE.c.cmdline = $(CC) -c $(CFLAGS) -o $@ $<
|
||||
LINK.o = $(CC) $(LDPREFLAGS) $(LDFLAGS)
|
||||
LINK.o.cmdline = $(LINK.o) $^ $(LDLIBS) -o $@$(EXESUFFIX)
|
||||
|
||||
ARCHIVE.cmdline = $(AR) $(ARFLAGS) $@ $^ && $(RANLIB) $@
|
||||
|
||||
%$(OBJSUFFIX):%.c
|
||||
$(COMPILE.c.cmdline)
|
||||
|
||||
%$(OBJSUFFIX):%.cpp
|
||||
$(COMPILE.cpp.cmdline)
|
||||
|
||||
# Directives
|
||||
|
||||
|
||||
# Variable definitions
|
||||
LIB_NAME = opus
|
||||
TARGET = $(LIBPREFIX)$(LIB_NAME)$(LIBSUFFIX)
|
||||
|
||||
SRCS_C = $(SILK_SOURCES) $(CELT_SOURCES) $(OPUS_SOURCES)
|
||||
|
||||
OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(SRCS_C))
|
||||
|
||||
OPUSDEMO_SRCS_C = src/opus_demo.c
|
||||
OPUSDEMO_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSDEMO_SRCS_C))
|
||||
|
||||
TESTOPUSAPI_SRCS_C = tests/test_opus_api.c
|
||||
TESTOPUSAPI_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSAPI_SRCS_C))
|
||||
|
||||
TESTOPUSDECODE_SRCS_C = tests/test_opus_decode.c
|
||||
TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
|
||||
|
||||
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
|
||||
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
|
||||
|
||||
TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
|
||||
TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
|
||||
|
||||
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
|
||||
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
|
||||
|
||||
OPUSCOMPARE_SRCS_C = src/opus_compare.c
|
||||
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
|
||||
|
||||
TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
|
||||
|
||||
# Rules
|
||||
all: lib opus_demo opus_compare $(TESTS)
|
||||
|
||||
lib: $(TARGET)
|
||||
|
||||
check: all
|
||||
for test in $(TESTS); do ./$$test; done
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(ARCHIVE.cmdline)
|
||||
|
||||
opus_demo$(EXESUFFIX): $(OPUSDEMO_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_api$(EXESUFFIX): $(TESTOPUSAPI_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
opus_compare$(EXESUFFIX): $(OPUSCOMPARE_OBJS)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
celt/celt.o: CFLAGS += -DPACKAGE_VERSION='$(PACKAGE_VERSION)'
|
||||
celt/celt.o: package_version
|
||||
|
||||
package_version: force
|
||||
@if [ -x ./update_version ]; then \
|
||||
./update_version || true; \
|
||||
elif [ ! -e ./package_version ]; then \
|
||||
echo 'PACKAGE_VERSION="unknown"' > ./package_version; \
|
||||
fi
|
||||
|
||||
force:
|
||||
|
||||
clean:
|
||||
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
|
||||
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
|
||||
test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
|
||||
test_opus_padding$(EXESUFFIX)
|
||||
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
|
||||
$(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
|
||||
$(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
|
||||
|
||||
.PHONY: all lib clean force check
|
||||
@@ -1,167 +0,0 @@
|
||||
#################### COMPILE OPTIONS #######################
|
||||
|
||||
# Uncomment this for fixed-point build
|
||||
#FIXED_POINT=1
|
||||
|
||||
# It is strongly recommended to uncomment one of these
|
||||
# VAR_ARRAYS: Use C99 variable-length arrays for stack allocation
|
||||
# USE_ALLOCA: Use alloca() for stack allocation
|
||||
# If none is defined, then the fallback is a non-threadsafe global array
|
||||
CFLAGS := -DUSE_ALLOCA $(CFLAGS)
|
||||
#CFLAGS := -DVAR_ARRAYS $(CFLAGS)
|
||||
|
||||
# These options affect performance
|
||||
# HAVE_LRINTF: Use C99 intrinsics to speed up float-to-int conversion
|
||||
#CFLAGS := -DHAVE_LRINTF $(CFLAGS)
|
||||
|
||||
###################### END OF OPTIONS ######################
|
||||
|
||||
-include package_version
|
||||
|
||||
include silk_sources.mk
|
||||
include celt_sources.mk
|
||||
include opus_sources.mk
|
||||
|
||||
ifdef FIXED_POINT
|
||||
SILK_SOURCES += $(SILK_SOURCES_FIXED)
|
||||
else
|
||||
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
|
||||
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
|
||||
endif
|
||||
|
||||
EXESUFFIX =
|
||||
LIBPREFIX = lib
|
||||
LIBSUFFIX = .a
|
||||
OBJSUFFIX = .o
|
||||
|
||||
CC = $(TOOLCHAIN_PREFIX)cc$(TOOLCHAIN_SUFFIX)
|
||||
AR = $(TOOLCHAIN_PREFIX)ar
|
||||
RANLIB = $(TOOLCHAIN_PREFIX)ranlib
|
||||
CP = $(TOOLCHAIN_PREFIX)cp
|
||||
|
||||
cppflags-from-defines = $(addprefix -D,$(1))
|
||||
cppflags-from-includes = $(addprefix -I,$(1))
|
||||
ldflags-from-ldlibdirs = $(addprefix -L,$(1))
|
||||
ldlibs-from-libs = $(addprefix -l,$(1))
|
||||
|
||||
WARNINGS = -Wall -W -Wstrict-prototypes -Wextra -Wcast-align -Wnested-externs -Wshadow
|
||||
CFLAGS += -O2 -g $(WARNINGS) -DOPUS_BUILD
|
||||
CINCLUDES = include silk celt
|
||||
|
||||
ifdef FIXED_POINT
|
||||
CFLAGS += -DFIXED_POINT=1 -DDISABLE_FLOAT_API
|
||||
CINCLUDES += silk/fixed
|
||||
else
|
||||
CINCLUDES += silk/float
|
||||
endif
|
||||
|
||||
|
||||
LIBS = m
|
||||
|
||||
LDLIBDIRS = ./
|
||||
|
||||
CFLAGS += $(call cppflags-from-defines,$(CDEFINES))
|
||||
CFLAGS += $(call cppflags-from-includes,$(CINCLUDES))
|
||||
LDFLAGS += $(call ldflags-from-ldlibdirs,$(LDLIBDIRS))
|
||||
LDLIBS += $(call ldlibs-from-libs,$(LIBS))
|
||||
|
||||
COMPILE.c.cmdline = $(CC) -c $(CFLAGS) -o $@ $<
|
||||
LINK.o = $(CC) $(LDPREFLAGS) $(LDFLAGS)
|
||||
LINK.o.cmdline = $(LINK.o) $^ $(LDLIBS) -o $@$(EXESUFFIX)
|
||||
|
||||
ARCHIVE.cmdline = $(AR) $(ARFLAGS) $@ $^ && $(RANLIB) $@
|
||||
|
||||
%$(OBJSUFFIX):%.c
|
||||
$(COMPILE.c.cmdline)
|
||||
|
||||
%$(OBJSUFFIX):%.cpp
|
||||
$(COMPILE.cpp.cmdline)
|
||||
|
||||
# Directives
|
||||
|
||||
|
||||
# Variable definitions
|
||||
LIB_NAME = opus
|
||||
TARGET = $(LIBPREFIX)$(LIB_NAME)$(LIBSUFFIX)
|
||||
|
||||
SRCS_C = $(SILK_SOURCES) $(CELT_SOURCES) $(OPUS_SOURCES)
|
||||
|
||||
OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(SRCS_C))
|
||||
|
||||
OPUSDEMO_SRCS_C = src/opus_demo.c
|
||||
OPUSDEMO_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSDEMO_SRCS_C))
|
||||
|
||||
TESTOPUSAPI_SRCS_C = tests/test_opus_api.c
|
||||
TESTOPUSAPI_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSAPI_SRCS_C))
|
||||
|
||||
TESTOPUSDECODE_SRCS_C = tests/test_opus_decode.c
|
||||
TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
|
||||
|
||||
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
|
||||
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
|
||||
|
||||
TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
|
||||
TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
|
||||
|
||||
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
|
||||
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
|
||||
|
||||
OPUSCOMPARE_SRCS_C = src/opus_compare.c
|
||||
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
|
||||
|
||||
TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
|
||||
|
||||
# Rules
|
||||
all: lib opus_demo opus_compare $(TESTS)
|
||||
|
||||
lib: $(TARGET)
|
||||
|
||||
check: all
|
||||
for test in $(TESTS); do ./$$test; done
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(ARCHIVE.cmdline)
|
||||
|
||||
opus_demo$(EXESUFFIX): $(OPUSDEMO_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_api$(EXESUFFIX): $(TESTOPUSAPI_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
opus_compare$(EXESUFFIX): $(OPUSCOMPARE_OBJS)
|
||||
$(LINK.o.cmdline)
|
||||
|
||||
celt/celt.o: CFLAGS += -DPACKAGE_VERSION='$(PACKAGE_VERSION)'
|
||||
celt/celt.o: package_version
|
||||
|
||||
package_version: force
|
||||
@if [ -x ./update_version ]; then \
|
||||
./update_version || true; \
|
||||
elif [ ! -e ./package_version ]; then \
|
||||
echo 'PACKAGE_VERSION="unknown"' > ./package_version; \
|
||||
fi
|
||||
|
||||
force:
|
||||
|
||||
clean:
|
||||
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
|
||||
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
|
||||
test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
|
||||
test_opus_padding$(EXESUFFIX)
|
||||
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
|
||||
$(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
|
||||
$(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
|
||||
|
||||
.PHONY: all lib clean force check
|
||||
@@ -1,54 +0,0 @@
|
||||
To build this source code, simply type:
|
||||
|
||||
% make
|
||||
|
||||
If this does not work, or if you want to change the default configuration
|
||||
(e.g., to compile for a fixed-point architecture), simply edit the options
|
||||
in the Makefile.
|
||||
|
||||
An up-to-date implementation conforming to this standard is available in a
|
||||
Git repository at https://gitlab.xiph.org/xiph/opus.git or on a website at:
|
||||
https://opus-codec.org/
|
||||
However, although that implementation is expected to remain conformant
|
||||
with the standard, it is the code in this RFC that shall remain normative.
|
||||
To build from the git repository instead of using this RFC, follow these
|
||||
steps:
|
||||
|
||||
1) Clone the repository (latest implementation of this standard at the time
|
||||
of publication)
|
||||
|
||||
% git clone https://gitlab.xiph.org/xiph/opus.git
|
||||
% cd opus
|
||||
|
||||
2) Compile
|
||||
|
||||
% ./autogen.sh
|
||||
% ./configure
|
||||
% make
|
||||
|
||||
Once you have compiled the codec, there will be a opus_demo executable in
|
||||
the top directory.
|
||||
|
||||
Usage: opus_demo [-e] <application> <sampling rate (Hz)> <channels (1/2)>
|
||||
<bits per second> [options] <input> <output>
|
||||
opus_demo -d <sampling rate (Hz)> <channels (1/2)> [options]
|
||||
<input> <output>
|
||||
|
||||
mode: voip | audio | restricted-lowdelay
|
||||
options:
|
||||
-e : only runs the encoder (output the bit-stream)
|
||||
-d : only runs the decoder (reads the bit-stream as input)
|
||||
-cbr : enable constant bitrate; default: variable bitrate
|
||||
-cvbr : enable constrained variable bitrate; default: unconstrained
|
||||
-bandwidth <NB|MB|WB|SWB|FB> : audio bandwidth (from narrowband to fullband);
|
||||
default: sampling rate
|
||||
-framesize <2.5|5|10|20|40|60> : frame size in ms; default: 20
|
||||
-max_payload <bytes> : maximum payload size in bytes, default: 1024
|
||||
-complexity <comp> : complexity, 0 (lowest) ... 10 (highest); default: 10
|
||||
-inbandfec : enable SILK inband FEC
|
||||
-forcemono : force mono encoding, even for stereo input
|
||||
-dtx : enable SILK DTX
|
||||
-loss <perc> : simulate packet loss, in percent (0-100); default: 0
|
||||
|
||||
input and output are little endian signed 16-bit PCM files or opus bitstreams
|
||||
with simple opus_demo proprietary framing.
|
||||
@@ -1,222 +0,0 @@
|
||||
## ESP32 OPUS
|
||||
|
||||
Only modified the CMakeLists.txt for compilation under the ESP-IDF 5.x SDK.
|
||||
|
||||
只修改了 CMakeLists.txt,用于在 ESP-IDF 5.x SDK 下编译。
|
||||
|
||||
Clone the repos into the components directory and it’s ready to use.
|
||||
|
||||
放置到 components 目录下即可使用。
|
||||
|
||||
### Example
|
||||
|
||||
```cpp
|
||||
#include "opus.h"
|
||||
|
||||
...
|
||||
|
||||
auto encoder = opus_encoder_create(16000, 1, OPUS_APPLICATION_VOIP, nullptr);
|
||||
assert(encoder != nullptr);
|
||||
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(5));
|
||||
|
||||
int frame_size = 960;
|
||||
int16_t pcm[frame_size];
|
||||
uint8_t opus[1500];
|
||||
|
||||
int len = opus_encode(encoder, pcm, frame_size, opus, sizeof(opus));
|
||||
printf("len: %d\n", len);
|
||||
|
||||
opus_encoder_destroy(encoder);
|
||||
```
|
||||
|
||||
## Original README
|
||||
|
||||
== Opus audio codec ==
|
||||
|
||||
Opus is a codec for interactive speech and audio transmission over the Internet.
|
||||
|
||||
Opus can handle a wide range of interactive audio applications, including
|
||||
Voice over IP, videoconferencing, in-game chat, and even remote live music
|
||||
performances. It can scale from low bit-rate narrowband speech to very high
|
||||
quality stereo music.
|
||||
|
||||
Opus, when coupled with an appropriate container format, is also suitable
|
||||
for non-realtime stored-file applications such as music distribution, game
|
||||
soundtracks, portable music players, jukeboxes, and other applications that
|
||||
have historically used high latency formats such as MP3, AAC, or Vorbis.
|
||||
|
||||
Opus is specified by IETF RFC 6716:
|
||||
https://tools.ietf.org/html/rfc6716
|
||||
|
||||
The Opus format and this implementation of it are subject to the royalty-
|
||||
free patent and copyright licenses specified in the file COPYING.
|
||||
|
||||
This package implements a shared library for encoding and decoding raw Opus
|
||||
bitstreams. Raw Opus bitstreams should be used over RTP according to
|
||||
https://tools.ietf.org/html/rfc7587
|
||||
|
||||
The package also includes a number of test tools used for testing the
|
||||
correct operation of the library. The bitstreams read/written by these
|
||||
tools should not be used for Opus file distribution: They include
|
||||
additional debugging data and cannot support seeking.
|
||||
|
||||
Opus stored in files should use the Ogg encapsulation for Opus which is
|
||||
described at:
|
||||
https://tools.ietf.org/html/rfc7845
|
||||
|
||||
An opus-tools package is available which provides encoding and decoding of
|
||||
Ogg encapsulated Opus files and includes a number of useful features.
|
||||
|
||||
Opus-tools can be found at:
|
||||
https://gitlab.xiph.org/xiph/opus-tools.git
|
||||
or on the main Opus website:
|
||||
https://opus-codec.org/
|
||||
|
||||
== Deep Learning and Opus ==
|
||||
|
||||
Lossy networks continue to be a challenge for real-time communications.
|
||||
While the original implementation of Opus provides an excellent packet loss
|
||||
concealment mechanism, the team has continued to advance the methodology used
|
||||
to improve audio quality in challenge network environments.
|
||||
|
||||
In Opus 1.5, we added a deep learning based redundancy encoder that enhances
|
||||
audio in lossy networks by embedding one second of recovery data in the padding
|
||||
data of each packet. The underlying algorithm behind encoding and decoding the
|
||||
recovery data is called the deep redundancy (DRED) algorithm. By leveraging
|
||||
the padding data within the packet, Opus 1.5 is fully backward compatible with
|
||||
prior revisions of Opus. Please see the README under the "dnn" subdirectory to
|
||||
understand DRED.
|
||||
|
||||
DRED was developed by a team that Amazon Web Services initially sponsored,
|
||||
who open-sourced the implementation as well as began the
|
||||
standardization process at the IETF:
|
||||
https://datatracker.ietf.org/doc/draft-ietf-mlcodec-opus-extension/
|
||||
The license behind Opus or the intellectual property position of Opus does
|
||||
not change with Opus 1.5.
|
||||
|
||||
== Compiling libopus ==
|
||||
|
||||
To build from a distribution tarball, you only need to do the following:
|
||||
|
||||
% ./configure
|
||||
% make
|
||||
|
||||
To build from the git repository, the following steps are necessary:
|
||||
|
||||
0) Set up a development environment:
|
||||
|
||||
On an Ubuntu or Debian family Linux distribution:
|
||||
|
||||
% sudo apt-get install git autoconf automake libtool gcc make
|
||||
|
||||
On a Fedora/Redhat based Linux:
|
||||
|
||||
% sudo dnf install git autoconf automake libtool gcc make
|
||||
|
||||
Or for older Redhat/Centos Linux releases:
|
||||
|
||||
% sudo yum install git autoconf automake libtool gcc make
|
||||
|
||||
On Apple macOS, install Xcode and brew.sh, then in the Terminal enter:
|
||||
|
||||
% brew install autoconf automake libtool
|
||||
|
||||
1) Clone the repository:
|
||||
|
||||
% git clone https://gitlab.xiph.org/xiph/opus.git
|
||||
% cd opus
|
||||
|
||||
2) Compiling the source
|
||||
|
||||
% ./autogen.sh
|
||||
% ./configure
|
||||
% make
|
||||
|
||||
On x86, it's a good idea to use a -march= option that allows the use of AVX2.
|
||||
|
||||
3) Install the codec libraries (optional)
|
||||
|
||||
% sudo make install
|
||||
|
||||
Once you have compiled the codec, there will be a opus_demo executable
|
||||
in the top directory.
|
||||
|
||||
Usage: opus_demo [-e] <application> <sampling rate (Hz)> <channels (1/2)>
|
||||
<bits per second> [options] <input> <output>
|
||||
opus_demo -d <sampling rate (Hz)> <channels (1/2)> [options]
|
||||
<input> <output>
|
||||
|
||||
mode: voip | audio | restricted-lowdelay
|
||||
options:
|
||||
-e : only runs the encoder (output the bit-stream)
|
||||
-d : only runs the decoder (reads the bit-stream as input)
|
||||
-cbr : enable constant bitrate; default: variable bitrate
|
||||
-cvbr : enable constrained variable bitrate; default:
|
||||
unconstrained
|
||||
-bandwidth <NB|MB|WB|SWB|FB>
|
||||
: audio bandwidth (from narrowband to fullband);
|
||||
default: sampling rate
|
||||
-framesize <2.5|5|10|20|40|60>
|
||||
: frame size in ms; default: 20
|
||||
-max_payload <bytes>
|
||||
: maximum payload size in bytes, default: 1024
|
||||
-complexity <comp>
|
||||
: complexity, 0 (lowest) ... 10 (highest); default: 10
|
||||
-inbandfec : enable SILK inband FEC
|
||||
-forcemono : force mono encoding, even for stereo input
|
||||
-dtx : enable SILK DTX
|
||||
-loss <perc> : simulate packet loss, in percent (0-100); default: 0
|
||||
|
||||
input and output are little-endian signed 16-bit PCM files or opus
|
||||
bitstreams with simple opus_demo proprietary framing.
|
||||
|
||||
== Testing ==
|
||||
|
||||
This package includes a collection of automated unit and system tests
|
||||
which SHOULD be run after compiling the package especially the first
|
||||
time it is run on a new platform.
|
||||
|
||||
To run the integrated tests:
|
||||
|
||||
% make check
|
||||
|
||||
There is also collection of standard test vectors which are not
|
||||
included in this package for size reasons but can be obtained from:
|
||||
https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz
|
||||
|
||||
To run compare the code to these test vectors:
|
||||
|
||||
% curl -OL https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz
|
||||
% tar -zxf opus_testvectors-rfc8251.tar.gz
|
||||
% ./tests/run_vectors.sh ./ opus_newvectors 48000
|
||||
|
||||
== Compiling libopus for Windows and alternative build systems ==
|
||||
|
||||
See cmake/README.md or meson/README.md.
|
||||
|
||||
== Portability notes ==
|
||||
|
||||
This implementation uses floating-point by default but can be compiled to
|
||||
use only fixed-point arithmetic by setting --enable-fixed-point (if using
|
||||
autoconf) or by defining the FIXED_POINT macro (if building manually).
|
||||
The fixed point implementation has somewhat lower audio quality and is
|
||||
slower on platforms with fast FPUs, it is normally only used in embedded
|
||||
environments.
|
||||
|
||||
The implementation can be compiled with either a C89 or a C99 compiler.
|
||||
While it does not rely on any _undefined behavior_ as defined by C89 or
|
||||
C99, it relies on common _implementation-defined behavior_ for two's
|
||||
complement architectures:
|
||||
|
||||
o Right shifts of negative values are consistent with two's
|
||||
complement arithmetic, so that a>>b is equivalent to
|
||||
floor(a/(2^b)),
|
||||
|
||||
o For conversion to a signed integer of N bits, the value is reduced
|
||||
modulo 2^N to be within range of the type,
|
||||
|
||||
o The result of integer division of a negative value is truncated
|
||||
towards zero, and
|
||||
|
||||
o The compiler provides a 64-bit integer type (a C99 requirement
|
||||
which is supported by most C89 compilers).
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
REM Run this to set up the build system: configure, makefiles, etc.
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Parse the real autogen.sh script for version
|
||||
for /F "tokens=2 delims= " %%A in ('findstr "dnn/download_model.sh" autogen.sh') do (
|
||||
set "model=%%A"
|
||||
)
|
||||
|
||||
call dnn\download_model.bat %model%
|
||||
|
||||
echo Updating build configuration files, please wait....
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2010-2015 Xiph.Org Foundation and contributors.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the COPYING file.
|
||||
|
||||
# Run this to set up the build system: configure, makefiles, etc.
|
||||
set -e
|
||||
|
||||
srcdir=`dirname $0`
|
||||
test -n "$srcdir" && cd "$srcdir"
|
||||
|
||||
dnn/download_model.sh "a86f0a9db852691d4335608733ec8384a407e585801ab9e4b490e0be297ac382"
|
||||
|
||||
echo "Updating build configuration files, please wait...."
|
||||
|
||||
autoreconf -isf
|
||||
@@ -1,188 +0,0 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.*/
|
||||
|
||||
#ifndef KISS_FFT_GUTS_H
|
||||
#define KISS_FFT_GUTS_H
|
||||
|
||||
#define MIN(a,b) ((a)<(b) ? (a):(b))
|
||||
#define MAX(a,b) ((a)>(b) ? (a):(b))
|
||||
|
||||
/* kiss_fft.h
|
||||
defines kiss_fft_scalar as either short or a float type
|
||||
and defines
|
||||
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
|
||||
#include "kiss_fft.h"
|
||||
|
||||
/*
|
||||
Explanation of macros dealing with complex math:
|
||||
|
||||
C_MUL(m,a,b) : m = a*b
|
||||
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise
|
||||
C_SUB( res, a,b) : res = a - b
|
||||
C_SUBFROM( res , a) : res -= a
|
||||
C_ADDTO( res , a) : res += a
|
||||
* */
|
||||
#ifdef FIXED_POINT
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
#define SAMP_MAX 2147483647
|
||||
#define TWID_MAX 32767
|
||||
#define TRIG_UPSCALE 1
|
||||
|
||||
#define SAMP_MIN -SAMP_MAX
|
||||
|
||||
#ifdef ENABLE_QEXT
|
||||
# define S_MUL(a,b) MULT32_32_Q31(b, a)
|
||||
# define S_MUL2(a,b) MULT32_32_Q31(b, a)
|
||||
#else
|
||||
# define S_MUL(a,b) MULT16_32_Q15(b, a)
|
||||
# define S_MUL2(a,b) MULT16_32_Q16(b, a)
|
||||
#endif
|
||||
|
||||
# define C_MUL(m,a,b) \
|
||||
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0)
|
||||
|
||||
# define C_MULC(m,a,b) \
|
||||
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0)
|
||||
|
||||
# define C_MULBYSCALAR( c, s ) \
|
||||
do{ (c).r = S_MUL( (c).r , s ) ;\
|
||||
(c).i = S_MUL( (c).i , s ) ; }while(0)
|
||||
|
||||
# define DIVSCALAR(x,k) \
|
||||
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 )
|
||||
|
||||
# define C_FIXDIV(c,div) \
|
||||
do { DIVSCALAR( (c).r , div); \
|
||||
DIVSCALAR( (c).i , div); }while (0)
|
||||
|
||||
#define C_ADD( res, a,b)\
|
||||
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
|
||||
}while(0)
|
||||
#define C_SUB( res, a,b)\
|
||||
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
|
||||
}while(0)
|
||||
#define C_ADDTO( res , a)\
|
||||
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
|
||||
}while(0)
|
||||
|
||||
#define C_SUBFROM( res , a)\
|
||||
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
|
||||
}while(0)
|
||||
|
||||
#if defined(OPUS_ARM_INLINE_ASM)
|
||||
#include "arm/kiss_fft_armv4.h"
|
||||
#endif
|
||||
|
||||
#if defined(OPUS_ARM_INLINE_EDSP)
|
||||
#include "arm/kiss_fft_armv5e.h"
|
||||
#endif
|
||||
#if defined(MIPSr1_ASM)
|
||||
#include "mips/kiss_fft_mipsr1.h"
|
||||
#endif
|
||||
|
||||
#else /* not FIXED_POINT*/
|
||||
|
||||
# define S_MUL(a,b) ( (a)*(b) )
|
||||
# define S_MUL2(a,b) ( (a)*(b) )
|
||||
#define C_MUL(m,a,b) \
|
||||
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
|
||||
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
|
||||
#define C_MULC(m,a,b) \
|
||||
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
|
||||
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0)
|
||||
|
||||
#define C_MUL4(m,a,b) C_MUL(m,a,b)
|
||||
|
||||
# define C_FIXDIV(c,div) /* NOOP */
|
||||
# define C_MULBYSCALAR( c, s ) \
|
||||
do{ (c).r *= (s);\
|
||||
(c).i *= (s); }while(0)
|
||||
#endif
|
||||
|
||||
#ifndef CHECK_OVERFLOW_OP
|
||||
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */
|
||||
#endif
|
||||
|
||||
#ifndef C_ADD
|
||||
#define C_ADD( res, a,b)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
|
||||
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
|
||||
}while(0)
|
||||
#define C_SUB( res, a,b)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
|
||||
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
|
||||
}while(0)
|
||||
#define C_ADDTO( res , a)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
|
||||
(res).r += (a).r; (res).i += (a).i;\
|
||||
}while(0)
|
||||
|
||||
#define C_SUBFROM( res , a)\
|
||||
do {\
|
||||
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
|
||||
(res).r -= (a).r; (res).i -= (a).i; \
|
||||
}while(0)
|
||||
#endif /* C_ADD defined */
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
|
||||
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/
|
||||
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase))
|
||||
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase))
|
||||
# define HALF_OF(x) ((x)>>1)
|
||||
#elif defined(USE_SIMD)
|
||||
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
|
||||
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
|
||||
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f))
|
||||
#else
|
||||
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
|
||||
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
|
||||
# define HALF_OF(x) ((x)*.5f)
|
||||
#endif
|
||||
|
||||
#define kf_cexp(x,phase) \
|
||||
do{ \
|
||||
(x)->r = KISS_FFT_COS(phase);\
|
||||
(x)->i = KISS_FFT_SIN(phase);\
|
||||
}while(0)
|
||||
|
||||
#define kf_cexp2(x,phase) \
|
||||
do{ \
|
||||
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
|
||||
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
|
||||
}while(0)
|
||||
|
||||
#endif /* KISS_FFT_GUTS_H */
|
||||
@@ -1,396 +0,0 @@
|
||||
/* Copyright (c) 2003-2008 Jean-Marc Valin
|
||||
Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/**
|
||||
@file arch.h
|
||||
@brief Various architecture definitions for CELT
|
||||
*/
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ARCH_H
|
||||
#define ARCH_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "opus_defines.h"
|
||||
|
||||
# if !defined(__GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define __GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define __GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#if OPUS_GNUC_PREREQ(3, 0)
|
||||
#define opus_likely(x) (__builtin_expect(!!(x), 1))
|
||||
#define opus_unlikely(x) (__builtin_expect(!!(x), 0))
|
||||
#else
|
||||
#define opus_likely(x) (!!(x))
|
||||
#define opus_unlikely(x) (!!(x))
|
||||
#endif
|
||||
|
||||
#define CELT_SIG_SCALE 32768.f
|
||||
|
||||
#define CELT_FATAL(str) celt_fatal(str, __FILE__, __LINE__)
|
||||
|
||||
#if defined(ENABLE_ASSERTIONS) || defined(ENABLE_HARDENING)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((noreturn))
|
||||
#endif
|
||||
void celt_fatal(const char *str, const char *file, int line);
|
||||
|
||||
#if defined(CELT_C) && !defined(OVERRIDE_celt_fatal)
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __GNUC__
|
||||
__attribute__((noreturn))
|
||||
#endif
|
||||
void celt_fatal(const char *str, const char *file, int line)
|
||||
{
|
||||
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str);
|
||||
#if defined(_MSC_VER)
|
||||
_set_abort_behavior( 0, _WRITE_ABORT_MSG);
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
#define celt_assert(cond) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond);}}
|
||||
#define celt_assert2(cond, message) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond "\n" message);}}
|
||||
#define MUST_SUCCEED(call) celt_assert((call) == OPUS_OK)
|
||||
#else
|
||||
#define celt_assert(cond)
|
||||
#define celt_assert2(cond, message)
|
||||
#define MUST_SUCCEED(call) do {if((call) != OPUS_OK) {RESTORE_STACK; return OPUS_INTERNAL_ERROR;} } while (0)
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_ASSERTIONS)
|
||||
#define celt_sig_assert(cond) {if (!(cond)) {CELT_FATAL("signal assertion failed: " #cond);}}
|
||||
#else
|
||||
#define celt_sig_assert(cond)
|
||||
#endif
|
||||
|
||||
#define IMUL32(a,b) ((a)*(b))
|
||||
|
||||
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */
|
||||
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
|
||||
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */
|
||||
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
|
||||
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */
|
||||
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */
|
||||
#define FMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum float value. */
|
||||
#define FMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum float value. */
|
||||
#define UADD32(a,b) ((a)+(b))
|
||||
#define USUB32(a,b) ((a)-(b))
|
||||
#define MAXG(a,b) MAX32(a, b)
|
||||
#define MING(a,b) MIN32(a, b)
|
||||
|
||||
/* Throughout the code, we use the following scaling for signals:
|
||||
FLOAT: used for float API, normalized to +/-1.
|
||||
INT16: used for 16-bit API, normalized to +/- 32768
|
||||
RES: internal Opus resolution, defined as +/-1. in float builds, or either 16-bit or 24-bit int for fixed-point builds
|
||||
SIG: internal CELT resolution: defined as +/- 32768. in float builds, or Q27 in fixed-point builds (int16 shifted by 12)
|
||||
*/
|
||||
|
||||
|
||||
/* Set this if opus_int64 is a native type of the CPU. */
|
||||
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
|
||||
(which can be ILP32 for x32) and Win64 (which is LLP64). */
|
||||
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64)
|
||||
#define OPUS_FAST_INT64 1
|
||||
#else
|
||||
#define OPUS_FAST_INT64 0
|
||||
#endif
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
#define ARG_FIXED(arg) , arg
|
||||
#else
|
||||
#define ARG_FIXED(arg)
|
||||
#endif
|
||||
|
||||
#define PRINT_MIPS(file)
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
|
||||
typedef opus_int16 opus_val16;
|
||||
typedef opus_int32 opus_val32;
|
||||
typedef opus_int64 opus_val64;
|
||||
|
||||
typedef opus_val32 celt_sig;
|
||||
typedef opus_val16 celt_norm;
|
||||
typedef opus_val32 celt_ener;
|
||||
typedef opus_val32 celt_glog;
|
||||
|
||||
#ifdef ENABLE_RES24
|
||||
typedef opus_val32 opus_res;
|
||||
#define RES_SHIFT 8
|
||||
#define SIG2RES(a) PSHR32(a, SIG_SHIFT-RES_SHIFT)
|
||||
#define RES2INT16(a) SAT16(PSHR32(a, RES_SHIFT))
|
||||
#define RES2INT24(a) (a)
|
||||
#define RES2FLOAT(a) ((1.f/32768.f/256.)*(a))
|
||||
#define INT16TORES(a) SHL32(EXTEND32(a), RES_SHIFT)
|
||||
#define INT24TORES(a) (a)
|
||||
#define ADD_RES(a, b) ADD32(a, b)
|
||||
#define FLOAT2RES(a) float2int(32768.f*256.f*(a))
|
||||
#define RES2SIG(a) SHL32((a), SIG_SHIFT-RES_SHIFT)
|
||||
#define MULT16_RES_Q15(a,b) MULT16_32_Q15(a,b)
|
||||
#define MAX_ENCODING_DEPTH 24
|
||||
#else
|
||||
typedef opus_val16 opus_res;
|
||||
#define RES_SHIFT 0
|
||||
#define SIG2RES(a) SIG2WORD16(a)
|
||||
#define RES2INT16(a) (a)
|
||||
#define RES2INT24(a) SHL32(EXTEND32(a), 8)
|
||||
#define RES2FLOAT(a) ((1.f/32768.f)*(a))
|
||||
#define INT16TORES(a) (a)
|
||||
#define INT24TORES(a) SAT16(PSHR32(a, 8))
|
||||
#define ADD_RES(a, b) SAT16(ADD32((a), (b)));
|
||||
#define FLOAT2RES(a) FLOAT2INT16(a)
|
||||
#define RES2SIG(a) SHL32(EXTEND32(a), SIG_SHIFT)
|
||||
#define MULT16_RES_Q15(a,b) MULT16_16_Q15(a,b)
|
||||
#define MAX_ENCODING_DEPTH 16
|
||||
#endif
|
||||
|
||||
#define RES2VAL16(a) RES2INT16(a)
|
||||
#define FLOAT2SIG(a) float2int(((opus_int32)32768<<SIG_SHIFT)*(a))
|
||||
#define INT16TOSIG(a) SHL32(EXTEND32(a), SIG_SHIFT)
|
||||
#define INT24TOSIG(a) SHL32(a, SIG_SHIFT-8)
|
||||
|
||||
#ifdef ENABLE_QEXT
|
||||
typedef opus_val32 celt_coef;
|
||||
#define COEF_ONE Q31ONE
|
||||
#define MULT_COEF_32(a, b) MULT32_32_Q31(a,b)
|
||||
#define MAC_COEF_32_ARM(c, a, b) ADD32((c), MULT32_32_Q32(a,b))
|
||||
#define MULT_COEF(a, b) MULT32_32_Q31(a,b)
|
||||
#define MULT_COEF_TAPS(a, b) SHL32(MULT16_16(a,b), 1)
|
||||
#define COEF2VAL16(x) EXTRACT16(SHR32(x, 16))
|
||||
#else
|
||||
typedef opus_val16 celt_coef;
|
||||
#define COEF_ONE Q15ONE
|
||||
#define MULT_COEF_32(a, b) MULT16_32_Q15(a,b)
|
||||
#define MAC_COEF_32_ARM(a, b, c) MAC16_32_Q16(a,b,c)
|
||||
#define MULT_COEF(a, b) MULT16_16_Q15(a,b)
|
||||
#define MULT_COEF_TAPS(a, b) MULT16_16_P15(a,b)
|
||||
#define COEF2VAL16(x) (x)
|
||||
#endif
|
||||
|
||||
#define celt_isnan(x) 0
|
||||
|
||||
#define Q15ONE 32767
|
||||
#define Q31ONE 2147483647
|
||||
|
||||
#define SIG_SHIFT 12
|
||||
/* Safe saturation value for 32-bit signals. We need to make sure that we can
|
||||
add two sig values and that the first stages of the MDCT don't cause an overflow.
|
||||
The most constraining is the ARM_ASM comb filter where we shift left by one
|
||||
and then add two values. Because of that, we use 2^29-1. SIG_SAT must be large
|
||||
enough to fit a full-scale high-freq tone through the prefilter and comb filter,
|
||||
meaning 1.85*1.75*2^(15+SIG_SHIFT) = 434529895.
|
||||
so the limit should be about 2^31*sqrt(.5). */
|
||||
#define SIG_SAT (536870911)
|
||||
|
||||
#define NORM_SCALING 16384
|
||||
|
||||
#define DB_SHIFT 24
|
||||
|
||||
#define EPSILON 1
|
||||
#define VERY_SMALL 0
|
||||
#define VERY_LARGE16 ((opus_val16)32767)
|
||||
#define Q15_ONE ((opus_val16)32767)
|
||||
|
||||
|
||||
#define ABS16(x) ((x) < 0 ? (-(x)) : (x))
|
||||
#define ABS32(x) ((x) < 0 ? (-(x)) : (x))
|
||||
|
||||
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) {
|
||||
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x;
|
||||
}
|
||||
|
||||
#ifdef FIXED_DEBUG
|
||||
#include "fixed_debug.h"
|
||||
#else
|
||||
|
||||
#include "fixed_generic.h"
|
||||
|
||||
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR
|
||||
#include "arm/fixed_arm64.h"
|
||||
#elif defined (OPUS_ARM_INLINE_EDSP)
|
||||
#include "arm/fixed_armv5e.h"
|
||||
#elif defined (OPUS_ARM_INLINE_ASM)
|
||||
#include "arm/fixed_armv4.h"
|
||||
#elif defined (BFIN_ASM)
|
||||
#include "fixed_bfin.h"
|
||||
#elif defined (TI_C5X_ASM)
|
||||
#include "fixed_c5x.h"
|
||||
#elif defined (TI_C6X_ASM)
|
||||
#include "fixed_c6x.h"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#else /* FIXED_POINT */
|
||||
|
||||
typedef float opus_val16;
|
||||
typedef float opus_val32;
|
||||
typedef float opus_val64;
|
||||
|
||||
typedef float celt_sig;
|
||||
typedef float celt_norm;
|
||||
typedef float celt_ener;
|
||||
typedef float celt_glog;
|
||||
|
||||
typedef float opus_res;
|
||||
typedef float celt_coef;
|
||||
|
||||
#ifdef FLOAT_APPROX
|
||||
/* This code should reliably detect NaN/inf even when -ffast-math is used.
|
||||
Assumes IEEE 754 format. */
|
||||
static OPUS_INLINE int celt_isnan(float x)
|
||||
{
|
||||
union {float f; opus_uint32 i;} in;
|
||||
in.f = x;
|
||||
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0;
|
||||
}
|
||||
#else
|
||||
#ifdef __FAST_MATH__
|
||||
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input
|
||||
#endif
|
||||
#define celt_isnan(x) ((x)!=(x))
|
||||
#endif
|
||||
|
||||
#define Q15ONE 1.0f
|
||||
#define Q31ONE 1.0f
|
||||
#define COEF_ONE 1.0f
|
||||
#define COEF2VAL16(x) (x)
|
||||
|
||||
#define NORM_SCALING 1.f
|
||||
|
||||
#define EPSILON 1e-15f
|
||||
#define VERY_SMALL 1e-30f
|
||||
#define VERY_LARGE16 1e15f
|
||||
#define Q15_ONE ((opus_val16)1.f)
|
||||
|
||||
/* This appears to be the same speed as C99's fabsf() but it's more portable. */
|
||||
#define ABS16(x) ((float)fabs(x))
|
||||
#define ABS32(x) ((float)fabs(x))
|
||||
|
||||
#define QCONST16(x,bits) (x)
|
||||
#define QCONST32(x,bits) (x)
|
||||
#define GCONST(x) (x)
|
||||
|
||||
#define NEG16(x) (-(x))
|
||||
#define NEG32(x) (-(x))
|
||||
#define NEG32_ovflw(x) (-(x))
|
||||
#define EXTRACT16(x) (x)
|
||||
#define EXTEND32(x) (x)
|
||||
#define SHR16(a,shift) (a)
|
||||
#define SHL16(a,shift) (a)
|
||||
#define SHR32(a,shift) (a)
|
||||
#define SHL32(a,shift) (a)
|
||||
#define PSHR32(a,shift) (a)
|
||||
#define VSHR32(a,shift) (a)
|
||||
|
||||
#define PSHR(a,shift) (a)
|
||||
#define SHR(a,shift) (a)
|
||||
#define SHL(a,shift) (a)
|
||||
#define SATURATE(x,a) (x)
|
||||
#define SATURATE16(x) (x)
|
||||
|
||||
#define ROUND16(a,shift) (a)
|
||||
#define SROUND16(a,shift) (a)
|
||||
#define HALF16(x) (.5f*(x))
|
||||
#define HALF32(x) (.5f*(x))
|
||||
|
||||
#define ADD16(a,b) ((a)+(b))
|
||||
#define SUB16(a,b) ((a)-(b))
|
||||
#define ADD32(a,b) ((a)+(b))
|
||||
#define SUB32(a,b) ((a)-(b))
|
||||
#define ADD32_ovflw(a,b) ((a)+(b))
|
||||
#define SUB32_ovflw(a,b) ((a)-(b))
|
||||
#define SHL32_ovflw(a,shift) (a)
|
||||
#define PSHR32_ovflw(a,shift) (a)
|
||||
|
||||
#define MULT16_16_16(a,b) ((a)*(b))
|
||||
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b))
|
||||
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b))
|
||||
|
||||
#define MULT16_32_Q15(a,b) ((a)*(b))
|
||||
#define MULT16_32_Q16(a,b) ((a)*(b))
|
||||
|
||||
#define MULT32_32_Q16(a,b) ((a)*(b))
|
||||
#define MULT32_32_Q31(a,b) ((a)*(b))
|
||||
|
||||
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
|
||||
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b))
|
||||
#define MAC_COEF_32_ARM(c,a,b) ((c)+(a)*(b))
|
||||
|
||||
#define MULT16_16_Q11_32(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q11(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q13(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q14(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q15(a,b) ((a)*(b))
|
||||
#define MULT16_16_P15(a,b) ((a)*(b))
|
||||
#define MULT16_16_P13(a,b) ((a)*(b))
|
||||
#define MULT16_16_P14(a,b) ((a)*(b))
|
||||
#define MULT16_32_P16(a,b) ((a)*(b))
|
||||
|
||||
#define MULT_COEF_32(a, b) ((a)*(b))
|
||||
#define MULT_COEF(a, b) ((a)*(b))
|
||||
#define MULT_COEF_TAPS(a, b) ((a)*(b))
|
||||
|
||||
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b))
|
||||
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b))
|
||||
|
||||
#define SIG2RES(a) ((1/CELT_SIG_SCALE)*(a))
|
||||
#define RES2INT16(a) FLOAT2INT16(a)
|
||||
#define RES2INT24(a) float2int(32768.f*256.f*(a))
|
||||
#define RES2FLOAT(a) (a)
|
||||
#define INT16TORES(a) ((a)*(1/CELT_SIG_SCALE))
|
||||
#define INT24TORES(a) ((1.f/32768.f/256.)*(a))
|
||||
#define ADD_RES(a, b) ADD32(a, b)
|
||||
#define FLOAT2RES(a) (a)
|
||||
#define RES2SIG(a) (CELT_SIG_SCALE*(a))
|
||||
#define MULT16_RES_Q15(a,b) MULT16_16_Q15(a,b)
|
||||
|
||||
#define RES2VAL16(a) (a)
|
||||
#define FLOAT2SIG(a) ((a)*CELT_SIG_SCALE)
|
||||
#define INT16TOSIG(a) ((float)(a))
|
||||
#define INT24TOSIG(a) ((float)(a)*(1.f/256.f))
|
||||
#define MAX_ENCODING_DEPTH 24
|
||||
|
||||
#endif /* !FIXED_POINT */
|
||||
|
||||
#ifndef GLOBAL_STACK_SIZE
|
||||
#ifdef FIXED_POINT
|
||||
#define GLOBAL_STACK_SIZE 120000
|
||||
#else
|
||||
#define GLOBAL_STACK_SIZE 120000
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* ARCH_H */
|
||||
@@ -1,353 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
# Copyright (C) 2002-2013 Xiph.org Foundation
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# - Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# - Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
my $bigend; # little/big endian
|
||||
my $nxstack;
|
||||
my $apple = 0;
|
||||
my $symprefix = "";
|
||||
|
||||
$nxstack = 0;
|
||||
|
||||
eval 'exec /usr/local/bin/perl -S $0 ${1+"$@"}'
|
||||
if $running_under_some_shell;
|
||||
|
||||
while ($ARGV[0] =~ /^-/) {
|
||||
$_ = shift;
|
||||
last if /^--$/;
|
||||
if (/^-n$/) {
|
||||
$nflag++;
|
||||
next;
|
||||
}
|
||||
if (/^--apple$/) {
|
||||
$apple = 1;
|
||||
$symprefix = "_";
|
||||
next;
|
||||
}
|
||||
die "I don't recognize this switch: $_\\n";
|
||||
}
|
||||
$printit++ unless $nflag;
|
||||
|
||||
$\ = "\n"; # automatically add newline on print
|
||||
$n=0;
|
||||
|
||||
$thumb = 0; # ARM mode by default, not Thumb.
|
||||
@proc_stack = ();
|
||||
|
||||
printf (" .syntax unified\n");
|
||||
|
||||
LINE:
|
||||
while (<>) {
|
||||
|
||||
# For ADRLs we need to add a new line after the substituted one.
|
||||
$addPadding = 0;
|
||||
|
||||
# First, we do not dare to touch *anything* inside double quotes, do we?
|
||||
# Second, if you want a dollar character in the string,
|
||||
# insert two of them -- that's how ARM C and assembler treat strings.
|
||||
s/^([A-Za-z_]\w*)[ \t]+DCB[ \t]*\"/$1: .ascii \"/ && do { s/\$\$/\$/g; next };
|
||||
s/\bDCB\b[ \t]*\"/.ascii \"/ && do { s/\$\$/\$/g; next };
|
||||
s/^(\S+)\s+RN\s+(\S+)/$1 .req r$2/ && do { s/\$\$/\$/g; next };
|
||||
# If there's nothing on a line but a comment, don't try to apply any further
|
||||
# substitutions (this is a cheap hack to avoid mucking up the license header)
|
||||
s/^([ \t]*);/$1@/ && do { s/\$\$/\$/g; next };
|
||||
# If substituted -- leave immediately !
|
||||
|
||||
s/@/,:/;
|
||||
s/;/@/;
|
||||
while ( /@.*'/ ) {
|
||||
s/(@.*)'/$1/g;
|
||||
}
|
||||
s/\{FALSE\}/0/g;
|
||||
s/\{TRUE\}/1/g;
|
||||
s/\{(\w\w\w\w+)\}/$1/g;
|
||||
s/\bINCLUDE[ \t]*([^ \t\n]+)/.include \"$1\"/;
|
||||
s/\bGET[ \t]*([^ \t\n]+)/.include \"${ my $x=$1; $x =~ s|\.s|-gnu.S|; \$x }\"/;
|
||||
s/\bIMPORT\b/.extern/;
|
||||
s/\bEXPORT\b\s*/.global $symprefix/;
|
||||
s/^(\s+)\[/$1IF/;
|
||||
s/^(\s+)\|/$1ELSE/;
|
||||
s/^(\s+)\]/$1ENDIF/;
|
||||
s/IF *:DEF:/ .ifdef/;
|
||||
s/IF *:LNOT: *:DEF:/ .ifndef/;
|
||||
s/ELSE/ .else/;
|
||||
s/ENDIF/ .endif/;
|
||||
|
||||
if( /\bIF\b/ ) {
|
||||
s/\bIF\b/ .if/;
|
||||
s/=/==/;
|
||||
}
|
||||
if ( $n == 2) {
|
||||
s/\$/\\/g;
|
||||
}
|
||||
if ($n == 1) {
|
||||
s/\$//g;
|
||||
s/label//g;
|
||||
$n = 2;
|
||||
}
|
||||
if ( /MACRO/ ) {
|
||||
s/MACRO *\n/.macro/;
|
||||
$n=1;
|
||||
}
|
||||
if ( /\bMEND\b/ ) {
|
||||
s/\bMEND\b/.endm/;
|
||||
$n=0;
|
||||
}
|
||||
|
||||
# ".rdata" doesn't work in 'as' version 2.13.2, as it is ".rodata" there.
|
||||
#
|
||||
if ( /\bAREA\b/ ) {
|
||||
my $align;
|
||||
$align = "2";
|
||||
if ( /ALIGN=(\d+)/ ) {
|
||||
$align = $1;
|
||||
}
|
||||
if ( /CODE/ ) {
|
||||
$nxstack = 1;
|
||||
}
|
||||
s/^(.+)CODE(.+)READONLY(.*)/ .text/;
|
||||
s/^(.+)DATA(.+)READONLY(.*)/ .section .rdata/;
|
||||
s/^(.+)\|\|\.data\|\|(.+)/ .data/;
|
||||
s/^(.+)\|\|\.bss\|\|(.+)/ .bss/;
|
||||
s/$/; .p2align $align/;
|
||||
# Enable NEON instructions but don't produce a binary that requires
|
||||
# ARMv7. RVCT does not have equivalent directives, so we just do this
|
||||
# for all CODE areas.
|
||||
if ( /.text/ ) {
|
||||
# Separating .arch, .fpu, etc., by semicolons does not work (gas
|
||||
# thinks the semicolon is part of the arch name, even when there's
|
||||
# whitespace separating them). Sadly this means our line numbers
|
||||
# won't match the original source file (we could use the .line
|
||||
# directive, which is documented to be obsolete, but then gdb will
|
||||
# show the wrong line in the translated source file).
|
||||
s/$/; .arch armv7-a\n .fpu neon\n .object_arch armv4t/ unless ($apple);
|
||||
}
|
||||
}
|
||||
|
||||
s/\|\|\.constdata\$(\d+)\|\|/.L_CONST$1/; # ||.constdata$3||
|
||||
s/\|\|\.bss\$(\d+)\|\|/.L_BSS$1/; # ||.bss$2||
|
||||
s/\|\|\.data\$(\d+)\|\|/.L_DATA$1/; # ||.data$2||
|
||||
s/\|\|([a-zA-Z0-9_]+)\@([a-zA-Z0-9_]+)\|\|/@ $&/;
|
||||
s/^(\s+)\%(\s)/ .space $1/;
|
||||
|
||||
s/\|(.+)\.(\d+)\|/\.$1_$2/; # |L80.123| -> .L80_123
|
||||
s/\bCODE32\b/.code 32/ && do {$thumb = 0};
|
||||
s/\bCODE16\b/.code 16/ && do {$thumb = 1};
|
||||
if (/\bPROC\b/)
|
||||
{
|
||||
my $prefix;
|
||||
my $proc;
|
||||
/^([A-Za-z_\.]\w+)\b/;
|
||||
$proc = $1;
|
||||
$prefix = "";
|
||||
if ($proc)
|
||||
{
|
||||
$prefix = $prefix.sprintf("\t.type\t%s, %%function", $proc) unless ($apple);
|
||||
# Make sure we $prefix isn't empty here (for the $apple case).
|
||||
# We handle mangling the label here, make sure it doesn't match
|
||||
# the label handling below (if $prefix would be empty).
|
||||
$prefix = $prefix."; ";
|
||||
push(@proc_stack, $proc);
|
||||
s/^[A-Za-z_\.]\w+/$symprefix$&:/;
|
||||
}
|
||||
$prefix = $prefix."\t.thumb_func; " if ($thumb);
|
||||
s/\bPROC\b/@ $&/;
|
||||
$_ = $prefix.$_;
|
||||
}
|
||||
s/^(\s*)(S|Q|SH|U|UQ|UH)ASX\b/$1$2ADDSUBX/;
|
||||
s/^(\s*)(S|Q|SH|U|UQ|UH)SAX\b/$1$2SUBADDX/;
|
||||
if (/\bENDP\b/)
|
||||
{
|
||||
my $proc;
|
||||
s/\bENDP\b/@ $&/;
|
||||
$proc = pop(@proc_stack);
|
||||
$_ = "\t.size $proc, .-$proc".$_ if ($proc && !$apple);
|
||||
}
|
||||
s/\bSUBT\b/@ $&/;
|
||||
s/\bDATA\b/@ $&/; # DATA directive is deprecated -- Asm guide, p.7-25
|
||||
s/\bKEEP\b/@ $&/;
|
||||
s/\bEXPORTAS\b/@ $&/;
|
||||
s/\|\|(.)+\bEQU\b/@ $&/;
|
||||
s/\|\|([\w\$]+)\|\|/$1/;
|
||||
s/\bENTRY\b/@ $&/;
|
||||
s/\bASSERT\b/@ $&/;
|
||||
s/\bGBLL\b/@ $&/;
|
||||
s/\bGBLA\b/@ $&/;
|
||||
s/^\W+OPT\b/@ $&/;
|
||||
s/:OR:/|/g;
|
||||
s/:SHL:/<</g;
|
||||
s/:SHR:/>>/g;
|
||||
s/:AND:/&/g;
|
||||
s/:LAND:/&&/g;
|
||||
s/CPSR/cpsr/;
|
||||
s/SPSR/spsr/;
|
||||
s/ALIGN$/.balign 4/;
|
||||
s/ALIGN\s+([0-9x]+)$/.balign $1/;
|
||||
s/psr_cxsf/psr_all/;
|
||||
s/LTORG/.ltorg/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+EQU/ .set $1,/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+SETL/ .set $1,/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+SETA/ .set $1,/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+\*/ .set $1,/;
|
||||
|
||||
# {PC} + 0xdeadfeed --> . + 0xdeadfeed
|
||||
s/\{PC\} \+/ \. +/;
|
||||
|
||||
# Single hex constant on the line !
|
||||
#
|
||||
# >>> NOTE <<<
|
||||
# Double-precision floats in gcc are always mixed-endian, which means
|
||||
# bytes in two words are little-endian, but words are big-endian.
|
||||
# So, 0x0000deadfeed0000 would be stored as 0x0000dead at low address
|
||||
# and 0xfeed0000 at high address.
|
||||
#
|
||||
s/\bDCFD\b[ \t]+0x([a-fA-F0-9]{8})([a-fA-F0-9]{8})/.long 0x$1, 0x$2/;
|
||||
# Only decimal constants on the line, no hex !
|
||||
s/\bDCFD\b[ \t]+([0-9\.\-]+)/.double $1/;
|
||||
|
||||
# Single hex constant on the line !
|
||||
# s/\bDCFS\b[ \t]+0x([a-f0-9]{8})([a-f0-9]{8})/.long 0x$1, 0x$2/;
|
||||
# Only decimal constants on the line, no hex !
|
||||
# s/\bDCFS\b[ \t]+([0-9\.\-]+)/.double $1/;
|
||||
s/\bDCFS[ \t]+0x/.word 0x/;
|
||||
s/\bDCFS\b/.float/;
|
||||
|
||||
s/^([A-Za-z_]\w*)[ \t]+DCD/$1 .word/;
|
||||
s/\bDCD\b/.word/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+DCW/$1 .short/;
|
||||
s/\bDCW\b/.short/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+DCB/$1 .byte/;
|
||||
s/\bDCB\b/.byte/;
|
||||
s/^([A-Za-z_]\w*)[ \t]+\%/.comm $1,/;
|
||||
s/^[A-Za-z_\.]\w+/$&:/;
|
||||
s/^(\d+)/$1:/;
|
||||
s/\%(\d+)/$1b_or_f/;
|
||||
s/\%[Bb](\d+)/$1b/;
|
||||
s/\%[Ff](\d+)/$1f/;
|
||||
s/\%[Ff][Tt](\d+)/$1f/;
|
||||
s/&([\dA-Fa-f]+)/0x$1/;
|
||||
if ( /\b2_[01]+\b/ ) {
|
||||
s/\b2_([01]+)\b/conv$1&&&&/g;
|
||||
while ( /[01][01][01][01]&&&&/ ) {
|
||||
s/0000&&&&/&&&&0/g;
|
||||
s/0001&&&&/&&&&1/g;
|
||||
s/0010&&&&/&&&&2/g;
|
||||
s/0011&&&&/&&&&3/g;
|
||||
s/0100&&&&/&&&&4/g;
|
||||
s/0101&&&&/&&&&5/g;
|
||||
s/0110&&&&/&&&&6/g;
|
||||
s/0111&&&&/&&&&7/g;
|
||||
s/1000&&&&/&&&&8/g;
|
||||
s/1001&&&&/&&&&9/g;
|
||||
s/1010&&&&/&&&&A/g;
|
||||
s/1011&&&&/&&&&B/g;
|
||||
s/1100&&&&/&&&&C/g;
|
||||
s/1101&&&&/&&&&D/g;
|
||||
s/1110&&&&/&&&&E/g;
|
||||
s/1111&&&&/&&&&F/g;
|
||||
}
|
||||
s/000&&&&/&&&&0/g;
|
||||
s/001&&&&/&&&&1/g;
|
||||
s/010&&&&/&&&&2/g;
|
||||
s/011&&&&/&&&&3/g;
|
||||
s/100&&&&/&&&&4/g;
|
||||
s/101&&&&/&&&&5/g;
|
||||
s/110&&&&/&&&&6/g;
|
||||
s/111&&&&/&&&&7/g;
|
||||
s/00&&&&/&&&&0/g;
|
||||
s/01&&&&/&&&&1/g;
|
||||
s/10&&&&/&&&&2/g;
|
||||
s/11&&&&/&&&&3/g;
|
||||
s/0&&&&/&&&&0/g;
|
||||
s/1&&&&/&&&&1/g;
|
||||
s/conv&&&&/0x/g;
|
||||
}
|
||||
|
||||
if ( /commandline/)
|
||||
{
|
||||
if( /-bigend/)
|
||||
{
|
||||
$bigend=1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( /\bDCDU\b/ )
|
||||
{
|
||||
my $cmd=$_;
|
||||
my $value;
|
||||
my $prefix;
|
||||
my $w1;
|
||||
my $w2;
|
||||
my $w3;
|
||||
my $w4;
|
||||
|
||||
s/\s+DCDU\b/@ $&/;
|
||||
|
||||
$cmd =~ /\bDCDU\b\s+0x(\d+)/;
|
||||
$value = $1;
|
||||
$value =~ /(\w\w)(\w\w)(\w\w)(\w\w)/;
|
||||
$w1 = $1;
|
||||
$w2 = $2;
|
||||
$w3 = $3;
|
||||
$w4 = $4;
|
||||
|
||||
if( $bigend ne "")
|
||||
{
|
||||
# big endian
|
||||
$prefix = "\t.byte\t0x".$w1.";".
|
||||
"\t.byte\t0x".$w2.";".
|
||||
"\t.byte\t0x".$w3.";".
|
||||
"\t.byte\t0x".$w4."; ";
|
||||
}
|
||||
else
|
||||
{
|
||||
# little endian
|
||||
$prefix = "\t.byte\t0x".$w4.";".
|
||||
"\t.byte\t0x".$w3.";".
|
||||
"\t.byte\t0x".$w2.";".
|
||||
"\t.byte\t0x".$w1."; ";
|
||||
}
|
||||
$_=$prefix.$_;
|
||||
}
|
||||
|
||||
if ( /\badrl\b/i )
|
||||
{
|
||||
s/\badrl\s+(\w+)\s*,\s*(\w+)/ldr $1,=$2/i;
|
||||
$addPadding = 1;
|
||||
}
|
||||
s/\bEND\b/@ END/;
|
||||
} continue {
|
||||
printf ("%s", $_) if $printit;
|
||||
if ($addPadding != 0)
|
||||
{
|
||||
printf (" mov r0,r0\n");
|
||||
$addPadding = 0;
|
||||
}
|
||||
}
|
||||
#If we had a code section, mark that this object doesn't need an executable
|
||||
# stack.
|
||||
if ($nxstack && !$apple) {
|
||||
printf (" .section\t.note.GNU-stack,\"\",\%\%progbits\n");
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/* Copyright (c) 2010 Xiph.Org Foundation
|
||||
* Copyright (c) 2013 Parrot
|
||||
* Copyright (c) 2024 Arm Limited */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "kiss_fft.h"
|
||||
#include "mathops.h"
|
||||
#include "mdct.h"
|
||||
#include "pitch.h"
|
||||
|
||||
#if defined(OPUS_HAVE_RTCD)
|
||||
|
||||
# if !defined(DISABLE_FLOAT_API)
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
|
||||
void (*const CELT_FLOAT2INT16_IMPL[OPUS_ARCHMASK+1])(const float * OPUS_RESTRICT in, short * OPUS_RESTRICT out, int cnt) = {
|
||||
celt_float2int16_c, /* ARMv4 */
|
||||
celt_float2int16_c, /* EDSP */
|
||||
celt_float2int16_c, /* Media */
|
||||
celt_float2int16_neon,/* NEON */
|
||||
celt_float2int16_neon /* DOTPROD */
|
||||
};
|
||||
|
||||
int (*const OPUS_LIMIT2_CHECKWITHIN1_IMPL[OPUS_ARCHMASK+1])(float * samples, int cnt) = {
|
||||
opus_limit2_checkwithin1_c, /* ARMv4 */
|
||||
opus_limit2_checkwithin1_c, /* EDSP */
|
||||
opus_limit2_checkwithin1_c, /* Media */
|
||||
opus_limit2_checkwithin1_neon,/* NEON */
|
||||
opus_limit2_checkwithin1_neon /* DOTPROD */
|
||||
};
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
|
||||
opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y, int N) = {
|
||||
celt_inner_prod_c, /* ARMv4 */
|
||||
celt_inner_prod_c, /* EDSP */
|
||||
celt_inner_prod_c, /* Media */
|
||||
celt_inner_prod_neon,/* NEON */
|
||||
celt_inner_prod_neon /* DOTPROD */
|
||||
};
|
||||
|
||||
void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02,
|
||||
int N, opus_val32 *xy1, opus_val32 *xy2) = {
|
||||
dual_inner_prod_c, /* ARMv4 */
|
||||
dual_inner_prod_c, /* EDSP */
|
||||
dual_inner_prod_c, /* Media */
|
||||
dual_inner_prod_neon,/* NEON */
|
||||
dual_inner_prod_neon /* DOTPROD */
|
||||
};
|
||||
# endif
|
||||
|
||||
# if defined(FIXED_POINT)
|
||||
# if ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \
|
||||
(defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \
|
||||
(defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP)))
|
||||
opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
|
||||
const opus_val16 *, opus_val32 *, int, int, int) = {
|
||||
celt_pitch_xcorr_c, /* ARMv4 */
|
||||
MAY_HAVE_EDSP(celt_pitch_xcorr), /* EDSP */
|
||||
MAY_HAVE_MEDIA(celt_pitch_xcorr), /* Media */
|
||||
MAY_HAVE_NEON(celt_pitch_xcorr), /* NEON */
|
||||
MAY_HAVE_NEON(celt_pitch_xcorr) /* DOTPROD */
|
||||
};
|
||||
|
||||
# endif
|
||||
# else /* !FIXED_POINT */
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
|
||||
void (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
|
||||
const opus_val16 *, opus_val32 *, int, int, int) = {
|
||||
celt_pitch_xcorr_c, /* ARMv4 */
|
||||
celt_pitch_xcorr_c, /* EDSP */
|
||||
celt_pitch_xcorr_c, /* Media */
|
||||
celt_pitch_xcorr_float_neon, /* Neon */
|
||||
celt_pitch_xcorr_float_neon /* DOTPROD */
|
||||
};
|
||||
# endif
|
||||
# endif /* FIXED_POINT */
|
||||
|
||||
#if defined(FIXED_POINT) && defined(OPUS_HAVE_RTCD) && \
|
||||
defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
|
||||
|
||||
void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])(
|
||||
const opus_val16 *x,
|
||||
const opus_val16 *y,
|
||||
opus_val32 sum[4],
|
||||
int len
|
||||
) = {
|
||||
xcorr_kernel_c, /* ARMv4 */
|
||||
xcorr_kernel_c, /* EDSP */
|
||||
xcorr_kernel_c, /* Media */
|
||||
xcorr_kernel_neon_fixed, /* Neon */
|
||||
xcorr_kernel_neon_fixed /* DOTPROD */
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
# if defined(HAVE_ARM_NE10)
|
||||
# if defined(CUSTOM_MODES)
|
||||
int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
|
||||
opus_fft_alloc_arch_c, /* ARMv4 */
|
||||
opus_fft_alloc_arch_c, /* EDSP */
|
||||
opus_fft_alloc_arch_c, /* Media */
|
||||
opus_fft_alloc_arm_neon, /* Neon with NE10 library support */
|
||||
opus_fft_alloc_arm_neon /* DOTPROD with NE10 library support */
|
||||
};
|
||||
|
||||
void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
|
||||
opus_fft_free_arch_c, /* ARMv4 */
|
||||
opus_fft_free_arch_c, /* EDSP */
|
||||
opus_fft_free_arch_c, /* Media */
|
||||
opus_fft_free_arm_neon, /* Neon with NE10 */
|
||||
opus_fft_free_arm_neon /* DOTPROD with NE10 */
|
||||
};
|
||||
# endif /* CUSTOM_MODES */
|
||||
|
||||
void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout) = {
|
||||
opus_fft_c, /* ARMv4 */
|
||||
opus_fft_c, /* EDSP */
|
||||
opus_fft_c, /* Media */
|
||||
opus_fft_neon, /* Neon with NE10 */
|
||||
opus_fft_neon /* DOTPROD with NE10 */
|
||||
};
|
||||
|
||||
void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout) = {
|
||||
opus_ifft_c, /* ARMv4 */
|
||||
opus_ifft_c, /* EDSP */
|
||||
opus_ifft_c, /* Media */
|
||||
opus_ifft_neon, /* Neon with NE10 */
|
||||
opus_ifft_neon /* DOTPROD with NE10 */
|
||||
};
|
||||
|
||||
void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
|
||||
kiss_fft_scalar *in,
|
||||
kiss_fft_scalar * OPUS_RESTRICT out,
|
||||
const opus_val16 *window,
|
||||
int overlap, int shift,
|
||||
int stride, int arch) = {
|
||||
clt_mdct_forward_c, /* ARMv4 */
|
||||
clt_mdct_forward_c, /* EDSP */
|
||||
clt_mdct_forward_c, /* Media */
|
||||
clt_mdct_forward_neon, /* Neon with NE10 */
|
||||
clt_mdct_forward_neon /* DOTPROD with NE10 */
|
||||
};
|
||||
|
||||
void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
|
||||
kiss_fft_scalar *in,
|
||||
kiss_fft_scalar * OPUS_RESTRICT out,
|
||||
const opus_val16 *window,
|
||||
int overlap, int shift,
|
||||
int stride, int arch) = {
|
||||
clt_mdct_backward_c, /* ARMv4 */
|
||||
clt_mdct_backward_c, /* EDSP */
|
||||
clt_mdct_backward_c, /* Media */
|
||||
clt_mdct_backward_neon, /* Neon with NE10 */
|
||||
clt_mdct_backward_neon /* DOTPROD with NE10 */
|
||||
};
|
||||
|
||||
# endif /* HAVE_ARM_NE10 */
|
||||
# endif /* OPUS_ARM_MAY_HAVE_NEON_INTR */
|
||||
|
||||
#endif /* OPUS_HAVE_RTCD */
|
||||
@@ -1,291 +0,0 @@
|
||||
/* Copyright (c) 2010 Xiph.Org Foundation
|
||||
* Copyright (c) 2013 Parrot */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* Original code from libtheora modified to suit to Opus */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef OPUS_HAVE_RTCD
|
||||
|
||||
#include "armcpu.h"
|
||||
#include "cpu_support.h"
|
||||
#include "os_support.h"
|
||||
#include "opus_types.h"
|
||||
#include "arch.h"
|
||||
|
||||
#define OPUS_CPU_ARM_V4_FLAG (1<<OPUS_ARCH_ARM_V4)
|
||||
#define OPUS_CPU_ARM_EDSP_FLAG (1<<OPUS_ARCH_ARM_EDSP)
|
||||
#define OPUS_CPU_ARM_MEDIA_FLAG (1<<OPUS_ARCH_ARM_MEDIA)
|
||||
#define OPUS_CPU_ARM_NEON_FLAG (1<<OPUS_ARCH_ARM_NEON)
|
||||
#define OPUS_CPU_ARM_DOTPROD_FLAG (1<<OPUS_ARCH_ARM_DOTPROD)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
/*For GetExceptionCode() and EXCEPTION_ILLEGAL_INSTRUCTION.*/
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_EXTRA_LEAN
|
||||
# include <windows.h>
|
||||
|
||||
static OPUS_INLINE opus_uint32 opus_cpu_capabilities(void){
|
||||
opus_uint32 flags;
|
||||
flags=0;
|
||||
/* MSVC has no OPUS_INLINE __asm support for ARM, but it does let you __emit
|
||||
* instructions via their assembled hex code.
|
||||
* All of these instructions should be essentially nops. */
|
||||
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
__try{
|
||||
/*PLD [r13]*/
|
||||
__emit(0xF5DDF000);
|
||||
flags|=OPUS_CPU_ARM_EDSP_FLAG;
|
||||
}
|
||||
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
|
||||
/*Ignore exception.*/
|
||||
}
|
||||
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
__try{
|
||||
/*SHADD8 r3,r3,r3*/
|
||||
__emit(0xE6333F93);
|
||||
flags|=OPUS_CPU_ARM_MEDIA_FLAG;
|
||||
}
|
||||
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
|
||||
/*Ignore exception.*/
|
||||
}
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
__try{
|
||||
/*VORR q0,q0,q0*/
|
||||
__emit(0xF2200150);
|
||||
flags|=OPUS_CPU_ARM_NEON_FLAG;
|
||||
}
|
||||
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
|
||||
/*Ignore exception.*/
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
return flags;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
/* Linux based */
|
||||
#include <stdio.h>
|
||||
|
||||
static opus_uint32 opus_cpu_capabilities(void)
|
||||
{
|
||||
opus_uint32 flags = 0;
|
||||
FILE *cpuinfo;
|
||||
|
||||
/* Reading /proc/self/auxv would be easier, but that doesn't work reliably on
|
||||
* Android */
|
||||
cpuinfo = fopen("/proc/cpuinfo", "r");
|
||||
|
||||
if(cpuinfo != NULL)
|
||||
{
|
||||
/* 512 should be enough for anybody (it's even enough for all the flags that
|
||||
* x86 has accumulated... so far). */
|
||||
char buf[512];
|
||||
|
||||
while(fgets(buf, 512, cpuinfo) != NULL)
|
||||
{
|
||||
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
/* Search for edsp and neon flag */
|
||||
if(memcmp(buf, "Features", 8) == 0)
|
||||
{
|
||||
char *p;
|
||||
p = strstr(buf, " edsp");
|
||||
if(p != NULL && (p[5] == ' ' || p[5] == '\n'))
|
||||
flags |= OPUS_CPU_ARM_EDSP_FLAG;
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
p = strstr(buf, " neon");
|
||||
if(p != NULL && (p[5] == ' ' || p[5] == '\n'))
|
||||
flags |= OPUS_CPU_ARM_NEON_FLAG;
|
||||
p = strstr(buf, " asimd");
|
||||
if(p != NULL && (p[6] == ' ' || p[6] == '\n'))
|
||||
flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG;
|
||||
# endif
|
||||
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
|
||||
p = strstr(buf, " asimddp");
|
||||
if(p != NULL && (p[8] == ' ' || p[8] == '\n'))
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
/* Search for media capabilities (>= ARMv6) */
|
||||
if(memcmp(buf, "CPU architecture:", 17) == 0)
|
||||
{
|
||||
int version;
|
||||
version = atoi(buf+17);
|
||||
|
||||
if(version >= 6)
|
||||
flags |= OPUS_CPU_ARM_MEDIA_FLAG;
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
|
||||
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
|
||||
# if defined(OPUS_ARM_PRESUME_DOTPROD)
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
fclose(cpuinfo);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
static opus_uint32 opus_cpu_capabilities(void)
|
||||
{
|
||||
opus_uint32 flags = 0;
|
||||
|
||||
#if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
|
||||
size_t size = sizeof(uint32_t);
|
||||
uint32_t value = 0;
|
||||
if (!sysctlbyname("hw.optional.arm.FEAT_DotProd", &value, &size, NULL, 0) && value)
|
||||
{
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
|
||||
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
|
||||
# if defined(OPUS_ARM_PRESUME_DOTPROD)
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
# endif
|
||||
#endif
|
||||
return flags;
|
||||
}
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <sys/auxv.h>
|
||||
|
||||
static opus_uint32 opus_cpu_capabilities(void)
|
||||
{
|
||||
long hwcap = 0;
|
||||
opus_uint32 flags = 0;
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
/* FreeBSD requires armv6+, which always supports media instructions */
|
||||
flags |= OPUS_CPU_ARM_MEDIA_FLAG;
|
||||
# endif
|
||||
|
||||
elf_aux_info(AT_HWCAP, &hwcap, sizeof hwcap);
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|
||||
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
# ifdef HWCAP_EDSP
|
||||
if (hwcap & HWCAP_EDSP)
|
||||
flags |= OPUS_CPU_ARM_EDSP_FLAG;
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
# ifdef HWCAP_NEON
|
||||
if (hwcap & HWCAP_NEON)
|
||||
flags |= OPUS_CPU_ARM_NEON_FLAG;
|
||||
# elif defined(HWCAP_ASIMD)
|
||||
if (hwcap & HWCAP_ASIMD)
|
||||
flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG;
|
||||
# endif
|
||||
# endif
|
||||
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD) && defined(HWCAP_ASIMDDP)
|
||||
if (hwcap & HWCAP_ASIMDDP)
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
|
||||
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
|
||||
# if defined(OPUS_ARM_PRESUME_DOTPROD)
|
||||
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
return (flags);
|
||||
}
|
||||
|
||||
#else
|
||||
/* The feature registers which can tell us what the processor supports are
|
||||
* accessible in priveleged modes only, so we can't have a general user-space
|
||||
* detection method like on x86.*/
|
||||
# error "Configured to use ARM asm but no CPU detection method available for " \
|
||||
"your platform. Reconfigure with --disable-rtcd (or send patches)."
|
||||
#endif
|
||||
|
||||
static int opus_select_arch_impl(void)
|
||||
{
|
||||
opus_uint32 flags = opus_cpu_capabilities();
|
||||
int arch = 0;
|
||||
|
||||
if(!(flags & OPUS_CPU_ARM_EDSP_FLAG)) {
|
||||
/* Asserts ensure arch values are sequential */
|
||||
celt_assert(arch == OPUS_ARCH_ARM_V4);
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
if(!(flags & OPUS_CPU_ARM_MEDIA_FLAG)) {
|
||||
celt_assert(arch == OPUS_ARCH_ARM_EDSP);
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
if(!(flags & OPUS_CPU_ARM_NEON_FLAG)) {
|
||||
celt_assert(arch == OPUS_ARCH_ARM_MEDIA);
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
if(!(flags & OPUS_CPU_ARM_DOTPROD_FLAG)) {
|
||||
celt_assert(arch == OPUS_ARCH_ARM_NEON);
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
celt_assert(arch == OPUS_ARCH_ARM_DOTPROD);
|
||||
return arch;
|
||||
}
|
||||
|
||||
int opus_select_arch(void) {
|
||||
int arch = opus_select_arch_impl();
|
||||
#ifdef FUZZING
|
||||
arch = rand()%(arch+1);
|
||||
#endif
|
||||
return arch;
|
||||
}
|
||||
#endif
|
||||
@@ -1,90 +0,0 @@
|
||||
/* Copyright (c) 2010 Xiph.Org Foundation
|
||||
* Copyright (c) 2013 Parrot */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if !defined(ARMCPU_H)
|
||||
# define ARMCPU_H
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_EDSP)
|
||||
# define MAY_HAVE_EDSP(name) name ## _edsp
|
||||
# else
|
||||
# define MAY_HAVE_EDSP(name) name ## _c
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_MEDIA)
|
||||
# define MAY_HAVE_MEDIA(name) name ## _media
|
||||
# else
|
||||
# define MAY_HAVE_MEDIA(name) MAY_HAVE_EDSP(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_NEON)
|
||||
# define MAY_HAVE_NEON(name) name ## _neon
|
||||
# else
|
||||
# define MAY_HAVE_NEON(name) MAY_HAVE_MEDIA(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
|
||||
# define MAY_HAVE_DOTPROD(name) name ## _dotprod
|
||||
# else
|
||||
# define MAY_HAVE_DOTPROD(name) MAY_HAVE_NEON(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_PRESUME_EDSP)
|
||||
# define PRESUME_EDSP(name) name ## _edsp
|
||||
# else
|
||||
# define PRESUME_EDSP(name) name ## _c
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_PRESUME_MEDIA)
|
||||
# define PRESUME_MEDIA(name) name ## _media
|
||||
# else
|
||||
# define PRESUME_MEDIA(name) PRESUME_EDSP(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_PRESUME_NEON)
|
||||
# define PRESUME_NEON(name) name ## _neon
|
||||
# else
|
||||
# define PRESUME_NEON(name) PRESUME_MEDIA(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_ARM_PRESUME_DOTPROD)
|
||||
# define PRESUME_DOTPROD(name) name ## _dotprod
|
||||
# else
|
||||
# define PRESUME_DOTPROD(name) PRESUME_NEON(name)
|
||||
# endif
|
||||
|
||||
# if defined(OPUS_HAVE_RTCD)
|
||||
int opus_select_arch(void);
|
||||
|
||||
#define OPUS_ARCH_ARM_V4 (0)
|
||||
#define OPUS_ARCH_ARM_EDSP (1)
|
||||
#define OPUS_ARCH_ARM_MEDIA (2)
|
||||
#define OPUS_ARCH_ARM_NEON (3)
|
||||
#define OPUS_ARCH_ARM_DOTPROD (4)
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
@@ -1,37 +0,0 @@
|
||||
/* Copyright (C) 2013 Mozilla Corporation */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
; Set the following to 1 if we have EDSP instructions
|
||||
; (LDRD/STRD, etc., ARMv5E and later).
|
||||
OPUS_ARM_MAY_HAVE_EDSP * @OPUS_ARM_MAY_HAVE_EDSP@
|
||||
|
||||
; Set the following to 1 if we have ARMv6 media instructions.
|
||||
OPUS_ARM_MAY_HAVE_MEDIA * @OPUS_ARM_MAY_HAVE_MEDIA@
|
||||
|
||||
; Set the following to 1 if we have NEON (some ARMv7)
|
||||
OPUS_ARM_MAY_HAVE_NEON * @OPUS_ARM_MAY_HAVE_NEON@
|
||||
|
||||
END
|
||||
@@ -1,173 +0,0 @@
|
||||
/* Copyright (c) 2015 Xiph.Org Foundation
|
||||
Written by Viswanath Puttagunta */
|
||||
/**
|
||||
@file celt_fft_ne10.c
|
||||
@brief ARM Neon optimizations for fft using NE10 library
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SKIP_CONFIG_H
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <NE10_dsp.h>
|
||||
#include "os_support.h"
|
||||
#include "kiss_fft.h"
|
||||
#include "stack_alloc.h"
|
||||
|
||||
#if !defined(FIXED_POINT)
|
||||
# define NE10_FFT_ALLOC_C2C_TYPE_NEON ne10_fft_alloc_c2c_float32_neon
|
||||
# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_float32_t
|
||||
# define NE10_FFT_STATE_TYPE_T ne10_fft_state_float32_t
|
||||
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_float32
|
||||
# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_float32_t
|
||||
# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_float32_neon
|
||||
#else
|
||||
# define NE10_FFT_ALLOC_C2C_TYPE_NEON(nfft) ne10_fft_alloc_c2c_int32_neon(nfft)
|
||||
# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_int32_t
|
||||
# define NE10_FFT_STATE_TYPE_T ne10_fft_state_int32_t
|
||||
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32
|
||||
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32
|
||||
# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_int32_t
|
||||
# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_int32_neon
|
||||
#endif
|
||||
|
||||
#if defined(CUSTOM_MODES)
|
||||
|
||||
/* nfft lengths in NE10 that support scaled fft */
|
||||
# define NE10_FFTSCALED_SUPPORT_MAX 4
|
||||
static const int ne10_fft_scaled_support[NE10_FFTSCALED_SUPPORT_MAX] = {
|
||||
480, 240, 120, 60
|
||||
};
|
||||
|
||||
int opus_fft_alloc_arm_neon(kiss_fft_state *st)
|
||||
{
|
||||
int i;
|
||||
size_t memneeded = sizeof(struct arch_fft_state);
|
||||
|
||||
st->arch_fft = (arch_fft_state *)opus_alloc(memneeded);
|
||||
if (!st->arch_fft)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < NE10_FFTSCALED_SUPPORT_MAX; i++) {
|
||||
if(st->nfft == ne10_fft_scaled_support[i])
|
||||
break;
|
||||
}
|
||||
if (i == NE10_FFTSCALED_SUPPORT_MAX) {
|
||||
/* This nfft length (scaled fft) is not supported in NE10 */
|
||||
st->arch_fft->is_supported = 0;
|
||||
st->arch_fft->priv = NULL;
|
||||
}
|
||||
else {
|
||||
st->arch_fft->is_supported = 1;
|
||||
st->arch_fft->priv = (void *)NE10_FFT_ALLOC_C2C_TYPE_NEON(st->nfft);
|
||||
if (st->arch_fft->priv == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opus_fft_free_arm_neon(kiss_fft_state *st)
|
||||
{
|
||||
NE10_FFT_CFG_TYPE_T cfg;
|
||||
|
||||
if (!st->arch_fft)
|
||||
return;
|
||||
|
||||
cfg = (NE10_FFT_CFG_TYPE_T)st->arch_fft->priv;
|
||||
if (cfg)
|
||||
NE10_FFT_DESTROY_C2C_TYPE(cfg);
|
||||
opus_free(st->arch_fft);
|
||||
}
|
||||
#endif
|
||||
|
||||
void opus_fft_neon(const kiss_fft_state *st,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout)
|
||||
{
|
||||
NE10_FFT_STATE_TYPE_T state;
|
||||
NE10_FFT_CFG_TYPE_T cfg = &state;
|
||||
VARDECL(NE10_FFT_CPX_TYPE_T, buffer);
|
||||
SAVE_STACK;
|
||||
ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T);
|
||||
|
||||
if (!st->arch_fft->is_supported) {
|
||||
/* This nfft length (scaled fft) not supported in NE10 */
|
||||
opus_fft_c(st, fin, fout);
|
||||
}
|
||||
else {
|
||||
memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T));
|
||||
state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0];
|
||||
#if !defined(FIXED_POINT)
|
||||
state.is_forward_scaled = 1;
|
||||
|
||||
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
|
||||
(NE10_FFT_CPX_TYPE_T *)fin,
|
||||
cfg, 0);
|
||||
#else
|
||||
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
|
||||
(NE10_FFT_CPX_TYPE_T *)fin,
|
||||
cfg, 0, 1);
|
||||
#endif
|
||||
}
|
||||
RESTORE_STACK;
|
||||
}
|
||||
|
||||
void opus_ifft_neon(const kiss_fft_state *st,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout)
|
||||
{
|
||||
NE10_FFT_STATE_TYPE_T state;
|
||||
NE10_FFT_CFG_TYPE_T cfg = &state;
|
||||
VARDECL(NE10_FFT_CPX_TYPE_T, buffer);
|
||||
SAVE_STACK;
|
||||
ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T);
|
||||
|
||||
if (!st->arch_fft->is_supported) {
|
||||
/* This nfft length (scaled fft) not supported in NE10 */
|
||||
opus_ifft_c(st, fin, fout);
|
||||
}
|
||||
else {
|
||||
memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T));
|
||||
state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0];
|
||||
#if !defined(FIXED_POINT)
|
||||
state.is_backward_scaled = 0;
|
||||
|
||||
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
|
||||
(NE10_FFT_CPX_TYPE_T *)fin,
|
||||
cfg, 1);
|
||||
#else
|
||||
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
|
||||
(NE10_FFT_CPX_TYPE_T *)fin,
|
||||
cfg, 1, 0);
|
||||
#endif
|
||||
}
|
||||
RESTORE_STACK;
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/* Copyright (c) 2015 Xiph.Org Foundation
|
||||
Written by Viswanath Puttagunta */
|
||||
/**
|
||||
@file celt_mdct_ne10.c
|
||||
@brief ARM Neon optimizations for mdct using NE10 library
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SKIP_CONFIG_H
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "kiss_fft.h"
|
||||
#include "_kiss_fft_guts.h"
|
||||
#include "mdct.h"
|
||||
#include "stack_alloc.h"
|
||||
|
||||
void clt_mdct_forward_neon(const mdct_lookup *l,
|
||||
kiss_fft_scalar *in,
|
||||
kiss_fft_scalar * OPUS_RESTRICT out,
|
||||
const opus_val16 *window,
|
||||
int overlap, int shift, int stride, int arch)
|
||||
{
|
||||
int i;
|
||||
int N, N2, N4;
|
||||
VARDECL(kiss_fft_scalar, f);
|
||||
VARDECL(kiss_fft_cpx, f2);
|
||||
const kiss_fft_state *st = l->kfft[shift];
|
||||
const kiss_twiddle_scalar *trig;
|
||||
|
||||
SAVE_STACK;
|
||||
|
||||
N = l->n;
|
||||
trig = l->trig;
|
||||
for (i=0;i<shift;i++)
|
||||
{
|
||||
N >>= 1;
|
||||
trig += N;
|
||||
}
|
||||
N2 = N>>1;
|
||||
N4 = N>>2;
|
||||
|
||||
ALLOC(f, N2, kiss_fft_scalar);
|
||||
ALLOC(f2, N4, kiss_fft_cpx);
|
||||
|
||||
/* Consider the input to be composed of four blocks: [a, b, c, d] */
|
||||
/* Window, shuffle, fold */
|
||||
{
|
||||
/* Temp pointers to make it really clear to the compiler what we're doing */
|
||||
const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1);
|
||||
const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1);
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp = f;
|
||||
const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1);
|
||||
const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1;
|
||||
for(i=0;i<((overlap+3)>>2);i++)
|
||||
{
|
||||
/* Real part arranged as -d-cR, Imag part arranged as -b+aR*/
|
||||
*yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2);
|
||||
*yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]);
|
||||
xp1+=2;
|
||||
xp2-=2;
|
||||
wp1+=2;
|
||||
wp2-=2;
|
||||
}
|
||||
wp1 = window;
|
||||
wp2 = window+overlap-1;
|
||||
for(;i<N4-((overlap+3)>>2);i++)
|
||||
{
|
||||
/* Real part arranged as a-bR, Imag part arranged as -c-dR */
|
||||
*yp++ = *xp2;
|
||||
*yp++ = *xp1;
|
||||
xp1+=2;
|
||||
xp2-=2;
|
||||
}
|
||||
for(;i<N4;i++)
|
||||
{
|
||||
/* Real part arranged as a-bR, Imag part arranged as -c-dR */
|
||||
*yp++ = -MULT16_32_Q15(*wp1, xp1[-N2]) + MULT16_32_Q15(*wp2, *xp2);
|
||||
*yp++ = MULT16_32_Q15(*wp2, *xp1) + MULT16_32_Q15(*wp1, xp2[N2]);
|
||||
xp1+=2;
|
||||
xp2-=2;
|
||||
wp1+=2;
|
||||
wp2-=2;
|
||||
}
|
||||
}
|
||||
/* Pre-rotation */
|
||||
{
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp = f;
|
||||
const kiss_twiddle_scalar *t = &trig[0];
|
||||
for(i=0;i<N4;i++)
|
||||
{
|
||||
kiss_fft_cpx yc;
|
||||
kiss_twiddle_scalar t0, t1;
|
||||
kiss_fft_scalar re, im, yr, yi;
|
||||
t0 = t[i];
|
||||
t1 = t[N4+i];
|
||||
re = *yp++;
|
||||
im = *yp++;
|
||||
yr = S_MUL(re,t0) - S_MUL(im,t1);
|
||||
yi = S_MUL(im,t0) + S_MUL(re,t1);
|
||||
yc.r = yr;
|
||||
yc.i = yi;
|
||||
f2[i] = yc;
|
||||
}
|
||||
}
|
||||
|
||||
opus_fft(st, f2, (kiss_fft_cpx *)f, arch);
|
||||
|
||||
/* Post-rotate */
|
||||
{
|
||||
/* Temp pointers to make it really clear to the compiler what we're doing */
|
||||
const kiss_fft_cpx * OPUS_RESTRICT fp = (kiss_fft_cpx *)f;
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp1 = out;
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1);
|
||||
const kiss_twiddle_scalar *t = &trig[0];
|
||||
/* Temp pointers to make it really clear to the compiler what we're doing */
|
||||
for(i=0;i<N4;i++)
|
||||
{
|
||||
kiss_fft_scalar yr, yi;
|
||||
yr = S_MUL(fp->i,t[N4+i]) - S_MUL(fp->r,t[i]);
|
||||
yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]);
|
||||
*yp1 = yr;
|
||||
*yp2 = yi;
|
||||
fp++;
|
||||
yp1 += 2*stride;
|
||||
yp2 -= 2*stride;
|
||||
}
|
||||
}
|
||||
RESTORE_STACK;
|
||||
}
|
||||
|
||||
void clt_mdct_backward_neon(const mdct_lookup *l,
|
||||
kiss_fft_scalar *in,
|
||||
kiss_fft_scalar * OPUS_RESTRICT out,
|
||||
const opus_val16 * OPUS_RESTRICT window,
|
||||
int overlap, int shift, int stride, int arch)
|
||||
{
|
||||
int i;
|
||||
int N, N2, N4;
|
||||
VARDECL(kiss_fft_scalar, f);
|
||||
const kiss_twiddle_scalar *trig;
|
||||
const kiss_fft_state *st = l->kfft[shift];
|
||||
|
||||
N = l->n;
|
||||
trig = l->trig;
|
||||
for (i=0;i<shift;i++)
|
||||
{
|
||||
N >>= 1;
|
||||
trig += N;
|
||||
}
|
||||
N2 = N>>1;
|
||||
N4 = N>>2;
|
||||
|
||||
ALLOC(f, N2, kiss_fft_scalar);
|
||||
|
||||
/* Pre-rotate */
|
||||
{
|
||||
/* Temp pointers to make it really clear to the compiler what we're doing */
|
||||
const kiss_fft_scalar * OPUS_RESTRICT xp1 = in;
|
||||
const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1);
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp = f;
|
||||
const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0];
|
||||
for(i=0;i<N4;i++)
|
||||
{
|
||||
kiss_fft_scalar yr, yi;
|
||||
yr = S_MUL(*xp2, t[i]) + S_MUL(*xp1, t[N4+i]);
|
||||
yi = S_MUL(*xp1, t[i]) - S_MUL(*xp2, t[N4+i]);
|
||||
yp[2*i] = yr;
|
||||
yp[2*i+1] = yi;
|
||||
xp1+=2*stride;
|
||||
xp2-=2*stride;
|
||||
}
|
||||
}
|
||||
|
||||
opus_ifft(st, (kiss_fft_cpx *)f, (kiss_fft_cpx*)(out+(overlap>>1)), arch);
|
||||
|
||||
/* Post-rotate and de-shuffle from both ends of the buffer at once to make
|
||||
it in-place. */
|
||||
{
|
||||
kiss_fft_scalar * yp0 = out+(overlap>>1);
|
||||
kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2;
|
||||
const kiss_twiddle_scalar *t = &trig[0];
|
||||
/* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the
|
||||
middle pair will be computed twice. */
|
||||
for(i=0;i<(N4+1)>>1;i++)
|
||||
{
|
||||
kiss_fft_scalar re, im, yr, yi;
|
||||
kiss_twiddle_scalar t0, t1;
|
||||
re = yp0[0];
|
||||
im = yp0[1];
|
||||
t0 = t[i];
|
||||
t1 = t[N4+i];
|
||||
/* We'd scale up by 2 here, but instead it's done when mixing the windows */
|
||||
yr = S_MUL(re,t0) + S_MUL(im,t1);
|
||||
yi = S_MUL(re,t1) - S_MUL(im,t0);
|
||||
re = yp1[0];
|
||||
im = yp1[1];
|
||||
yp0[0] = yr;
|
||||
yp1[1] = yi;
|
||||
|
||||
t0 = t[(N4-i-1)];
|
||||
t1 = t[(N2-i-1)];
|
||||
/* We'd scale up by 2 here, but instead it's done when mixing the windows */
|
||||
yr = S_MUL(re,t0) + S_MUL(im,t1);
|
||||
yi = S_MUL(re,t1) - S_MUL(im,t0);
|
||||
yp1[0] = yr;
|
||||
yp0[1] = yi;
|
||||
yp0 += 2;
|
||||
yp1 -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mirror on both sides for TDAC */
|
||||
{
|
||||
kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1;
|
||||
kiss_fft_scalar * OPUS_RESTRICT yp1 = out;
|
||||
const opus_val16 * OPUS_RESTRICT wp1 = window;
|
||||
const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1;
|
||||
|
||||
for(i = 0; i < overlap/2; i++)
|
||||
{
|
||||
kiss_fft_scalar x1, x2;
|
||||
x1 = *xp1;
|
||||
x2 = *yp1;
|
||||
*yp1++ = MULT16_32_Q15(*wp2, x2) - MULT16_32_Q15(*wp1, x1);
|
||||
*xp1-- = MULT16_32_Q15(*wp1, x2) + MULT16_32_Q15(*wp2, x1);
|
||||
wp1++;
|
||||
wp2--;
|
||||
}
|
||||
}
|
||||
RESTORE_STACK;
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
/* Copyright (c) 2014-2015 Xiph.Org Foundation
|
||||
Copyright (c) 2024 Arm Limited
|
||||
Written by Viswanath Puttagunta */
|
||||
/**
|
||||
@file celt_neon_intr.c
|
||||
@brief ARM Neon Intrinsic optimizations for celt
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <arm_neon.h>
|
||||
#include "../float_cast.h"
|
||||
#include "../mathops.h"
|
||||
#include "../pitch.h"
|
||||
#if defined(OPUS_CHECK_ASM)
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_FLOAT_API) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
|
||||
|
||||
void celt_float2int16_neon(const float * OPUS_RESTRICT in, short * OPUS_RESTRICT out, int cnt)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
#if defined(__ARM_NEON)
|
||||
const int BLOCK_SIZE = 16;
|
||||
const int blockedSize = cnt / BLOCK_SIZE * BLOCK_SIZE;
|
||||
|
||||
for (; i < blockedSize; i += BLOCK_SIZE)
|
||||
{
|
||||
float32x4_t orig_a = vld1q_f32(&in[i + 0]);
|
||||
float32x4_t orig_b = vld1q_f32(&in[i + 4]);
|
||||
float32x4_t orig_c = vld1q_f32(&in[i + 8]);
|
||||
float32x4_t orig_d = vld1q_f32(&in[i + 12]);
|
||||
|
||||
int16x4_t asShort_a = vqmovn_s32(vroundf(vmulq_n_f32(orig_a, CELT_SIG_SCALE)));
|
||||
int16x4_t asShort_b = vqmovn_s32(vroundf(vmulq_n_f32(orig_b, CELT_SIG_SCALE)));
|
||||
int16x4_t asShort_c = vqmovn_s32(vroundf(vmulq_n_f32(orig_c, CELT_SIG_SCALE)));
|
||||
int16x4_t asShort_d = vqmovn_s32(vroundf(vmulq_n_f32(orig_d, CELT_SIG_SCALE)));
|
||||
|
||||
vst1_s16(&out[i + 0], asShort_a);
|
||||
vst1_s16(&out[i + 4], asShort_b);
|
||||
vst1_s16(&out[i + 8], asShort_c);
|
||||
vst1_s16(&out[i + 12], asShort_d);
|
||||
# if defined(OPUS_CHECK_ASM)
|
||||
short out_c[BLOCK_SIZE];
|
||||
int j;
|
||||
for(j = 0; j < BLOCK_SIZE; j++)
|
||||
{
|
||||
out_c[j] = FLOAT2INT16(in[i + j]);
|
||||
celt_assert(abs((out_c[j] - out[i + j])) <= 1);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; i < cnt; i++)
|
||||
{
|
||||
out[i] = FLOAT2INT16(in[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int opus_limit2_checkwithin1_neon(float *samples, int cnt)
|
||||
{
|
||||
const float hardclipMin = -2.0f;
|
||||
const float hardclipMax = 2.0f;
|
||||
|
||||
int i = 0;
|
||||
int exceeding1 = 0;
|
||||
int nextIndex = 0;
|
||||
|
||||
#if defined(__ARM_NEON)
|
||||
const int BLOCK_SIZE = 16;
|
||||
const int blockedSize = cnt / BLOCK_SIZE * BLOCK_SIZE;
|
||||
|
||||
float32x4_t min_all_0 = vdupq_n_f32(0.0f);
|
||||
float32x4_t min_all_1 = vdupq_n_f32(0.0f);
|
||||
float32x4_t max_all_0 = vdupq_n_f32(0.0f);
|
||||
float32x4_t max_all_1 = vdupq_n_f32(0.0f);
|
||||
|
||||
float max, min;
|
||||
|
||||
for (i = 0; i < blockedSize; i += BLOCK_SIZE)
|
||||
{
|
||||
const float32x4_t orig_a = vld1q_f32(&samples[i + 0]);
|
||||
const float32x4_t orig_b = vld1q_f32(&samples[i + 4]);
|
||||
const float32x4_t orig_c = vld1q_f32(&samples[i + 8]);
|
||||
const float32x4_t orig_d = vld1q_f32(&samples[i + 12]);
|
||||
max_all_0 = vmaxq_f32(max_all_0, vmaxq_f32(orig_a, orig_b));
|
||||
max_all_1 = vmaxq_f32(max_all_1, vmaxq_f32(orig_c, orig_d));
|
||||
min_all_0 = vminq_f32(min_all_0, vminq_f32(orig_a, orig_b));
|
||||
min_all_1 = vminq_f32(min_all_1, vminq_f32(orig_c, orig_d));
|
||||
}
|
||||
|
||||
max = vmaxvf(vmaxq_f32(max_all_0, max_all_1));
|
||||
min = vminvf(vminq_f32(min_all_0, min_all_1));
|
||||
|
||||
if (min < hardclipMin || max > hardclipMax)
|
||||
{
|
||||
const float32x4_t hardclipMinReg = vdupq_n_f32(hardclipMin);
|
||||
const float32x4_t hardclipMaxReg = vdupq_n_f32(hardclipMax);
|
||||
for (i = 0; i < blockedSize; i += BLOCK_SIZE)
|
||||
{
|
||||
const float32x4_t orig_a = vld1q_f32(&samples[i + 0]);
|
||||
const float32x4_t orig_b = vld1q_f32(&samples[i + 4]);
|
||||
const float32x4_t orig_c = vld1q_f32(&samples[i + 8]);
|
||||
const float32x4_t orig_d = vld1q_f32(&samples[i + 12]);
|
||||
const float32x4_t clipped_a = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_a, hardclipMinReg));
|
||||
const float32x4_t clipped_b = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_b, hardclipMinReg));
|
||||
const float32x4_t clipped_c = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_c, hardclipMinReg));
|
||||
const float32x4_t clipped_d = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_d, hardclipMinReg));
|
||||
vst1q_f32(&samples[i + 0], clipped_a);
|
||||
vst1q_f32(&samples[i + 4], clipped_b);
|
||||
vst1q_f32(&samples[i + 8], clipped_c);
|
||||
vst1q_f32(&samples[i + 12], clipped_d);
|
||||
}
|
||||
}
|
||||
|
||||
nextIndex = blockedSize;
|
||||
exceeding1 |= max > 1.0f || min < -1.0f;
|
||||
|
||||
#endif
|
||||
|
||||
for (i = nextIndex; i < cnt; i++)
|
||||
{
|
||||
const float origVal = samples[i];
|
||||
float clippedVal = origVal;
|
||||
clippedVal = MAX16(hardclipMin, clippedVal);
|
||||
clippedVal = MIN16(hardclipMax, clippedVal);
|
||||
samples[i] = clippedVal;
|
||||
|
||||
exceeding1 |= origVal > 1.0f || origVal < -1.0f;
|
||||
}
|
||||
|
||||
return !exceeding1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(FIXED_POINT)
|
||||
#include <string.h>
|
||||
|
||||
void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len)
|
||||
{
|
||||
int j;
|
||||
int32x4_t a = vld1q_s32(sum);
|
||||
/* Load y[0...3] */
|
||||
/* This requires len>0 to always be valid (which we assert in the C code). */
|
||||
int16x4_t y0 = vld1_s16(y);
|
||||
y += 4;
|
||||
|
||||
/* This loop loads one y value more than we actually need.
|
||||
Therefore we have to stop as soon as there are 8 or fewer samples left
|
||||
(instead of 7), to avoid reading past the end of the array. */
|
||||
for (j = 0; j + 8 < len; j += 8)
|
||||
{
|
||||
/* Load x[0...7] */
|
||||
int16x8_t xx = vld1q_s16(x);
|
||||
int16x4_t x0 = vget_low_s16(xx);
|
||||
int16x4_t x4 = vget_high_s16(xx);
|
||||
/* Load y[4...11] */
|
||||
int16x8_t yy = vld1q_s16(y);
|
||||
int16x4_t y4 = vget_low_s16(yy);
|
||||
int16x4_t y8 = vget_high_s16(yy);
|
||||
int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0);
|
||||
int32x4_t a1 = vmlal_lane_s16(a0, y4, x4, 0);
|
||||
|
||||
int16x4_t y1 = vext_s16(y0, y4, 1);
|
||||
int16x4_t y5 = vext_s16(y4, y8, 1);
|
||||
int32x4_t a2 = vmlal_lane_s16(a1, y1, x0, 1);
|
||||
int32x4_t a3 = vmlal_lane_s16(a2, y5, x4, 1);
|
||||
|
||||
int16x4_t y2 = vext_s16(y0, y4, 2);
|
||||
int16x4_t y6 = vext_s16(y4, y8, 2);
|
||||
int32x4_t a4 = vmlal_lane_s16(a3, y2, x0, 2);
|
||||
int32x4_t a5 = vmlal_lane_s16(a4, y6, x4, 2);
|
||||
|
||||
int16x4_t y3 = vext_s16(y0, y4, 3);
|
||||
int16x4_t y7 = vext_s16(y4, y8, 3);
|
||||
int32x4_t a6 = vmlal_lane_s16(a5, y3, x0, 3);
|
||||
int32x4_t a7 = vmlal_lane_s16(a6, y7, x4, 3);
|
||||
|
||||
y0 = y8;
|
||||
a = a7;
|
||||
x += 8;
|
||||
y += 8;
|
||||
}
|
||||
if (j + 4 < len) {
|
||||
/* Load x[0...3] */
|
||||
int16x4_t x0 = vld1_s16(x);
|
||||
/* Load y[4...7] */
|
||||
int16x4_t y4 = vld1_s16(y);
|
||||
int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0);
|
||||
int16x4_t y1 = vext_s16(y0, y4, 1);
|
||||
int32x4_t a1 = vmlal_lane_s16(a0, y1, x0, 1);
|
||||
int16x4_t y2 = vext_s16(y0, y4, 2);
|
||||
int32x4_t a2 = vmlal_lane_s16(a1, y2, x0, 2);
|
||||
int16x4_t y3 = vext_s16(y0, y4, 3);
|
||||
int32x4_t a3 = vmlal_lane_s16(a2, y3, x0, 3);
|
||||
y0 = y4;
|
||||
a = a3;
|
||||
x += 4;
|
||||
y += 4;
|
||||
j += 4;
|
||||
}
|
||||
if (j + 2 < len) {
|
||||
/* Load x[0...1] */
|
||||
int16x4x2_t xx = vld2_dup_s16(x);
|
||||
int16x4_t x0 = xx.val[0];
|
||||
int16x4_t x1 = xx.val[1];
|
||||
/* Load y[4...5].
|
||||
We would like to use vld1_dup_s32(), but casting the pointer would
|
||||
break strict aliasing rules and potentially have alignment issues.
|
||||
Fortunately the compiler seems capable of translating this memcpy()
|
||||
and vdup_n_s32() into the equivalent vld1_dup_s32().*/
|
||||
int32_t yy;
|
||||
memcpy(&yy, y, sizeof(yy));
|
||||
int16x4_t y4 = vreinterpret_s16_s32(vdup_n_s32(yy));
|
||||
int32x4_t a0 = vmlal_s16(a, y0, x0);
|
||||
int16x4_t y1 = vext_s16(y0, y4, 1);
|
||||
/* Replace bottom copy of {y[5], y[4]} in y4 with {y[3], y[2]} from y0,
|
||||
using VSRI instead of VEXT, since it's a data-processing
|
||||
instruction. */
|
||||
y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
|
||||
vreinterpret_s64_s16(y0), 32));
|
||||
int32x4_t a1 = vmlal_s16(a0, y1, x1);
|
||||
a = a1;
|
||||
x += 2;
|
||||
y += 2;
|
||||
j += 2;
|
||||
}
|
||||
if (j + 1 < len) {
|
||||
/* Load next x. */
|
||||
int16x4_t x0 = vld1_dup_s16(x);
|
||||
int32x4_t a0 = vmlal_s16(a, y0, x0);
|
||||
/* Load last y. */
|
||||
int16x4_t y4 = vld1_dup_s16(y);
|
||||
y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
|
||||
vreinterpret_s64_s16(y0), 16));
|
||||
a = a0;
|
||||
x++;
|
||||
}
|
||||
/* Load last x. */
|
||||
int16x4_t x0 = vld1_dup_s16(x);
|
||||
int32x4_t a0 = vmlal_s16(a, y0, x0);
|
||||
vst1q_s32(sum, a0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if defined(__ARM_FEATURE_FMA) && defined(__ARM_ARCH_ISA_A64)
|
||||
/* If we can, force the compiler to use an FMA instruction rather than break
|
||||
* vmlaq_f32() into fmul/fadd. */
|
||||
#ifdef vmlaq_lane_f32
|
||||
#undef vmlaq_lane_f32
|
||||
#endif
|
||||
#define vmlaq_lane_f32(a,b,c,lane) vfmaq_lane_f32(a,b,c,lane)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Function: xcorr_kernel_neon_float
|
||||
* ---------------------------------
|
||||
* Computes 4 correlation values and stores them in sum[4]
|
||||
*/
|
||||
static void xcorr_kernel_neon_float(const float32_t *x, const float32_t *y,
|
||||
float32_t sum[4], int len) {
|
||||
float32x4_t YY[3];
|
||||
float32x4_t YEXT[3];
|
||||
float32x4_t XX[2];
|
||||
float32x2_t XX_2;
|
||||
float32x4_t SUMM;
|
||||
const float32_t *xi = x;
|
||||
const float32_t *yi = y;
|
||||
|
||||
celt_assert(len>0);
|
||||
|
||||
YY[0] = vld1q_f32(yi);
|
||||
SUMM = vdupq_n_f32(0);
|
||||
|
||||
/* Consume 8 elements in x vector and 12 elements in y
|
||||
* vector. However, the 12'th element never really gets
|
||||
* touched in this loop. So, if len == 8, then we only
|
||||
* must access y[0] to y[10]. y[11] must not be accessed
|
||||
* hence make sure len > 8 and not len >= 8
|
||||
*/
|
||||
while (len > 8) {
|
||||
yi += 4;
|
||||
YY[1] = vld1q_f32(yi);
|
||||
yi += 4;
|
||||
YY[2] = vld1q_f32(yi);
|
||||
|
||||
XX[0] = vld1q_f32(xi);
|
||||
xi += 4;
|
||||
XX[1] = vld1q_f32(xi);
|
||||
xi += 4;
|
||||
|
||||
SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0);
|
||||
YEXT[0] = vextq_f32(YY[0], YY[1], 1);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1);
|
||||
YEXT[1] = vextq_f32(YY[0], YY[1], 2);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0);
|
||||
YEXT[2] = vextq_f32(YY[0], YY[1], 3);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1);
|
||||
|
||||
SUMM = vmlaq_lane_f32(SUMM, YY[1], vget_low_f32(XX[1]), 0);
|
||||
YEXT[0] = vextq_f32(YY[1], YY[2], 1);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[1]), 1);
|
||||
YEXT[1] = vextq_f32(YY[1], YY[2], 2);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[1]), 0);
|
||||
YEXT[2] = vextq_f32(YY[1], YY[2], 3);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[1]), 1);
|
||||
|
||||
YY[0] = YY[2];
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* Consume 4 elements in x vector and 8 elements in y
|
||||
* vector. However, the 8'th element in y never really gets
|
||||
* touched in this loop. So, if len == 4, then we only
|
||||
* must access y[0] to y[6]. y[7] must not be accessed
|
||||
* hence make sure len>4 and not len>=4
|
||||
*/
|
||||
if (len > 4) {
|
||||
yi += 4;
|
||||
YY[1] = vld1q_f32(yi);
|
||||
|
||||
XX[0] = vld1q_f32(xi);
|
||||
xi += 4;
|
||||
|
||||
SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0);
|
||||
YEXT[0] = vextq_f32(YY[0], YY[1], 1);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1);
|
||||
YEXT[1] = vextq_f32(YY[0], YY[1], 2);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0);
|
||||
YEXT[2] = vextq_f32(YY[0], YY[1], 3);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1);
|
||||
|
||||
YY[0] = YY[1];
|
||||
len -= 4;
|
||||
}
|
||||
|
||||
while (--len > 0) {
|
||||
XX_2 = vld1_dup_f32(xi++);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0);
|
||||
YY[0]= vld1q_f32(++yi);
|
||||
}
|
||||
|
||||
XX_2 = vld1_dup_f32(xi);
|
||||
SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0);
|
||||
|
||||
vst1q_f32(sum, SUMM);
|
||||
}
|
||||
|
||||
void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y,
|
||||
opus_val32 *xcorr, int len, int max_pitch, int arch) {
|
||||
int i;
|
||||
(void)arch;
|
||||
celt_assert(max_pitch > 0);
|
||||
celt_sig_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0);
|
||||
|
||||
for (i = 0; i < (max_pitch-3); i += 4) {
|
||||
xcorr_kernel_neon_float((const float32_t *)_x, (const float32_t *)_y+i,
|
||||
(float32_t *)xcorr+i, len);
|
||||
}
|
||||
|
||||
/* In case max_pitch isn't a multiple of 4, do non-unrolled version. */
|
||||
for (; i < max_pitch; i++) {
|
||||
xcorr[i] = celt_inner_prod_neon(_x, _y+i, len);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,551 +0,0 @@
|
||||
; Copyright (c) 2007-2008 CSIRO
|
||||
; Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
; Copyright (c) 2013 Parrot
|
||||
; Written by Aurélien Zanelli
|
||||
;
|
||||
; Redistribution and use in source and binary forms, with or without
|
||||
; modification, are permitted provided that the following conditions
|
||||
; are met:
|
||||
;
|
||||
; - Redistributions of source code must retain the above copyright
|
||||
; notice, this list of conditions and the following disclaimer.
|
||||
;
|
||||
; - Redistributions in binary form must reproduce the above copyright
|
||||
; notice, this list of conditions and the following disclaimer in the
|
||||
; documentation and/or other materials provided with the distribution.
|
||||
;
|
||||
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
; ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
; OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
AREA |.text|, CODE, READONLY
|
||||
|
||||
GET celt/arm/armopts.s
|
||||
|
||||
IF OPUS_ARM_MAY_HAVE_EDSP
|
||||
EXPORT celt_pitch_xcorr_edsp
|
||||
ENDIF
|
||||
|
||||
IF OPUS_ARM_MAY_HAVE_NEON
|
||||
EXPORT celt_pitch_xcorr_neon
|
||||
ENDIF
|
||||
|
||||
IF OPUS_ARM_MAY_HAVE_NEON
|
||||
|
||||
; Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3
|
||||
xcorr_kernel_neon PROC
|
||||
xcorr_kernel_neon_start
|
||||
; input:
|
||||
; r3 = int len
|
||||
; r4 = opus_val16 *x
|
||||
; r5 = opus_val16 *y
|
||||
; q0 = opus_val32 sum[4]
|
||||
; output:
|
||||
; q0 = opus_val32 sum[4]
|
||||
; preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15
|
||||
; internal usage:
|
||||
; r12 = int j
|
||||
; d3 = y_3|y_2|y_1|y_0
|
||||
; q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4
|
||||
; q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0
|
||||
; q8 = scratch
|
||||
;
|
||||
; Load y[0...3]
|
||||
; This requires len>0 to always be valid (which we assert in the C code).
|
||||
VLD1.16 {d5}, [r5]!
|
||||
SUBS r12, r3, #8
|
||||
BLE xcorr_kernel_neon_process4
|
||||
; Process 8 samples at a time.
|
||||
; This loop loads one y value more than we actually need. Therefore we have to
|
||||
; stop as soon as there are 8 or fewer samples left (instead of 7), to avoid
|
||||
; reading past the end of the array.
|
||||
xcorr_kernel_neon_process8
|
||||
; This loop has 19 total instructions (10 cycles to issue, minimum), with
|
||||
; - 2 cycles of ARM insrtuctions,
|
||||
; - 10 cycles of load/store/byte permute instructions, and
|
||||
; - 9 cycles of data processing instructions.
|
||||
; On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the
|
||||
; latter two categories, meaning the whole loop should run in 10 cycles per
|
||||
; iteration, barring cache misses.
|
||||
;
|
||||
; Load x[0...7]
|
||||
VLD1.16 {d6, d7}, [r4]!
|
||||
; Unlike VMOV, VAND is a data processsing instruction (and doesn't get
|
||||
; assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1.
|
||||
VAND d3, d5, d5
|
||||
SUBS r12, r12, #8
|
||||
; Load y[4...11]
|
||||
VLD1.16 {d4, d5}, [r5]!
|
||||
VMLAL.S16 q0, d3, d6[0]
|
||||
VEXT.16 d16, d3, d4, #1
|
||||
VMLAL.S16 q0, d4, d7[0]
|
||||
VEXT.16 d17, d4, d5, #1
|
||||
VMLAL.S16 q0, d16, d6[1]
|
||||
VEXT.16 d16, d3, d4, #2
|
||||
VMLAL.S16 q0, d17, d7[1]
|
||||
VEXT.16 d17, d4, d5, #2
|
||||
VMLAL.S16 q0, d16, d6[2]
|
||||
VEXT.16 d16, d3, d4, #3
|
||||
VMLAL.S16 q0, d17, d7[2]
|
||||
VEXT.16 d17, d4, d5, #3
|
||||
VMLAL.S16 q0, d16, d6[3]
|
||||
VMLAL.S16 q0, d17, d7[3]
|
||||
BGT xcorr_kernel_neon_process8
|
||||
; Process 4 samples here if we have > 4 left (still reading one extra y value).
|
||||
xcorr_kernel_neon_process4
|
||||
ADDS r12, r12, #4
|
||||
BLE xcorr_kernel_neon_process2
|
||||
; Load x[0...3]
|
||||
VLD1.16 d6, [r4]!
|
||||
; Use VAND since it's a data processing instruction again.
|
||||
VAND d4, d5, d5
|
||||
SUB r12, r12, #4
|
||||
; Load y[4...7]
|
||||
VLD1.16 d5, [r5]!
|
||||
VMLAL.S16 q0, d4, d6[0]
|
||||
VEXT.16 d16, d4, d5, #1
|
||||
VMLAL.S16 q0, d16, d6[1]
|
||||
VEXT.16 d16, d4, d5, #2
|
||||
VMLAL.S16 q0, d16, d6[2]
|
||||
VEXT.16 d16, d4, d5, #3
|
||||
VMLAL.S16 q0, d16, d6[3]
|
||||
; Process 2 samples here if we have > 2 left (still reading one extra y value).
|
||||
xcorr_kernel_neon_process2
|
||||
ADDS r12, r12, #2
|
||||
BLE xcorr_kernel_neon_process1
|
||||
; Load x[0...1]
|
||||
VLD2.16 {d6[],d7[]}, [r4]!
|
||||
; Use VAND since it's a data processing instruction again.
|
||||
VAND d4, d5, d5
|
||||
SUB r12, r12, #2
|
||||
; Load y[4...5]
|
||||
VLD1.32 {d5[]}, [r5]!
|
||||
VMLAL.S16 q0, d4, d6
|
||||
VEXT.16 d16, d4, d5, #1
|
||||
; Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI
|
||||
; instead of VEXT, since it's a data-processing instruction.
|
||||
VSRI.64 d5, d4, #32
|
||||
VMLAL.S16 q0, d16, d7
|
||||
; Process 1 sample using the extra y value we loaded above.
|
||||
xcorr_kernel_neon_process1
|
||||
; Load next *x
|
||||
VLD1.16 {d6[]}, [r4]!
|
||||
ADDS r12, r12, #1
|
||||
; y[0...3] are left in d5 from prior iteration(s) (if any)
|
||||
VMLAL.S16 q0, d5, d6
|
||||
MOVLE pc, lr
|
||||
; Now process 1 last sample, not reading ahead.
|
||||
; Load last *y
|
||||
VLD1.16 {d4[]}, [r5]!
|
||||
VSRI.64 d4, d5, #16
|
||||
; Load last *x
|
||||
VLD1.16 {d6[]}, [r4]!
|
||||
VMLAL.S16 q0, d4, d6
|
||||
MOV pc, lr
|
||||
ENDP
|
||||
|
||||
; opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y,
|
||||
; opus_val32 *xcorr, int len, int max_pitch, int arch)
|
||||
celt_pitch_xcorr_neon PROC
|
||||
; input:
|
||||
; r0 = opus_val16 *_x
|
||||
; r1 = opus_val16 *_y
|
||||
; r2 = opus_val32 *xcorr
|
||||
; r3 = int len
|
||||
; output:
|
||||
; r0 = int maxcorr
|
||||
; internal usage:
|
||||
; r4 = opus_val16 *x (for xcorr_kernel_neon())
|
||||
; r5 = opus_val16 *y (for xcorr_kernel_neon())
|
||||
; r6 = int max_pitch
|
||||
; r12 = int j
|
||||
; q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon())
|
||||
; ignored:
|
||||
; int arch
|
||||
STMFD sp!, {r4-r6, lr}
|
||||
LDR r6, [sp, #16]
|
||||
VMOV.S32 q15, #1
|
||||
; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
|
||||
SUBS r6, r6, #4
|
||||
BLT celt_pitch_xcorr_neon_process4_done
|
||||
celt_pitch_xcorr_neon_process4
|
||||
; xcorr_kernel_neon parameters:
|
||||
; r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0}
|
||||
MOV r4, r0
|
||||
MOV r5, r1
|
||||
VEOR q0, q0, q0
|
||||
; xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3.
|
||||
; So we don't save/restore any other registers.
|
||||
BL xcorr_kernel_neon_start
|
||||
SUBS r6, r6, #4
|
||||
VST1.32 {q0}, [r2]!
|
||||
; _y += 4
|
||||
ADD r1, r1, #8
|
||||
VMAX.S32 q15, q15, q0
|
||||
; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
|
||||
BGE celt_pitch_xcorr_neon_process4
|
||||
; We have less than 4 sums left to compute.
|
||||
celt_pitch_xcorr_neon_process4_done
|
||||
ADDS r6, r6, #4
|
||||
; Reduce maxcorr to a single value
|
||||
VMAX.S32 d30, d30, d31
|
||||
VPMAX.S32 d30, d30, d30
|
||||
; if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done
|
||||
BLE celt_pitch_xcorr_neon_done
|
||||
; Now compute each remaining sum one at a time.
|
||||
celt_pitch_xcorr_neon_process_remaining
|
||||
MOV r4, r0
|
||||
MOV r5, r1
|
||||
VMOV.I32 q0, #0
|
||||
SUBS r12, r3, #8
|
||||
BLT celt_pitch_xcorr_neon_process_remaining4
|
||||
; Sum terms 8 at a time.
|
||||
celt_pitch_xcorr_neon_process_remaining_loop8
|
||||
; Load x[0...7]
|
||||
VLD1.16 {q1}, [r4]!
|
||||
; Load y[0...7]
|
||||
VLD1.16 {q2}, [r5]!
|
||||
SUBS r12, r12, #8
|
||||
VMLAL.S16 q0, d4, d2
|
||||
VMLAL.S16 q0, d5, d3
|
||||
BGE celt_pitch_xcorr_neon_process_remaining_loop8
|
||||
; Sum terms 4 at a time.
|
||||
celt_pitch_xcorr_neon_process_remaining4
|
||||
ADDS r12, r12, #4
|
||||
BLT celt_pitch_xcorr_neon_process_remaining4_done
|
||||
; Load x[0...3]
|
||||
VLD1.16 {d2}, [r4]!
|
||||
; Load y[0...3]
|
||||
VLD1.16 {d3}, [r5]!
|
||||
SUB r12, r12, #4
|
||||
VMLAL.S16 q0, d3, d2
|
||||
celt_pitch_xcorr_neon_process_remaining4_done
|
||||
; Reduce the sum to a single value.
|
||||
VADD.S32 d0, d0, d1
|
||||
VPADDL.S32 d0, d0
|
||||
ADDS r12, r12, #4
|
||||
BLE celt_pitch_xcorr_neon_process_remaining_loop_done
|
||||
; Sum terms 1 at a time.
|
||||
celt_pitch_xcorr_neon_process_remaining_loop1
|
||||
VLD1.16 {d2[]}, [r4]!
|
||||
VLD1.16 {d3[]}, [r5]!
|
||||
SUBS r12, r12, #1
|
||||
VMLAL.S16 q0, d2, d3
|
||||
BGT celt_pitch_xcorr_neon_process_remaining_loop1
|
||||
celt_pitch_xcorr_neon_process_remaining_loop_done
|
||||
VST1.32 {d0[0]}, [r2]!
|
||||
VMAX.S32 d30, d30, d0
|
||||
SUBS r6, r6, #1
|
||||
; _y++
|
||||
ADD r1, r1, #2
|
||||
; if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining
|
||||
BGT celt_pitch_xcorr_neon_process_remaining
|
||||
celt_pitch_xcorr_neon_done
|
||||
VMOV.32 r0, d30[0]
|
||||
LDMFD sp!, {r4-r6, pc}
|
||||
ENDP
|
||||
|
||||
ENDIF
|
||||
|
||||
IF OPUS_ARM_MAY_HAVE_EDSP
|
||||
|
||||
; This will get used on ARMv7 devices without NEON, so it has been optimized
|
||||
; to take advantage of dual-issuing where possible.
|
||||
xcorr_kernel_edsp PROC
|
||||
xcorr_kernel_edsp_start
|
||||
; input:
|
||||
; r3 = int len
|
||||
; r4 = opus_val16 *_x (must be 32-bit aligned)
|
||||
; r5 = opus_val16 *_y (must be 32-bit aligned)
|
||||
; r6...r9 = opus_val32 sum[4]
|
||||
; output:
|
||||
; r6...r9 = opus_val32 sum[4]
|
||||
; preserved: r0-r5
|
||||
; internal usage
|
||||
; r2 = int j
|
||||
; r12,r14 = opus_val16 x[4]
|
||||
; r10,r11 = opus_val16 y[4]
|
||||
STMFD sp!, {r2,r4,r5,lr}
|
||||
LDR r10, [r5], #4 ; Load y[0...1]
|
||||
SUBS r2, r3, #4 ; j = len-4
|
||||
LDR r11, [r5], #4 ; Load y[2...3]
|
||||
BLE xcorr_kernel_edsp_process4_done
|
||||
LDR r12, [r4], #4 ; Load x[0...1]
|
||||
; Stall
|
||||
xcorr_kernel_edsp_process4
|
||||
; The multiplies must issue from pipeline 0, and can't dual-issue with each
|
||||
; other. Every other instruction here dual-issues with a multiply, and is
|
||||
; thus "free". There should be no stalls in the body of the loop.
|
||||
SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_0,y_0)
|
||||
LDR r14, [r4], #4 ; Load x[2...3]
|
||||
SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x_0,y_1)
|
||||
SUBS r2, r2, #4 ; j-=4
|
||||
SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_0,y_2)
|
||||
SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x_0,y_3)
|
||||
SMLATT r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_1,y_1)
|
||||
LDR r10, [r5], #4 ; Load y[4...5]
|
||||
SMLATB r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],x_1,y_2)
|
||||
SMLATT r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_1,y_3)
|
||||
SMLATB r9, r12, r10, r9 ; sum[3] = MAC16_16(sum[3],x_1,y_4)
|
||||
LDRGT r12, [r4], #4 ; Load x[0...1]
|
||||
SMLABB r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_2,y_2)
|
||||
SMLABT r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x_2,y_3)
|
||||
SMLABB r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_2,y_4)
|
||||
SMLABT r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x_2,y_5)
|
||||
SMLATT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_3,y_3)
|
||||
LDR r11, [r5], #4 ; Load y[6...7]
|
||||
SMLATB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],x_3,y_4)
|
||||
SMLATT r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_3,y_5)
|
||||
SMLATB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],x_3,y_6)
|
||||
BGT xcorr_kernel_edsp_process4
|
||||
xcorr_kernel_edsp_process4_done
|
||||
ADDS r2, r2, #4
|
||||
BLE xcorr_kernel_edsp_done
|
||||
LDRH r12, [r4], #2 ; r12 = *x++
|
||||
SUBS r2, r2, #1 ; j--
|
||||
; Stall
|
||||
SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_0)
|
||||
LDRHGT r14, [r4], #2 ; r14 = *x++
|
||||
SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x,y_1)
|
||||
SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_2)
|
||||
SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x,y_3)
|
||||
BLE xcorr_kernel_edsp_done
|
||||
SMLABT r6, r14, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_1)
|
||||
SUBS r2, r2, #1 ; j--
|
||||
SMLABB r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x,y_2)
|
||||
LDRH r10, [r5], #2 ; r10 = y_4 = *y++
|
||||
SMLABT r8, r14, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_3)
|
||||
LDRHGT r12, [r4], #2 ; r12 = *x++
|
||||
SMLABB r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x,y_4)
|
||||
BLE xcorr_kernel_edsp_done
|
||||
SMLABB r6, r12, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_2)
|
||||
CMP r2, #1 ; j--
|
||||
SMLABT r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_3)
|
||||
LDRH r2, [r5], #2 ; r2 = y_5 = *y++
|
||||
SMLABB r8, r12, r10, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_4)
|
||||
LDRHGT r14, [r4] ; r14 = *x
|
||||
SMLABB r9, r12, r2, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_5)
|
||||
BLE xcorr_kernel_edsp_done
|
||||
SMLABT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_3)
|
||||
LDRH r11, [r5] ; r11 = y_6 = *y
|
||||
SMLABB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_4)
|
||||
SMLABB r8, r14, r2, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_5)
|
||||
SMLABB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_6)
|
||||
xcorr_kernel_edsp_done
|
||||
LDMFD sp!, {r2,r4,r5,pc}
|
||||
ENDP
|
||||
|
||||
celt_pitch_xcorr_edsp PROC
|
||||
; input:
|
||||
; r0 = opus_val16 *_x (must be 32-bit aligned)
|
||||
; r1 = opus_val16 *_y (only needs to be 16-bit aligned)
|
||||
; r2 = opus_val32 *xcorr
|
||||
; r3 = int len
|
||||
; output:
|
||||
; r0 = maxcorr
|
||||
; internal usage
|
||||
; r4 = opus_val16 *x
|
||||
; r5 = opus_val16 *y
|
||||
; r6 = opus_val32 sum0
|
||||
; r7 = opus_val32 sum1
|
||||
; r8 = opus_val32 sum2
|
||||
; r9 = opus_val32 sum3
|
||||
; r1 = int max_pitch
|
||||
; r12 = int j
|
||||
; ignored:
|
||||
; int arch
|
||||
STMFD sp!, {r4-r11, lr}
|
||||
MOV r5, r1
|
||||
LDR r1, [sp, #36]
|
||||
MOV r4, r0
|
||||
TST r5, #3
|
||||
; maxcorr = 1
|
||||
MOV r0, #1
|
||||
BEQ celt_pitch_xcorr_edsp_process1u_done
|
||||
; Compute one sum at the start to make y 32-bit aligned.
|
||||
SUBS r12, r3, #4
|
||||
; r14 = sum = 0
|
||||
MOV r14, #0
|
||||
LDRH r8, [r5], #2
|
||||
BLE celt_pitch_xcorr_edsp_process1u_loop4_done
|
||||
LDR r6, [r4], #4
|
||||
MOV r8, r8, LSL #16
|
||||
celt_pitch_xcorr_edsp_process1u_loop4
|
||||
LDR r9, [r5], #4
|
||||
SMLABT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
|
||||
LDR r7, [r4], #4
|
||||
SMLATB r14, r6, r9, r14 ; sum = MAC16_16(sum, x_1, y_1)
|
||||
LDR r8, [r5], #4
|
||||
SMLABT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2)
|
||||
SUBS r12, r12, #4 ; j-=4
|
||||
SMLATB r14, r7, r8, r14 ; sum = MAC16_16(sum, x_3, y_3)
|
||||
LDRGT r6, [r4], #4
|
||||
BGT celt_pitch_xcorr_edsp_process1u_loop4
|
||||
MOV r8, r8, LSR #16
|
||||
celt_pitch_xcorr_edsp_process1u_loop4_done
|
||||
ADDS r12, r12, #4
|
||||
celt_pitch_xcorr_edsp_process1u_loop1
|
||||
LDRHGE r6, [r4], #2
|
||||
; Stall
|
||||
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y)
|
||||
SUBSGE r12, r12, #1
|
||||
LDRHGT r8, [r5], #2
|
||||
BGT celt_pitch_xcorr_edsp_process1u_loop1
|
||||
; Restore _x
|
||||
SUB r4, r4, r3, LSL #1
|
||||
; Restore and advance _y
|
||||
SUB r5, r5, r3, LSL #1
|
||||
; maxcorr = max(maxcorr, sum)
|
||||
CMP r0, r14
|
||||
ADD r5, r5, #2
|
||||
MOVLT r0, r14
|
||||
SUBS r1, r1, #1
|
||||
; xcorr[i] = sum
|
||||
STR r14, [r2], #4
|
||||
BLE celt_pitch_xcorr_edsp_done
|
||||
celt_pitch_xcorr_edsp_process1u_done
|
||||
; if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2
|
||||
SUBS r1, r1, #4
|
||||
BLT celt_pitch_xcorr_edsp_process2
|
||||
celt_pitch_xcorr_edsp_process4
|
||||
; xcorr_kernel_edsp parameters:
|
||||
; r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0}
|
||||
MOV r6, #0
|
||||
MOV r7, #0
|
||||
MOV r8, #0
|
||||
MOV r9, #0
|
||||
BL xcorr_kernel_edsp_start ; xcorr_kernel_edsp(_x, _y+i, xcorr+i, len)
|
||||
; maxcorr = max(maxcorr, sum0, sum1, sum2, sum3)
|
||||
CMP r0, r6
|
||||
; _y+=4
|
||||
ADD r5, r5, #8
|
||||
MOVLT r0, r6
|
||||
CMP r0, r7
|
||||
MOVLT r0, r7
|
||||
CMP r0, r8
|
||||
MOVLT r0, r8
|
||||
CMP r0, r9
|
||||
MOVLT r0, r9
|
||||
STMIA r2!, {r6-r9}
|
||||
SUBS r1, r1, #4
|
||||
BGE celt_pitch_xcorr_edsp_process4
|
||||
celt_pitch_xcorr_edsp_process2
|
||||
ADDS r1, r1, #2
|
||||
BLT celt_pitch_xcorr_edsp_process1a
|
||||
SUBS r12, r3, #4
|
||||
; {r10, r11} = {sum0, sum1} = {0, 0}
|
||||
MOV r10, #0
|
||||
MOV r11, #0
|
||||
LDR r8, [r5], #4
|
||||
BLE celt_pitch_xcorr_edsp_process2_loop_done
|
||||
LDR r6, [r4], #4
|
||||
LDR r9, [r5], #4
|
||||
celt_pitch_xcorr_edsp_process2_loop4
|
||||
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
|
||||
LDR r7, [r4], #4
|
||||
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
|
||||
SUBS r12, r12, #4 ; j-=4
|
||||
SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1)
|
||||
LDR r8, [r5], #4
|
||||
SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2)
|
||||
LDRGT r6, [r4], #4
|
||||
SMLABB r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_2, y_2)
|
||||
SMLABT r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_2, y_3)
|
||||
SMLATT r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_3, y_3)
|
||||
LDRGT r9, [r5], #4
|
||||
SMLATB r11, r7, r8, r11 ; sum1 = MAC16_16(sum1, x_3, y_4)
|
||||
BGT celt_pitch_xcorr_edsp_process2_loop4
|
||||
celt_pitch_xcorr_edsp_process2_loop_done
|
||||
ADDS r12, r12, #2
|
||||
BLE celt_pitch_xcorr_edsp_process2_1
|
||||
LDR r6, [r4], #4
|
||||
; Stall
|
||||
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
|
||||
LDR r9, [r5], #4
|
||||
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
|
||||
SUB r12, r12, #2
|
||||
SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1)
|
||||
MOV r8, r9
|
||||
SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2)
|
||||
celt_pitch_xcorr_edsp_process2_1
|
||||
LDRH r6, [r4], #2
|
||||
ADDS r12, r12, #1
|
||||
; Stall
|
||||
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
|
||||
LDRHGT r7, [r4], #2
|
||||
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
|
||||
BLE celt_pitch_xcorr_edsp_process2_done
|
||||
LDRH r9, [r5], #2
|
||||
SMLABT r10, r7, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_1)
|
||||
SMLABB r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_0, y_2)
|
||||
celt_pitch_xcorr_edsp_process2_done
|
||||
; Restore _x
|
||||
SUB r4, r4, r3, LSL #1
|
||||
; Restore and advance _y
|
||||
SUB r5, r5, r3, LSL #1
|
||||
; maxcorr = max(maxcorr, sum0)
|
||||
CMP r0, r10
|
||||
ADD r5, r5, #2
|
||||
MOVLT r0, r10
|
||||
SUB r1, r1, #2
|
||||
; maxcorr = max(maxcorr, sum1)
|
||||
CMP r0, r11
|
||||
; xcorr[i] = sum
|
||||
STR r10, [r2], #4
|
||||
MOVLT r0, r11
|
||||
STR r11, [r2], #4
|
||||
celt_pitch_xcorr_edsp_process1a
|
||||
ADDS r1, r1, #1
|
||||
BLT celt_pitch_xcorr_edsp_done
|
||||
SUBS r12, r3, #4
|
||||
; r14 = sum = 0
|
||||
MOV r14, #0
|
||||
BLT celt_pitch_xcorr_edsp_process1a_loop_done
|
||||
LDR r6, [r4], #4
|
||||
LDR r8, [r5], #4
|
||||
LDR r7, [r4], #4
|
||||
LDR r9, [r5], #4
|
||||
celt_pitch_xcorr_edsp_process1a_loop4
|
||||
SMLABB r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
|
||||
SUBS r12, r12, #4 ; j-=4
|
||||
SMLATT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1)
|
||||
LDRGE r6, [r4], #4
|
||||
SMLABB r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2)
|
||||
LDRGE r8, [r5], #4
|
||||
SMLATT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_3, y_3)
|
||||
LDRGE r7, [r4], #4
|
||||
LDRGE r9, [r5], #4
|
||||
BGE celt_pitch_xcorr_edsp_process1a_loop4
|
||||
celt_pitch_xcorr_edsp_process1a_loop_done
|
||||
ADDS r12, r12, #2
|
||||
LDRGE r6, [r4], #4
|
||||
LDRGE r8, [r5], #4
|
||||
; Stall
|
||||
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
|
||||
SUBGE r12, r12, #2
|
||||
SMLATTGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1)
|
||||
ADDS r12, r12, #1
|
||||
LDRHGE r6, [r4], #2
|
||||
LDRHGE r8, [r5], #2
|
||||
; Stall
|
||||
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y)
|
||||
; maxcorr = max(maxcorr, sum)
|
||||
CMP r0, r14
|
||||
; xcorr[i] = sum
|
||||
STR r14, [r2], #4
|
||||
MOVLT r0, r14
|
||||
celt_pitch_xcorr_edsp_done
|
||||
LDMFD sp!, {r4-r11, pc}
|
||||
ENDP
|
||||
|
||||
ENDIF
|
||||
|
||||
END
|
||||
@@ -1,71 +0,0 @@
|
||||
/* Copyright (c) 2015 Xiph.Org Foundation
|
||||
Written by Viswanath Puttagunta */
|
||||
/**
|
||||
@file fft_arm.h
|
||||
@brief ARM Neon Intrinsic optimizations for fft using NE10 library
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#if !defined(FFT_ARM_H)
|
||||
#define FFT_ARM_H
|
||||
|
||||
#include "kiss_fft.h"
|
||||
|
||||
#if defined(HAVE_ARM_NE10)
|
||||
|
||||
int opus_fft_alloc_arm_neon(kiss_fft_state *st);
|
||||
void opus_fft_free_arm_neon(kiss_fft_state *st);
|
||||
|
||||
void opus_fft_neon(const kiss_fft_state *st,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout);
|
||||
|
||||
void opus_ifft_neon(const kiss_fft_state *st,
|
||||
const kiss_fft_cpx *fin,
|
||||
kiss_fft_cpx *fout);
|
||||
|
||||
#if !defined(OPUS_HAVE_RTCD)
|
||||
#define OVERRIDE_OPUS_FFT (1)
|
||||
|
||||
#define opus_fft_alloc_arch(_st, arch) \
|
||||
((void)(arch), opus_fft_alloc_arm_neon(_st))
|
||||
|
||||
#define opus_fft_free_arch(_st, arch) \
|
||||
((void)(arch), opus_fft_free_arm_neon(_st))
|
||||
|
||||
#define opus_fft(_st, _fin, _fout, arch) \
|
||||
((void)(arch), opus_fft_neon(_st, _fin, _fout))
|
||||
|
||||
#define opus_ifft(_st, _fin, _fout, arch) \
|
||||
((void)(arch), opus_ifft_neon(_st, _fin, _fout))
|
||||
|
||||
#endif /* OPUS_HAVE_RTCD */
|
||||
|
||||
#endif /* HAVE_ARM_NE10 */
|
||||
|
||||
#endif
|
||||
@@ -1,35 +0,0 @@
|
||||
/* Copyright (C) 2015 Vidyo */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FIXED_ARM64_H
|
||||
#define FIXED_ARM64_H
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#undef SIG2WORD16
|
||||
#define SIG2WORD16(x) (vqmovns_s32(PSHR32((x), SIG_SHIFT)))
|
||||
|
||||
#endif
|
||||
@@ -1,80 +0,0 @@
|
||||
/* Copyright (C) 2013 Xiph.Org Foundation and contributors */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FIXED_ARMv4_H
|
||||
#define FIXED_ARMv4_H
|
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */
|
||||
#undef MULT16_32_Q16
|
||||
static OPUS_INLINE opus_val32 MULT16_32_Q16_armv4(opus_val16 a, opus_val32 b)
|
||||
{
|
||||
unsigned rd_lo;
|
||||
int rd_hi;
|
||||
__asm__(
|
||||
"#MULT16_32_Q16\n\t"
|
||||
"smull %0, %1, %2, %3\n\t"
|
||||
: "=&r"(rd_lo), "=&r"(rd_hi)
|
||||
: "%r"(b),"r"(SHL32(a,16))
|
||||
);
|
||||
return rd_hi;
|
||||
}
|
||||
#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv4(a, b))
|
||||
|
||||
|
||||
/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */
|
||||
#undef MULT16_32_Q15
|
||||
static OPUS_INLINE opus_val32 MULT16_32_Q15_armv4(opus_val16 a, opus_val32 b)
|
||||
{
|
||||
unsigned rd_lo;
|
||||
int rd_hi;
|
||||
__asm__(
|
||||
"#MULT16_32_Q15\n\t"
|
||||
"smull %0, %1, %2, %3\n\t"
|
||||
: "=&r"(rd_lo), "=&r"(rd_hi)
|
||||
: "%r"(b), "r"(SHL32(a,16))
|
||||
);
|
||||
/*We intentionally don't OR in the high bit of rd_lo for speed.*/
|
||||
return SHL32(rd_hi,1);
|
||||
}
|
||||
#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv4(a, b))
|
||||
|
||||
|
||||
/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add.
|
||||
b must fit in 31 bits.
|
||||
Result fits in 32 bits. */
|
||||
#undef MAC16_32_Q15
|
||||
#define MAC16_32_Q15(c, a, b) ADD32(c, MULT16_32_Q15(a, b))
|
||||
|
||||
/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add.
|
||||
Result fits in 32 bits. */
|
||||
#undef MAC16_32_Q16
|
||||
#define MAC16_32_Q16(c, a, b) ADD32(c, MULT16_32_Q16(a, b))
|
||||
|
||||
/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */
|
||||
#undef MULT32_32_Q31
|
||||
#define MULT32_32_Q31(a,b) (opus_val32)((((opus_int64)(a)) * ((opus_int64)(b)))>>31)
|
||||
|
||||
#endif
|
||||
@@ -1,151 +0,0 @@
|
||||
/* Copyright (C) 2007-2009 Xiph.Org Foundation
|
||||
Copyright (C) 2003-2008 Jean-Marc Valin
|
||||
Copyright (C) 2007-2008 CSIRO
|
||||
Copyright (C) 2013 Parrot */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FIXED_ARMv5E_H
|
||||
#define FIXED_ARMv5E_H
|
||||
|
||||
#include "fixed_armv4.h"
|
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */
|
||||
#undef MULT16_32_Q16
|
||||
static OPUS_INLINE opus_val32 MULT16_32_Q16_armv5e(opus_val16 a, opus_val32 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MULT16_32_Q16\n\t"
|
||||
"smulwb %0, %1, %2\n\t"
|
||||
: "=r"(res)
|
||||
: "r"(b),"r"(a)
|
||||
);
|
||||
return res;
|
||||
}
|
||||
#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv5e(a, b))
|
||||
|
||||
|
||||
/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */
|
||||
#undef MULT16_32_Q15
|
||||
static OPUS_INLINE opus_val32 MULT16_32_Q15_armv5e(opus_val16 a, opus_val32 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MULT16_32_Q15\n\t"
|
||||
"smulwb %0, %1, %2\n\t"
|
||||
: "=r"(res)
|
||||
: "r"(b), "r"(a)
|
||||
);
|
||||
return SHL32(res,1);
|
||||
}
|
||||
#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv5e(a, b))
|
||||
|
||||
|
||||
/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add.
|
||||
b must fit in 31 bits.
|
||||
Result fits in 32 bits. */
|
||||
#undef MAC16_32_Q15
|
||||
static OPUS_INLINE opus_val32 MAC16_32_Q15_armv5e(opus_val32 c, opus_val16 a,
|
||||
opus_val32 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MAC16_32_Q15\n\t"
|
||||
"smlawb %0, %1, %2, %3;\n"
|
||||
: "=r"(res)
|
||||
: "r"(SHL32(b,1)), "r"(a), "r"(c)
|
||||
);
|
||||
return res;
|
||||
}
|
||||
#define MAC16_32_Q15(c, a, b) (MAC16_32_Q15_armv5e(c, a, b))
|
||||
|
||||
/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add.
|
||||
Result fits in 32 bits. */
|
||||
#undef MAC16_32_Q16
|
||||
static OPUS_INLINE opus_val32 MAC16_32_Q16_armv5e(opus_val32 c, opus_val16 a,
|
||||
opus_val32 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MAC16_32_Q16\n\t"
|
||||
"smlawb %0, %1, %2, %3;\n"
|
||||
: "=r"(res)
|
||||
: "r"(b), "r"(a), "r"(c)
|
||||
);
|
||||
return res;
|
||||
}
|
||||
#define MAC16_32_Q16(c, a, b) (MAC16_32_Q16_armv5e(c, a, b))
|
||||
|
||||
/** 16x16 multiply-add where the result fits in 32 bits */
|
||||
#undef MAC16_16
|
||||
static OPUS_INLINE opus_val32 MAC16_16_armv5e(opus_val32 c, opus_val16 a,
|
||||
opus_val16 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MAC16_16\n\t"
|
||||
"smlabb %0, %1, %2, %3;\n"
|
||||
: "=r"(res)
|
||||
: "r"(a), "r"(b), "r"(c)
|
||||
);
|
||||
return res;
|
||||
}
|
||||
#define MAC16_16(c, a, b) (MAC16_16_armv5e(c, a, b))
|
||||
|
||||
/** 16x16 multiplication where the result fits in 32 bits */
|
||||
#undef MULT16_16
|
||||
static OPUS_INLINE opus_val32 MULT16_16_armv5e(opus_val16 a, opus_val16 b)
|
||||
{
|
||||
int res;
|
||||
__asm__(
|
||||
"#MULT16_16\n\t"
|
||||
"smulbb %0, %1, %2;\n"
|
||||
: "=r"(res)
|
||||
: "r"(a), "r"(b)
|
||||
);
|
||||
return res;
|
||||
}
|
||||
#define MULT16_16(a, b) (MULT16_16_armv5e(a, b))
|
||||
|
||||
#ifdef OPUS_ARM_INLINE_MEDIA
|
||||
|
||||
#undef SIG2WORD16
|
||||
static OPUS_INLINE opus_val16 SIG2WORD16_armv6(opus_val32 x)
|
||||
{
|
||||
celt_sig res;
|
||||
__asm__(
|
||||
"#SIG2WORD16\n\t"
|
||||
"ssat %0, #16, %1, ASR #12\n\t"
|
||||
: "=r"(res)
|
||||
: "r"(x+2048)
|
||||
);
|
||||
return EXTRACT16(res);
|
||||
}
|
||||
#define SIG2WORD16(x) (SIG2WORD16_armv6(x))
|
||||
|
||||
#endif /* OPUS_ARM_INLINE_MEDIA */
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user