使用dfrobot ESP32-S3- AI CAM制作的远程喊话智能监控
本帖最后由 名动神界 于 2025-5-27 11:15 编辑测试时比较仓促,加之能力有限、经验不足,测试结果不具备普遍意义,这是一次简易的试用报告。相信dfrobot的产品质量都是过硬的,我没有把它的性能发挥出来。本案例中用到的所有头文件在此,下面不再重复
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "ESP_I2S.h"
#include "esp_camera.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"所有引脚的配置如下图:
// 摄像头引脚配置(ESP32-S3默认)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 5
#define Y9_GPIO_NUM 4
#define Y8_GPIO_NUM 6
#define Y7_GPIO_NUM 7
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 17
#define Y4_GPIO_NUM 21
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 16
#define VSYNC_GPIO_NUM 1
#define HREF_GPIO_NUM 2
#define PCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 8
#define SIOC_GPIO_NUM 9
// TF卡SPI引脚配置
#define SCK_PIN 12
#define MISO_PIN 13
#define MOSI_PIN 11
#define CS_PIN 10
// 麦克风引脚
#define MIC_DATA 39
#define MIC_CLK 38
[*]第一步,是测试延时拍摄,因为板子不具备h264等编码器,录制mp4需占用80%以上的cpu且工作非常不稳定。
void setup() {
Serial.begin(115200);
camera_config_t config;// 初始化摄像头
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed: 0x%x", err);
return;}
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);//初始化sd卡
if(!SD.begin(CS_PIN)){
Serial.println("SD Card Mount Failed");
return;}}
void loop() {
// 捕获帧并保存
camera_fb_t *fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
return;}
String path = "/video_" + String(millis()) + ".jpg"; // 创建唯一文件名
File file = SD.open(path.c_str(), FILE_WRITE);// 写入SD卡
if(!file){
Serial.println("Failed to open file");
} else {
file.write(fb->buf, fb->len);
Serial.println("Saved: " + path);}
file.close();
esp_camera_fb_return(fb);
delay(100);} // 控制帧率
录了6000多张,不过由于昨晚角度没放好,一直录的是距离桌面2米高的屋顶,红外夜视效果就出不来了,官方公布的红外夜视距离是2米以内。后面的图片是选取的典型状态图,第一张是晚上10点,最后一张是早上7点过。
(典型的延时拍摄截图)
[*]第二步是测试远程喊话,这个费了点时间,因为我本人硬件调试经验薄弱,有些瞎子摸象的窘态,不过最终还是成功地使用百度语音合成生成了声音。
// 配置参数
#define BAIDU_TTS_URL "http://tsn.baidu.com/text2audio"
#define API_KEY "***"
#define SECRET_KEY "***"
#define SAMPLE_RATE 16000
I2SClass i2s;
WiFiClient client;
String getToken() {
HTTPClient http;
String url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials";
url += "&client_id=" + String(API_KEY);
url += "&client_secret=" + String(SECRET_KEY);
http.begin(url);
int httpCode = http.GET();
if(httpCode == HTTP_CODE_OK) {
String payload = http.getString();
int tokenStart = payload.indexOf("access_token") + 15;
int tokenEnd = payload.indexOf("\"", tokenStart);
String accessToken = payload.substring(tokenStart, tokenEnd);
return accessToken;
}
return "";
}
void playAudio(uint8_t* data, size_t len) {
i2s.write(data, len);
}
void textToSpeech(String text) {
String token = getToken();
if(token == "") {
Serial.println("Failed to get token");
return;
}
HTTPClient http;
String url = BAIDU_TTS_URL "?tex=" + text;
url += "&tok=" + token;
url += "&cuid=esp32_device_zzq";
url += "&ctp=1";
url += "&lan=zh";
url += "&spd=5";
url += "&pit=5";
url += "&vol=5";
url += "&per=0";
url += "&aue=4"; // mp3格式
http.begin(client, url);
int httpCode = http.GET();
if(httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr();
uint8_t buffer;
while(stream->available()) {
size_t len = stream->readBytes(buffer, sizeof(buffer));
playAudio(buffer, len);
}
}
http.end();
}
void setup() {
Serial.begin(115200);
WiFi.begin("qzezsteam", "qzez@1953");
while(WiFi.status() != WL_CONNECTED) delay(500);
Serial.println(WiFi.localIP());
i2s.setPins(45, 46, 42);
if(!i2s.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO)) {
Serial.println("I2S init failed");
}
}
void loop() {
if(Serial.available()) {
String input = Serial.readStringUntil('\n');
Serial.println(input);
textToSpeech(input);
}
}主要的难点是把百度语音合成的音频流格式,也就是aue,和板子的播放命令(playmp3,playwav,playaudio)相匹配。本人水平有限,使用aue=3,也就是mp3格式,aue=36,也就是wav格式,都调试失败。最后我把aue设置了无压缩的pcm格式,这是一种没有文件头、原汗原味的声音流,通过i2s.write命令播放成功。
i2s.write
[*]功能:直接向I2S总线发送原始PCM音频数据流。
[*]数据要求:输入数据必须是未经压缩的PCM格式(如16位/32位量化、单/双声道)35。
[*]处理流程:需手动完成音频数据的解码、格式转换(如调整字节序、声道排列)和缓冲区管理57。
[*]典型应用:播放自定义音频流或需要低延迟控制的场景,如实时语音传输或音频合成
[*]第三步:测试web远程喊话,这个可以通过特殊手段把服务端html模板写入固件来实现,不过我还没学会。我是通过arduino ide中定义了html网页变量来做成功的。
[*]void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>远程监控系统</title>
</head>
<body>
<div>
<div>
<h2>远程喊话</h2>
<form action="/speak" method="POST">
<input type="text" name="text" placeholder="输入文字"><br>
<select name="preset">
<option value="">--选择预设短语--</option>
<option value="坏人,请赶快离开!">警告短语</option>
<option value="你有什么事么?">询问短语</option>
<option value="你好我是实验室主人">欢迎短语</option>
</select><br>
<input type="submit" value="播放">
</form>
</div>
<div>
</body>
</html>
)rawliteral";
server.send(200, "text/html; charset=utf-8", html);
}第四步就是制作延时拍摄了,开始走了弯路,总想用板子向web发送更新命令,失败n次,换成web端向板子请求数据,最后终于成功了
[*]<h2>实时监控</h2>
<img id='stream' src='/capture' onload='setTimeout(function(){ document.getElementById(\"stream\").src = \"/capture?\" + new Date().getTime(); }, 100);'>
onload这个回调函数,可以让图片实时更新,更新的频是100毫秒,也就是每秒钟回传10张。
[*]最后就制作完成了,下面是视频演示
[*]https://www.bilibili.com/video/BV1tQj1zAEHq/
页:
[1]