芯片开放社区
登录
直播中
abdkjshd
8年用户
1156经验值
擅长:可编程逻辑
私信
关注
[问答]
如何去实现基于HTTPClient云语音识别的POST请求功能呢
开启该帖子的消息推送
httpclient
如何去实现基于HTTPClient云语音识别的POST请求功能呢?有哪些步骤呢?
回帖
(1)
薄坤坤
2022-3-8 09:16:42
RVB2601
首先,我们结合之前所作的工作,重新梳理云语音识别项目的目标与思路。
目标:按下板载按钮,RVB2601 开始录音,上传云端语音识别后返回识别结果至串口。
思路:实现的程序流程与所需要的关键组件如下:
gpio_pin:设置按键中断。按下按钮后设置“开始录音标志位”为1。
rhino:注册监控任务。监控“开始录音标志位”,若为1则:
codec:进行录音。
HTTPClient :上传录音数据,接收并返回识别结果。
清空标志位
HTTPClient 发送录音数据
首先从最复杂的,手动发送音频数据开始。
借鉴另一个组件 http 的思路,先针对我们的应用场景设置好 boundary 和请求体的头部、尾部:
// 请求体格式
static const char *boundary = "----WebKitFormBoundarypNjgoVtFRlzPquKE";
#define MY_FORMAT_START "------WebKitFormBoundarypNjgoVtFRlzPquKErnContent-Disposition: %s; name="%s"; filename="%s"rnContent-Type: %srnrn"
#define MY_FORMAT_END "rn------WebKitFormBoundarypNjgoVtFRlzPquKE--rn"
由于我们的音频数据较大,且一次流程只发送一个文件,因此发送 POST 请求体时可以分三部分发送:头部、音频数据、尾部。所以在进行音频数据传输时,相比于原来的流程,我们要先设置“Content-Type”,接着基于模板制备请求体头部尾部,设置 Content-Length,并将 POST 相关变量统一存放在一个结构体中,方便进行 POST 请求时调用。以下是音频发送函数:
static void post_audio()
{
http_client_config_t config = {
.url = "http://upload.hazhuzhu.com/myasr.php",
.method = HTTP_METHOD_POST,
.event_handler = _http_event_handler,
};
http_client_handle_t client = http_client_init(&config);
// 设置 Content-Type
http_client_set_header(client, "Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarypNjgoVtFRlzPquKE");
const unsigned char *post_content = repeater_data_addr; // POST 请求体的内容为录音数据
// 请求体格式相关变量
const char *content_disposition = "form-data";
const char *name = "file";
const char *filename = "raw_recording";
const char *content_type = "application/octet-stream";
int boundary_len = strlen(boundary);
// 制备请求体头部
int post_start_len = strlen(MY_FORMAT_START) - 8 + strlen(content_disposition) + strlen(name) + strlen(filename) + strlen(content_type) + 1;
char *post_start = (char *)malloc(post_start_len + 1);
memset(post_start, 0, sizeof(post_start_len));
snprintf(post_start, post_start_len, MY_FORMAT_START, content_disposition, name, filename, content_type);
const char *post_end = MY_FORMAT_END;
// 设置 Content-Length
http_client_set_post_len(client, strlen(post_start) + PCM_LEN + strlen(post_end));
// 设置请求体相关变量
my_post_data_t post_data = {
.start = post_start,
.end = post_end,
.content = post_content,
.start_len = strlen(post_start),
.end_len = strlen(post_end),
.content_len = PCM_LEN,
};
// 进行一次 POST 请求
http_errors_t err = http_client_myperform(client, &post_data);
if (err == HTTP_CLI_OK)
{
LOGI(TAG, "HTTP POST Status = %d, content_length = %d rn",
http_client_get_status_code(client),
http_client_get_content_length(client));
char *raw_data_p;
result_len = http_client_get_response_raw_data(client, &raw_data_p);
// 输出 response
if (result_len)
{
char *result = (char *)malloc(result_len + 1);
memcpy(result, raw_data_p, result_len);
memcpy(result + result_len, " ", 1);
printf("%drn", result_len);
printf("%srn", result);
result_len = 0;
free(result);
}
}
else
{
LOGE(TAG, "HTTP POST request failed: 0x%x @#@@@@@@", (err));
e_count++;
}
http_client_cleanup(client);
}
其中,http_client_myperform() 是将 http_client_perform() 中的 http_client_send_post_data() 替换为 http_client_mysend_post_data():
/* http_client.c */
#define MY_BODY_SIZE 1000
static web_err_t http_client_mysend_post_data(http_client_handle_t client, my_post_data_t *post_data)
{
// 此时请求头应发送完毕
if (client->state != HTTP_STATE_REQ_COMPLETE_HEADER)
{
LOGE(TAG, "Invalid state");
return WEB_ERR_INVALID_STATE;
}
// 没有要发送的 post_data 直接返回(包括其它请求)
if (!(post_data->content && client->post_len))
{
goto success;
}
// 发送请求体头部
int wret = http_client_write(client, post_data->start, post_data->start_len);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送音频数据,可能发送多次
int content_idx = 0;
for (int i = 0; i < post_data->content_len / MY_BODY_SIZE; ++i)
{
wret = http_client_write(client, post_data->content + content_idx, MY_BODY_SIZE);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
content_idx += MY_BODY_SIZE;
}
wret = http_client_write(client, post_data->content + content_idx, post_data->content_len - content_idx);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送请求体尾部
wret = http_client_write(client, post_data->end, post_data->end_len);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送完毕
if (client->data_write_left <= 0)
{
goto success;
}
else
{
return ERR_HTTP_WRITE_DATA;
}
success:
// 更新状态
client->state = HTTP_STATE_REQ_COMPLETE_DATA;
return WEB_OK;
}
相比于原来只发送一次数据,现在分三部分发送。并且,由于网络库提供的 http_client_write() 函数没有考虑 TCP 数据包大小限制,我们手动将音频数据每次 MY_BODY_SIZE 大小,多次发送:
http_client_write() 调用流程:
transport_write()
_write()
select()
整个流程都没有考虑数据包大小,若一次发送数据过大会阻塞,因此需要手动分包。
http_client_myperform() 的其它流程都不需要修改,接收到 response 后库函数会自动解析。阅读源码后发现最后会将服务器端发回的原始数据放在 client->response->buffer->raw_data 里。我们写一个函数提取出来:
/* http_client.c */
int http_client_get_response_raw_data(http_client_handle_t client, char **raw_data)
{
int raw_len = client->response->buffer->raw_len;
if (raw_len)
{
*raw_data = client->response->buffer->raw_data;
return raw_len;
}
return 0;
}
在 post_audio() 的最后我们会调用这个函数,并将识别结果打印出来。
codec 录制音频
录音部分仿照 ch2601_ft_demo 写。由于 RVB2601 存储资源有限,我们只开辟 49152 字节的录音缓存(不能初始化赋值,否则 flash 装不下)。设置采样率 8000Hz,位深 16bit(更清晰方便识别)。这样大概能录制 1.536s,对于我们演示一些简短的口令来说也够用了。
事实上我们后面要调用的语音识别 api 要求音频文件是单声道的,我们后面还需要进行转换。所以这里其实存在一定的存储空间浪费,但我没有在库里找到只录制单声道音频的方法……默认就是双声道的……
/* 录音 */
static void cmd_mic_handler()
{
csi_error_t ret;
csi_codec_input_config_t input_config;
ret = csi_codec_init(&codec, 0);
if (ret != CSI_OK)
{
printf("csi_codec_init errorn");
return;
}
codec_input_ch.ring_buf = &input_ring_buffer;
csi_codec_input_open(&codec, &codec_input_ch, 0);
/* input ch config */
csi_codec_input_attach_callback(&codec_input_ch, codec_input_event_cb_fun, NULL);
input_config.bit_width = 16;
input_config.sample_rate = 8000;
input_config.buffer = input_buf;
input_config.buffer_size = INPUT_BUF_SIZE;
input_config.period = 1024;
input_config.mode = CODEC_INPUT_DIFFERENCE;
csi_codec_input_config(&codec_input_ch, &input_config);
csi_codec_input_analog_gain(&codec_input_ch, 0xbf);
csi_codec_input_link_dma(&codec_input_ch, &dma_ch_input_handle);
printf("start recodern");
csi_codec_input_start(&codec_input_ch);
// 麦克风录音写入数据 48x1024=49152
while (new_data_flag < 48)
{
if (cb_input_transfer_flag)
{
csi_codec_input_read_async(&codec_input_ch, repeater_data_addr + (new_data_flag * 1024), 1024);
cb_input_transfer_flag = 0U; // 回调函数将其置 1
new_data_flag++;
}
}
new_data_flag = 0;
printf("stop recodern");
csi_codec_input_stop(&codec_input_ch);
csi_codec_input_link_dma(&codec_input_ch, NULL);
csi_codec_input_detach_callback(&codec_input_ch);
csi_codec_uninit(&codec);
return;
}
按键中断
初始化时设置按键中断,并在回调函数里设置标志位:
static void gpio_pin_callback(csi_gpio_pin_t *pin, void *arg)
{
start_to_record = 1; // 标志位置 1 通知 mic 任务开始录音并上传
}
/* Key1 初始化 */
int btn_init()
{
// key 1
memset(&g_handle, 0, sizeof(g_handle));
csi_pin_set_mux(PA11, PIN_FUNC_GPIO);
csi_gpio_pin_init(&g_handle, PA11);
csi_gpio_pin_dir(&g_handle, GPIO_DIRECTION_INPUT);
csi_gpio_pin_mode(&g_handle, GPIO_MODE_PULLUP);
csi_gpio_pin_debounce(&g_handle, true);
csi_gpio_pin_attach_callback(&g_handle, gpio_pin_callback, &g_handle);
csi_gpio_pin_irq_mode(&g_handle, GPIO_IRQ_MODE_FALLING_EDGE);
csi_gpio_pin_irq_enable(&g_handle, true);
return 0;
}
监控任务
编写监控任务并注册。我也写了 cli 命令,可以通过串口控制:
// 监控任务
static void mic_task(void *arg)
{
while (1)
{
// 按下按钮后录制并上传
if (start_to_record == 1)
{
cmd_mic_handler();
post_audio();
start_to_record = 0;
}
aos_msleep(100);
}
}
static void cmd_http_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
if (argc == 2 && strcmp(argv[1], "post") == 0)
{
post_audio();
}
else
{
printf("thttp postn");
}
}
static void cmd_mic_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
if (argc == 2 && strcmp(argv[1], "record") == 0)
{
cmd_mic_handler();
}
else
{
printf("tmic recordn");
}
}
int cli_reg_cmd_asr(void)
{
char url[128];
// POST 录音
static const struct cli_command http_cmd_info = {
"http",
"http post",
cmd_http_func,
};
// 录音命令
static const struct cli_command mic_cmd_info = {
"mic",
"mic record",
cmd_mic_func,
};
// 注册命令
aos_cli_register_command(&http_cmd_info);
aos_cli_register_command(&mic_cmd_info);
// 新建麦克风任务
aos_task_new("mic", mic_task, NULL, 10 * 1024);
return 0;
}
修改链接文件
如果编译中遇到 SRAM overflowed 的问题,可以修改 configs/gcc_flash.ld:
- REGION_ALIAS("REGION_BSS", SRAM);
+ REGION_ALIAS("REGION_BSS", DSRAM);
将 BSS 段存到 DSRAM。ch2601_webplayer_demo 工程中的 linker file 和默认的不一样,不知道为什么……
这样,板端就基本开发完毕了。本来打算将识别结果显示在 oled 上的,然而 flash 不够用了……
服务器
nginx+PHP架构,使用腾讯云提供的语音识别 api。
nginx
server
{
listen 80;
server_name asr.hazhuzhu.com;
client_max_body_size 128m;
root /home/wwwroot/asr;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ =404;
}
location ~ .php$ {
include fastcgi.conf;
fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_keep_conn on;
}
}
PHP
后端负责存储音频文件并调用“一句话识别” api(需要使用腾讯云相关 SDK),最后返回识别结果。api 调用部分腾讯云提供了相关文档和代码生成工具,比较方便。
由于 RVB2601 codec 库存储的录音数据是 PCM 编码,2通道。而 api 只接收 wav/mp3 ,1通道的音频文件。而且,由于我们在 http_client_mysend_post_data() 中手动发送音频时没有考虑大小端的问题,所以服务器接收到我们 16bit 音频还是大端存储的。所以在调用 api 前,还需要修正大小端、通道数和文件格式。处于效率,直接调用 ffmepg 来实现……
// 调用腾讯云 SDK 省略
$uploads_dir = 'ch2601_recordings';
if ($_FILES['file']['error'] == UPLOAD_ERR_OK)
{
$tmp_name = $_FILES['file']['tmp_name'];
// $name = $_FILES['file']['name'];
$date_str=date('YmdHis');
move_uploaded_file($tmp_name, "$uploads_dir/$date_str".'-b2.pcm');
// 用 ffmpeg 转码,先转大小端并压缩为 1 通道,再转为 wav 格式
exec('ffmpeg -f s16be -ar 8000 -ac 2 -i '."$uploads_dir/$date_str".'-b2.pcm'.' -f s16le -ar 8000 -ac 1 '."$uploads_dir/$date_str".'-l1.pcm');
exec('ffmpeg -f s16le -ar 8000 -ac 1 -i '."$uploads_dir/$date_str".'-l1.pcm '."$uploads_dir/$date_str".'-l1.wav');
try {
// api 调用部分,省略……
$resp = $client->SentenceRecognition($req);
$result_str=$resp->getResult();
$result_file=fopen("$uploads_dir/$date_str".'.txt',"a");
fwrite($result_file,$result_str);
fclose($result_file);
echo $result_str;
}
catch(TencentCloudSDKException $e) {
echo $e;
}
}
至此我们完成了一个完整的基于 RVB2601 的云语音识别应用。
总结
首先我想谈谈使用 RVB2601 的开发体验。正如前文所述,RVB2601 板载资源丰富,能够实现大部分物联网场景应用。并且剑池 CDK 也和我们熟悉的其它单片机开发 IDE 类似,很容易就能上手。RVB2601 的几个示例程序也能让我们很快熟悉板子的开发步骤。RVB2601 有 RST 键,下载后不需要反复插拔。工作人员也非常热心认真,在我发现组件网络资源有点问题的时候几分钟就回应并修复了。
最重要的是,YOC 功能非常强大。还有太多的 API 与组件我还没有尝试,但我相信如果能熟练运用,我们很容易就能基于 YOC 支持芯片上完成一个完整并强大的物联网系统。
RVB2601
首先,我们结合之前所作的工作,重新梳理云语音识别项目的目标与思路。
目标:按下板载按钮,RVB2601 开始录音,上传云端语音识别后返回识别结果至串口。
思路:实现的程序流程与所需要的关键组件如下:
gpio_pin:设置按键中断。按下按钮后设置“开始录音标志位”为1。
rhino:注册监控任务。监控“开始录音标志位”,若为1则:
codec:进行录音。
HTTPClient :上传录音数据,接收并返回识别结果。
清空标志位
HTTPClient 发送录音数据
首先从最复杂的,手动发送音频数据开始。
借鉴另一个组件 http 的思路,先针对我们的应用场景设置好 boundary 和请求体的头部、尾部:
// 请求体格式
static const char *boundary = "----WebKitFormBoundarypNjgoVtFRlzPquKE";
#define MY_FORMAT_START "------WebKitFormBoundarypNjgoVtFRlzPquKErnContent-Disposition: %s; name="%s"; filename="%s"rnContent-Type: %srnrn"
#define MY_FORMAT_END "rn------WebKitFormBoundarypNjgoVtFRlzPquKE--rn"
由于我们的音频数据较大,且一次流程只发送一个文件,因此发送 POST 请求体时可以分三部分发送:头部、音频数据、尾部。所以在进行音频数据传输时,相比于原来的流程,我们要先设置“Content-Type”,接着基于模板制备请求体头部尾部,设置 Content-Length,并将 POST 相关变量统一存放在一个结构体中,方便进行 POST 请求时调用。以下是音频发送函数:
static void post_audio()
{
http_client_config_t config = {
.url = "http://upload.hazhuzhu.com/myasr.php",
.method = HTTP_METHOD_POST,
.event_handler = _http_event_handler,
};
http_client_handle_t client = http_client_init(&config);
// 设置 Content-Type
http_client_set_header(client, "Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarypNjgoVtFRlzPquKE");
const unsigned char *post_content = repeater_data_addr; // POST 请求体的内容为录音数据
// 请求体格式相关变量
const char *content_disposition = "form-data";
const char *name = "file";
const char *filename = "raw_recording";
const char *content_type = "application/octet-stream";
int boundary_len = strlen(boundary);
// 制备请求体头部
int post_start_len = strlen(MY_FORMAT_START) - 8 + strlen(content_disposition) + strlen(name) + strlen(filename) + strlen(content_type) + 1;
char *post_start = (char *)malloc(post_start_len + 1);
memset(post_start, 0, sizeof(post_start_len));
snprintf(post_start, post_start_len, MY_FORMAT_START, content_disposition, name, filename, content_type);
const char *post_end = MY_FORMAT_END;
// 设置 Content-Length
http_client_set_post_len(client, strlen(post_start) + PCM_LEN + strlen(post_end));
// 设置请求体相关变量
my_post_data_t post_data = {
.start = post_start,
.end = post_end,
.content = post_content,
.start_len = strlen(post_start),
.end_len = strlen(post_end),
.content_len = PCM_LEN,
};
// 进行一次 POST 请求
http_errors_t err = http_client_myperform(client, &post_data);
if (err == HTTP_CLI_OK)
{
LOGI(TAG, "HTTP POST Status = %d, content_length = %d rn",
http_client_get_status_code(client),
http_client_get_content_length(client));
char *raw_data_p;
result_len = http_client_get_response_raw_data(client, &raw_data_p);
// 输出 response
if (result_len)
{
char *result = (char *)malloc(result_len + 1);
memcpy(result, raw_data_p, result_len);
memcpy(result + result_len, " ", 1);
printf("%drn", result_len);
printf("%srn", result);
result_len = 0;
free(result);
}
}
else
{
LOGE(TAG, "HTTP POST request failed: 0x%x @#@@@@@@", (err));
e_count++;
}
http_client_cleanup(client);
}
其中,http_client_myperform() 是将 http_client_perform() 中的 http_client_send_post_data() 替换为 http_client_mysend_post_data():
/* http_client.c */
#define MY_BODY_SIZE 1000
static web_err_t http_client_mysend_post_data(http_client_handle_t client, my_post_data_t *post_data)
{
// 此时请求头应发送完毕
if (client->state != HTTP_STATE_REQ_COMPLETE_HEADER)
{
LOGE(TAG, "Invalid state");
return WEB_ERR_INVALID_STATE;
}
// 没有要发送的 post_data 直接返回(包括其它请求)
if (!(post_data->content && client->post_len))
{
goto success;
}
// 发送请求体头部
int wret = http_client_write(client, post_data->start, post_data->start_len);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送音频数据,可能发送多次
int content_idx = 0;
for (int i = 0; i < post_data->content_len / MY_BODY_SIZE; ++i)
{
wret = http_client_write(client, post_data->content + content_idx, MY_BODY_SIZE);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
content_idx += MY_BODY_SIZE;
}
wret = http_client_write(client, post_data->content + content_idx, post_data->content_len - content_idx);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送请求体尾部
wret = http_client_write(client, post_data->end, post_data->end_len);
if (wret < 0)
{
return wret;
}
client->data_write_left -= wret;
// 发送完毕
if (client->data_write_left <= 0)
{
goto success;
}
else
{
return ERR_HTTP_WRITE_DATA;
}
success:
// 更新状态
client->state = HTTP_STATE_REQ_COMPLETE_DATA;
return WEB_OK;
}
相比于原来只发送一次数据,现在分三部分发送。并且,由于网络库提供的 http_client_write() 函数没有考虑 TCP 数据包大小限制,我们手动将音频数据每次 MY_BODY_SIZE 大小,多次发送:
http_client_write() 调用流程:
transport_write()
_write()
select()
整个流程都没有考虑数据包大小,若一次发送数据过大会阻塞,因此需要手动分包。
http_client_myperform() 的其它流程都不需要修改,接收到 response 后库函数会自动解析。阅读源码后发现最后会将服务器端发回的原始数据放在 client->response->buffer->raw_data 里。我们写一个函数提取出来:
/* http_client.c */
int http_client_get_response_raw_data(http_client_handle_t client, char **raw_data)
{
int raw_len = client->response->buffer->raw_len;
if (raw_len)
{
*raw_data = client->response->buffer->raw_data;
return raw_len;
}
return 0;
}
在 post_audio() 的最后我们会调用这个函数,并将识别结果打印出来。
codec 录制音频
录音部分仿照 ch2601_ft_demo 写。由于 RVB2601 存储资源有限,我们只开辟 49152 字节的录音缓存(不能初始化赋值,否则 flash 装不下)。设置采样率 8000Hz,位深 16bit(更清晰方便识别)。这样大概能录制 1.536s,对于我们演示一些简短的口令来说也够用了。
事实上我们后面要调用的语音识别 api 要求音频文件是单声道的,我们后面还需要进行转换。所以这里其实存在一定的存储空间浪费,但我没有在库里找到只录制单声道音频的方法……默认就是双声道的……
/* 录音 */
static void cmd_mic_handler()
{
csi_error_t ret;
csi_codec_input_config_t input_config;
ret = csi_codec_init(&codec, 0);
if (ret != CSI_OK)
{
printf("csi_codec_init errorn");
return;
}
codec_input_ch.ring_buf = &input_ring_buffer;
csi_codec_input_open(&codec, &codec_input_ch, 0);
/* input ch config */
csi_codec_input_attach_callback(&codec_input_ch, codec_input_event_cb_fun, NULL);
input_config.bit_width = 16;
input_config.sample_rate = 8000;
input_config.buffer = input_buf;
input_config.buffer_size = INPUT_BUF_SIZE;
input_config.period = 1024;
input_config.mode = CODEC_INPUT_DIFFERENCE;
csi_codec_input_config(&codec_input_ch, &input_config);
csi_codec_input_analog_gain(&codec_input_ch, 0xbf);
csi_codec_input_link_dma(&codec_input_ch, &dma_ch_input_handle);
printf("start recodern");
csi_codec_input_start(&codec_input_ch);
// 麦克风录音写入数据 48x1024=49152
while (new_data_flag < 48)
{
if (cb_input_transfer_flag)
{
csi_codec_input_read_async(&codec_input_ch, repeater_data_addr + (new_data_flag * 1024), 1024);
cb_input_transfer_flag = 0U; // 回调函数将其置 1
new_data_flag++;
}
}
new_data_flag = 0;
printf("stop recodern");
csi_codec_input_stop(&codec_input_ch);
csi_codec_input_link_dma(&codec_input_ch, NULL);
csi_codec_input_detach_callback(&codec_input_ch);
csi_codec_uninit(&codec);
return;
}
按键中断
初始化时设置按键中断,并在回调函数里设置标志位:
static void gpio_pin_callback(csi_gpio_pin_t *pin, void *arg)
{
start_to_record = 1; // 标志位置 1 通知 mic 任务开始录音并上传
}
/* Key1 初始化 */
int btn_init()
{
// key 1
memset(&g_handle, 0, sizeof(g_handle));
csi_pin_set_mux(PA11, PIN_FUNC_GPIO);
csi_gpio_pin_init(&g_handle, PA11);
csi_gpio_pin_dir(&g_handle, GPIO_DIRECTION_INPUT);
csi_gpio_pin_mode(&g_handle, GPIO_MODE_PULLUP);
csi_gpio_pin_debounce(&g_handle, true);
csi_gpio_pin_attach_callback(&g_handle, gpio_pin_callback, &g_handle);
csi_gpio_pin_irq_mode(&g_handle, GPIO_IRQ_MODE_FALLING_EDGE);
csi_gpio_pin_irq_enable(&g_handle, true);
return 0;
}
监控任务
编写监控任务并注册。我也写了 cli 命令,可以通过串口控制:
// 监控任务
static void mic_task(void *arg)
{
while (1)
{
// 按下按钮后录制并上传
if (start_to_record == 1)
{
cmd_mic_handler();
post_audio();
start_to_record = 0;
}
aos_msleep(100);
}
}
static void cmd_http_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
if (argc == 2 && strcmp(argv[1], "post") == 0)
{
post_audio();
}
else
{
printf("thttp postn");
}
}
static void cmd_mic_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
if (argc == 2 && strcmp(argv[1], "record") == 0)
{
cmd_mic_handler();
}
else
{
printf("tmic recordn");
}
}
int cli_reg_cmd_asr(void)
{
char url[128];
// POST 录音
static const struct cli_command http_cmd_info = {
"http",
"http post",
cmd_http_func,
};
// 录音命令
static const struct cli_command mic_cmd_info = {
"mic",
"mic record",
cmd_mic_func,
};
// 注册命令
aos_cli_register_command(&http_cmd_info);
aos_cli_register_command(&mic_cmd_info);
// 新建麦克风任务
aos_task_new("mic", mic_task, NULL, 10 * 1024);
return 0;
}
修改链接文件
如果编译中遇到 SRAM overflowed 的问题,可以修改 configs/gcc_flash.ld:
- REGION_ALIAS("REGION_BSS", SRAM);
+ REGION_ALIAS("REGION_BSS", DSRAM);
将 BSS 段存到 DSRAM。ch2601_webplayer_demo 工程中的 linker file 和默认的不一样,不知道为什么……
这样,板端就基本开发完毕了。本来打算将识别结果显示在 oled 上的,然而 flash 不够用了……
服务器
nginx+PHP架构,使用腾讯云提供的语音识别 api。
nginx
server
{
listen 80;
server_name asr.hazhuzhu.com;
client_max_body_size 128m;
root /home/wwwroot/asr;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ =404;
}
location ~ .php$ {
include fastcgi.conf;
fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_keep_conn on;
}
}
PHP
后端负责存储音频文件并调用“一句话识别” api(需要使用腾讯云相关 SDK),最后返回识别结果。api 调用部分腾讯云提供了相关文档和代码生成工具,比较方便。
由于 RVB2601 codec 库存储的录音数据是 PCM 编码,2通道。而 api 只接收 wav/mp3 ,1通道的音频文件。而且,由于我们在 http_client_mysend_post_data() 中手动发送音频时没有考虑大小端的问题,所以服务器接收到我们 16bit 音频还是大端存储的。所以在调用 api 前,还需要修正大小端、通道数和文件格式。处于效率,直接调用 ffmepg 来实现……
// 调用腾讯云 SDK 省略
$uploads_dir = 'ch2601_recordings';
if ($_FILES['file']['error'] == UPLOAD_ERR_OK)
{
$tmp_name = $_FILES['file']['tmp_name'];
// $name = $_FILES['file']['name'];
$date_str=date('YmdHis');
move_uploaded_file($tmp_name, "$uploads_dir/$date_str".'-b2.pcm');
// 用 ffmpeg 转码,先转大小端并压缩为 1 通道,再转为 wav 格式
exec('ffmpeg -f s16be -ar 8000 -ac 2 -i '."$uploads_dir/$date_str".'-b2.pcm'.' -f s16le -ar 8000 -ac 1 '."$uploads_dir/$date_str".'-l1.pcm');
exec('ffmpeg -f s16le -ar 8000 -ac 1 -i '."$uploads_dir/$date_str".'-l1.pcm '."$uploads_dir/$date_str".'-l1.wav');
try {
// api 调用部分,省略……
$resp = $client->SentenceRecognition($req);
$result_str=$resp->getResult();
$result_file=fopen("$uploads_dir/$date_str".'.txt',"a");
fwrite($result_file,$result_str);
fclose($result_file);
echo $result_str;
}
catch(TencentCloudSDKException $e) {
echo $e;
}
}
至此我们完成了一个完整的基于 RVB2601 的云语音识别应用。
总结
首先我想谈谈使用 RVB2601 的开发体验。正如前文所述,RVB2601 板载资源丰富,能够实现大部分物联网场景应用。并且剑池 CDK 也和我们熟悉的其它单片机开发 IDE 类似,很容易就能上手。RVB2601 的几个示例程序也能让我们很快熟悉板子的开发步骤。RVB2601 有 RST 键,下载后不需要反复插拔。工作人员也非常热心认真,在我发现组件网络资源有点问题的时候几分钟就回应并修复了。
最重要的是,YOC 功能非常强大。还有太多的 API 与组件我还没有尝试,但我相信如果能熟练运用,我们很容易就能基于 YOC 支持芯片上完成一个完整并强大的物联网系统。
举报
更多回帖
rotate(-90deg);
回复
相关问答
httpclient
在RVB2601上怎样
去
实现
基于
HTTPClient
组件的
云
语音
识别
呢
2022-03-08
2089
嵌入式
语音
识别
技术在80251内核中该如何
去
实现
呢
2021-12-23
2116
如何
去
实现
一种特定人
语音
识别
系统?
2021-05-19
1940
怎样
去
解决RK3328 Android 7.1录音出现偶现
语音
无法
识别的
问题
2022-03-09
1596
特定人
语音
识别的
方法有哪些?
2021-05-14
1781
如何利用MCU
实现
语音
识别
?
2021-04-02
2029
自动
语音
识别的
原理是什么?
2021-06-15
2569
GET和
POST
的
请求
示例流程
2021-04-02
3320
语音
识别的
现状如何?
2019-10-08
2938
凌阳16位单片机关于
语音
识别的
东东
2015-03-15
2759
发帖
登录/注册
20万+
工程师都在用,
免费
PCB检查工具
无需安装、支持浏览器和手机在线查看、实时共享
查看
点击登录
登录更多精彩功能!
首页
论坛版块
小组
免费开发板试用
ebook
直播
搜索
登录
×
20
完善资料,
赚取积分