This commit is contained in:
2025-09-14 13:19:18 +08:00
parent 63e404d610
commit 5c43129024
7969 changed files with 1 additions and 2459369 deletions

View File

@@ -1,2 +0,0 @@
CompileFlags:
Remove: [-f*, -m*]

19
.gitignore vendored
View File

@@ -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
View File

@@ -0,0 +1 @@
{}

View File

@@ -1,3 +0,0 @@
dist/
.component_hash
.idea

File diff suppressed because one or more lines are too long

View File

@@ -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"
)

View File

@@ -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.

View File

@@ -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");
// 无需手动 deleteunique_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)

View File

@@ -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

View File

@@ -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_

View File

@@ -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_

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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_

View File

@@ -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";
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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_;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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_

View File

@@ -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);
}
}
}

View File

@@ -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_

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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_

View File

@@ -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 "";
}

View File

@@ -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

View File

@@ -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";
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -1 +0,0 @@
b80e5b6d6dc4bf6b0bf1a3729f52b80500e9e6b1003b3e827b78ba738283a296

View File

@@ -1 +0,0 @@
dist/

View File

@@ -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"}]}

View File

@@ -1,10 +0,0 @@
idf_component_register(
SRCS
"opus_encoder.cc"
"opus_decoder.cc"
"opus_resampler.cc"
INCLUDE_DIRS
"include"
PRIV_INCLUDE_DIRS
"."
)

View File

@@ -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

View File

@@ -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_

View File

@@ -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_

View File

@@ -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

View File

@@ -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 */

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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_;
}

View File

@@ -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_

View File

@@ -1 +0,0 @@
8182b733f071d7bfe1e837f4c9f8649a63e4c937177f089e65772880c02f2e17

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -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})

View File

@@ -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/

View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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 its 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).

View File

@@ -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....

View File

@@ -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

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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");
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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