EC600U_esp32_iap_uart/tts/tts_demo2.c
2024-02-05 17:39:56 +08:00

773 lines
21 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*================================================================
Copyright (c) 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved.
Quectel Wireless Solution Proprietary and Confidential.
=================================================================*/
/************************************************************************************************************
此demo将tts的api与audio的播放函数一起进行封装封装后的ql_tts_playql_tts_initql_tts_deinit与我司
ASR系列的TTS播放函数类似直接调用ql_tts_init+ql_tts_play即可以开始播放播放完成后调用ql_tts_deinit
函数释放TTS资源。用户可参考tts_demo.c中的方法也可使用本demo中的方法进行TTS播放
注意由于audio的播放器只有一个因此此demo与tts_demo.c同一时间只能有一个运行
用户如需将TTS库放到内核则在target.config中将 CONFIG_QUEC_PROJECT_FEATURE_TTS_IN_KERNEL 置为 y,
并修改分区将内核分区增大250k左右其余的内容不变
*************************************************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "tts_demo.h"
#include "ql_api_osi.h"
#include "ql_api_tts.h"
#include "ql_log.h"
#include "ql_osi_def.h"
#include "ql_audio.h"
#include "ql_fs.h"
#include "ql_api_dev.h"
/*
1. 不同的TTS资源文件对应的播放效果不同。其中中文资源不能用来播英文单词单词会以字母的方式播出; 英文资源也不能用来播中文。默认使用
中文16k TTS资源
2. 若使用16k中文TTS资源且TTS资源文件预置到内置flash中则不需要修改json脚本(脚本默认已选择预置16k中文资源且预置到内置flash),只需要调用
ql_tts_engine_init函数即可完成初始化不需要关注以下描述
3. 所有的资源文件均在components\ql-config\download\prepack下其中:
英文16k资源文件名为: "quectel_tts_resource_english_16k.bin"
中文8k资源文件为"quectel_tts_resource_chinese_8k.bin"
中文16k资源文件为"quectel_pcm_resource.bin"
4. 预置文件时请将json脚本中的"file"固定为"/qsfs/quectel_pcm_resource.bin"(预置资源文件到内置flash), 或
"/ext/qsfs/quectel_pcm_resource.bin"(预置到外置6线spi flash中), 并修改"local_file"来选择上传哪个资源文件,如下述示例.
若不使用中文16k资源则需要使用"ql_tts_engine_init_ex"函数,将配置结构体中的"resource"变量设置为需要使用的资源;
若将资源文件预置到外置6线spi flash需要将"position"变量设置为 POSIT_EFS
当TTS资源文件预置在内置Flash时针对需要FOTA升级的情况新版本SDK中默认将该文件进行拆分为多个子文件进行预置
外置存储时可以不用拆分。
5. 使用英文16k TTS资源播放时需要1.45M的RAM空间因此要注意RAM空间是否充足 选择中文16k TTS资源文件时需要620k的RAM空间 选择中文
8k资源时需要570kRAM空间
预置文件示例:
1. 预置16k中文TTS资源文件到内部flash(默认):
"files": [
{
"file": "/qsfs/quectel_pcm_resource.bin",
"local_file": "quectel_pcm_resource.bin"
}
]
2. 预置16k英文TTS资源文件到内部flash(以"/qsfs/quectel_pcm_resource.bin"为文件系统路径)
"files": [
{
"file": "/qsfs/quectel_pcm_resource.bin",
"local_file": "quectel_tts_resource_english_16k.bin"
}
]
3. 预置8K中文TTS资源文件到内部flash(以"/qsfs/quectel_pcm_resource.bin"为文件系统路径)
"files": [
{
"file": "/qsfs/quectel_pcm_resource.bin",
"local_file": "quectel_tts_resource_chinese_8k.bin"
}
]
4. (1)预置16k英文TTS资源到外置6线spi flash(以/ext/qsfs/quectel_pcm_resource.bin"为文件系统路径)
"files": [
{
"file": "/ext/qsfs/quectel_pcm_resource.bin",
"local_file": "quectel_tts_resource_english_16k.bin"
}
]
(2)需要把boot_fdl_dnld.c文件的bool fdlDnldStart(fdlEngine_t *fdl, unsigned devtype)
6线flash部分的#if 0打开为1CONFIG_QUEC_PROJECT_FEATURE_SPI6_EXT_NOR_SFFS部分;
(3)在target.config中CONFIG_QUEC_PROJECT_FEATURE_SPI6_EXT_NOR_SFFS打开CONFIG_QUEC_PROJECT_FEATURE_SPI4_EXT_NOR关闭
5. (1)预置16k中文TTS资源文件到外置4线flash
"files": [
{
"file": "/ext4n/qsfs/quectel_pcm_resource.bin",
"local_file": "quectel_pcm_resource.bin"
}
]
(2)需要把boot_fdl_dnld.c文件的bool fdlDnldStart(fdlEngine_t *fdl, unsigned devtype)
4线flash部分的#if 0打开为1CONFIG_QUEC_PROJECT_FEATURE_SPI4_EXT_NOR_SFFS部分;
(3)在target.config中CONFIG_QUEC_PROJECT_FEATURE_SPI4_EXT_NOR_SFFS打开CONFIG_QUEC_PROJECT_FEATURE_SPI6_EXT_NOR关闭
*/
//QL_TTS_RESAMPLE_TEST宏控开启用于展示如何使用重采样相关函数
//重采样就是把采样率进行重新调整比如tts为16k资源但需要语音通话中是8k采样率因此必须将采样率进行重采样成8k的才能正常播放
#define QL_TTS_LANGUAGE_USE_ENGLISH 0
#define QL_TTS_RESAMPLE_TEST 0
/*0:tts库在内置flash1:tts库在六线flash2tts库在四线flash*/
#define QL_TTS_LOCATION 0
#define QL_TTS_LOG_LEVEL QL_LOG_LEVEL_INFO
#define QL_TTS_LOG(msg, ...) QL_LOG(QL_TTS_LOG_LEVEL, "ql_app_tts", msg, ##__VA_ARGS__)
#define QL_TTS_LOG_PUSH(msg, ...) QL_LOG_PUSH("ql_app_tts", msg, ##__VA_ARGS__)
#if !defined(tts_demo_no_err)
#define tts_demo_no_err(x, action, str) \
do \
{ \
if(x != 0) \
{ \
QL_TTS_LOG(str); \
{action;} \
} \
} while( 1==0 )
#endif
typedef struct
{
ql_tts_encoding_e encode;
char *str;
uint len;
}tts_demo_play_info_t;
#ifdef QL_TTS_RESAMPLE_TEST
typedef struct osi_buff
{
unsigned size;
unsigned rd;
unsigned wr;
char data[];
}osi_buff_t;
typedef struct
{
char *input_buffer;
char *output_buffer;
osi_buff_t *pipe;
ql_resampler *filter;
PCM_HANDLE_T pcm;
}tts_loacl_info_t;
static tts_loacl_info_t tts_loacl_info = {0};
#define QUEC_16K_FRAME_LEN 640
#define QUEC_8K_FRAME_LEN 320
#define QUEC_BEFORE_SAMPLING 16000
#define QUEC_AFTER_SAMPLING 8000
#endif
/*===========================================================================
* Variate
===========================================================================*/
PCM_HANDLE_T ql_player = NULL;
PCM_HANDLE_T ql_recorder = NULL;
ql_task_t ql_tts_demo_task2 = NULL;
ql_queue_t ql_tts_demo_queue = NULL;
/*===========================================================================
* Functions
===========================================================================*/
/**************************数据结构相关函数*********************************/
#ifdef QL_TTS_RESAMPLE_TEST
osi_buff_t *osibuffCreate(unsigned size)
{
if (size == 0)
return NULL;
osi_buff_t *pipe = (osi_buff_t *)calloc(1, sizeof(osi_buff_t) + size);
if (pipe == NULL)
return NULL;
pipe->size = size;
pipe->rd = 0;
pipe->wr = 0;
return pipe;
}
int osibuffWrite(osi_buff_t *pipe, const void *buf, unsigned size)
{
if (size == 0)
return 0;
if (pipe == NULL || buf == NULL)
return -1;
uint32_t critical = ql_rtos_enter_critical();
unsigned space = pipe->size - (pipe->wr - pipe->rd);
unsigned len = OSI_MIN(unsigned, size, space);
unsigned wr = pipe->wr;
if (len == 0)
{
ql_rtos_exit_critical(critical);
return 0;
}
unsigned offset = wr % pipe->size;
unsigned tail = pipe->size - offset;
if (tail >= len)
{
memcpy(&pipe->data[offset], buf, len);
}
else
{
memcpy(&pipe->data[offset], buf, tail);
memcpy(pipe->data, (const char *)buf + tail, len - tail);
}
pipe->wr += len;
ql_rtos_exit_critical(critical);
return len;
}
int osibuffWriteAll(osi_buff_t *pipe, const void *buf, unsigned size, unsigned timeout)
{
if (size == 0)
return 0;
if (pipe == NULL || buf == NULL)
return -1;
unsigned len = 0;
for (;;)
{
int bytes = osibuffWrite(pipe, buf, size);
if (bytes < 0)
return -1;
len += bytes;
size -= bytes;
buf = (const char *)buf + bytes;
if (size == 0 )
break;
}
return len;
}
int osibuffReadAvail(osi_buff_t *pipe)
{
if (pipe == NULL)
return -1;
uint32_t critical = ql_rtos_enter_critical();
unsigned bytes = pipe->wr - pipe->rd;
ql_rtos_exit_critical(critical);
return bytes;
}
int osibuffRead(osi_buff_t *pipe, void *buf, unsigned size)
{
if (size == 0)
return 0;
if (pipe == NULL || buf == NULL)
return -1;
uint32_t critical = ql_rtos_enter_critical();
unsigned bytes = pipe->wr - pipe->rd;
unsigned len = OSI_MIN(unsigned, size, bytes);
unsigned rd = pipe->rd;
if (len == 0)
{
ql_rtos_exit_critical(critical);
return 0;
}
unsigned offset = rd % pipe->size;
unsigned tail = pipe->size - offset;
if (tail >= len)
{
memcpy(buf, &pipe->data[offset], len);
}
else
{
memcpy(buf, &pipe->data[offset], tail);
memcpy((char *)buf + tail, pipe->data, len - tail);
}
pipe->rd += len;
ql_rtos_exit_critical(critical);
return len;
}
int osibuffReadAll(osi_buff_t *pipe, void *buf, unsigned size, unsigned timeout)
{
if (size == 0)
return 0;
if (pipe == NULL || buf == NULL)
return -1;
unsigned len = 0;
for (;;)
{
int bytes = osibuffRead(pipe, buf, size);
if (bytes < 0)
return -1;
len += bytes;
size -= bytes;
buf = (char *)buf + bytes;
if (size == 0 )
break;
}
return len;
}
#endif
/*************************************************************************/
int userCallback(void *param, int param1, int param2, int param3, int data_len, const void *pcm_data)
{
#ifndef QL_TTS_RESAMPLE_TEST
int err;
err = ql_pcm_write(ql_player, (void *)pcm_data, data_len);
if(err <= 0)
{
QL_TTS_LOG("write data to PCM player failed");
return -1;
}
return 0;
#else
int err,cnt = 0;
tts_loacl_info_t *tts = &tts_loacl_info;
err = osibuffWriteAll(tts->pipe, pcm_data, data_len, QL_WAIT_FOREVER); //降采样需要每次输入一包完整的PCM帧640字节,因此先缓存
tts_demo_no_err((err<0), goto exit, "cache data fail");
tts_demo_no_err(!((uint)tts->input_buffer & (uint)tts->output_buffer), goto exit, "invalid buffer");
while(1)
{
cnt = osibuffReadAvail(tts->pipe);
tts_demo_no_err((err<0), goto exit, "read avil fail");
if(cnt >= QUEC_16K_FRAME_LEN) //降采样需要每次输入一包完整的PCM帧640字节,因此先缓存,再每次读取640字节
{
cnt = osibuffReadAll(tts->pipe, (void *)tts->input_buffer, QUEC_16K_FRAME_LEN, QL_WAIT_FOREVER);
tts_demo_no_err((cnt!=QUEC_16K_FRAME_LEN), goto exit, "pipe read fail");
ql_aud_resampler_run(tts->filter, (short *)tts->input_buffer, (short *)tts->output_buffer); //开始降采样
err = ql_pcm_write(ql_player, (void *)tts->output_buffer, QUEC_8K_FRAME_LEN);
tts_demo_no_err((err!=QUEC_8K_FRAME_LEN), goto exit, "pcm write fail");
}
else
{
break;
}
}
return 0;
exit:
QL_TTS_LOG("tts callback failed");
return -1;
#endif
}
#ifdef QL_TTS_RESAMPLE_TEST
void TTS_RESAM_Init(void)
{
tts_loacl_info_t *tts = &tts_loacl_info;
tts->filter = (ql_resampler *)calloc(1, sizeof(ql_resampler));
if(!(tts->filter))
{
QL_TTS_LOG("no mem");
goto exit;
}
ql_aud_resampler_create(QUEC_BEFORE_SAMPLING, QUEC_AFTER_SAMPLING, QUEC_BEFORE_SAMPLING*20/1000, tts->filter);
tts->pipe = osibuffCreate(4*1024);
tts_demo_no_err(!tts->pipe, goto exit, "no mem");
tts->input_buffer = (char *)calloc(1, 640);
tts_demo_no_err(!tts->input_buffer, goto exit, "no mem");
tts->output_buffer = (char *)calloc(1, 640);
tts_demo_no_err(!tts->output_buffer, goto exit, "no mem");
return;
exit:
if(tts->filter)
{
ql_aud_resampler_destroy(tts->filter);
free(tts->filter);
tts->filter = NULL;
}
if(tts->pipe)
{
free(tts->pipe);
tts->pipe = NULL;
}
if(tts->input_buffer)
{
free(tts->input_buffer);
tts->input_buffer = NULL;
}
if(tts->output_buffer){
free(tts->output_buffer);
tts->output_buffer = NULL;
}
return;
}
#endif
void ql_tts_thread_demo_2(void *param)
{
int err = 0;
tts_demo_play_info_t info = {0};
ql_set_audio_path_earphone();
ql_aud_set_volume(QL_AUDIO_PLAY_TYPE_LOCAL, AUDIOHAL_SPK_VOL_11);
#ifdef QL_TTS_RESAMPLE_TEST
TTS_RESAM_Init();
#endif
poc_demo_test();
while(1)
{
err = ql_rtos_queue_wait(ql_tts_demo_queue, (uint8 *)&info, sizeof(tts_demo_play_info_t), QL_WAIT_FOREVER);
tts_demo_no_err(err, goto exit, "invalid queue");
if(!info.str || !info.len){
QL_TTS_LOG("invalid tts string");
continue;
}
ql_pcm_poc_init_ex();
err = ql_tts_init(userCallback);
tts_demo_no_err(err, goto exit, "tts init failed");
#if !QL_TTS_LANGUAGE_USE_ENGLISH //使用英文TTS库不需要设置编码
err = ql_tts_set_config_param(QL_TTS_CONFIG_ENCODING, info.encode);
tts_demo_no_err(err, goto exit, "config tts failed");
#endif
//设置使用数字播报
//err = ql_tts_set_config_param(QL_TTS_CONFIG_DIGITS,TTS_DIGIT_NUMBER);
//tts_demo_no_err(err, goto exit, "config tts failed");
err = ql_tts_start(info.str, info.len);
tts_demo_no_err(err, goto exit, "tts start failed");
while(ql_pcm_buffer_used(ql_player)) //in poc mode, player will not stop if not ql_aud_stop_poc_mode called
{
ql_rtos_task_sleep_ms(20); //wait the write buffer empty
}
exit:
if(info.str){
free(info.str);
}
QL_TTS_LOG("tts done");
ql_pcm_poc_deinit_ex();
ql_tts_deinit();
}
}
int ql_tts_init(pUserCallback mCallback)
{
tts_param_t tts_param = {0};
if(!mCallback){
return QL_TTS_INVALID_PARAM;
}
if(!ql_tts_is_running())
{
#if !QL_TTS_LANGUAGE_USE_ENGLISH
tts_param.resource = TTS_RESOURCE_16K_CN; //使用中文16k资源
#else
tts_param.resource = TTS_RESOURCE_16K_EN; //使用英文16k资源
#endif
#if QL_TTS_LOCATION==1 //使用的tts库在外部6线flash
tts_param.position = POSIT_EFS;
#elif QL_TTS_LOCATION==2 //使用的tts库在外部4线flash
tts_param.position = POSIT_EXNSFFS;
#else //默认使用的tts库在内置flash
tts_param.position = POSIT_INTERNAL_FS;
#endif
//int err = ql_tts_engine_init(userCallback); //若使用默认的中文16k资源且资源文件预置到内置flash, 则直接调用ql_tts_engine_init即可
int err = ql_tts_engine_init_ex(userCallback, &tts_param);
tts_demo_no_err(err, return err, "tts session begain failed");
}
else
{
QL_TTS_LOG("tts is running");
return QL_TTS_DEVICE_BUSY;
}
return QL_TTS_SUCCESS;
}
int ql_tts_deinit(void)
{
int err = 0;
err = ql_tts_end();
tts_demo_no_err(err, return err, "tts end failed");
return err;
}
int ql_tts_play(ql_tts_encoding_e encoding, const char* string, uint len)
{
tts_demo_play_info_t param = {0};
int err = 0;
if(encoding < QL_TTS_GBK || encoding > QL_TTS_UCS2 || !string || !len)
{
QL_TTS_LOG("invalid param");
return QL_TTS_INVALID_PARAM;
}
param.encode = encoding;
param.len = len;
param.str = calloc(1, len);
if(!param.str)
{
QL_TTS_LOG("tts no memory");
return QL_TTS_NO_MEMORY;
}
memcpy(param.str, string, len);
//这部分代码实现tts播放过程中新的tts语句打断直接播放修改
// if(ql_tts_is_running())
// {
// ql_tts_exit();
// }
err = ql_rtos_queue_release(ql_tts_demo_queue, sizeof(tts_demo_play_info_t), (uint8 *)&param, 0);
if(err)
{
free(param.str);
}
return err;
}
int ql_tts_play_english(const char* string, uint len)
{
tts_demo_play_info_t param = {0};
int err = 0;
if(!string || !len)
{
QL_TTS_LOG("invalid param");
return QL_TTS_INVALID_PARAM;
}
param.len = len;
param.str = calloc(1, len);
if(!param.str)
{
QL_TTS_LOG("tts no memory");
return QL_TTS_NO_MEMORY;
}
memcpy(param.str, string, len);
//这部分代码实现tts播放过程中新的tts语句打断直接播放修改
// if(ql_tts_is_running())
// {
// ql_tts_exit();
// }
err = ql_rtos_queue_release(ql_tts_demo_queue, sizeof(tts_demo_play_info_t), (uint8 *)&param, 0);
if(err)
{
free(param.str);
}
return err;
}
void ql_tts_demo2_init(void)
{
uint8_t err = QL_OSI_SUCCESS;
err = ql_rtos_queue_create(&ql_tts_demo_queue, sizeof(tts_demo_play_info_t), 10);
if (err != QL_OSI_SUCCESS)
{
QL_TTS_LOG("TTS queue created failed");
return;
}
err = ql_rtos_task_create(&ql_tts_demo_task2, QL_TTS_TASK_STACK, QL_TTS_TASK_PRIO-1, "ql_tts_task", ql_tts_thread_demo_2, NULL, 1);
if (err != QL_OSI_SUCCESS)
{
ql_rtos_queue_delete(ql_tts_demo_queue);
ql_tts_demo_queue = NULL;
QL_TTS_LOG("TTS demo task2 created failed");
return;
}
#if !QL_TTS_LANGUAGE_USE_ENGLISH //播放中文TTS
char *str1 = "支付宝收款 12345元";
char *str2 = "您已超速, 请减速";
char *str3 = "条形码为: 2 2 1 9 8 3 3 6 4 5 2 3 8 8"; //空格代表以号码的方式播报
uint16 ucs_str[8] = {0x6B22, 0x8FCE, 0x4F7F, 0x7528, 0x79FB, 0x8FDC, 0x6A21, 0x5757}; //欢迎使用移远模块
ql_tts_play(QL_TTS_UTF8, str1, strlen(str1));
ql_tts_play(QL_TTS_UTF8, str2, strlen(str2));
ql_tts_play(QL_TTS_UTF8, str3, strlen(str3));
ql_tts_play(QL_TTS_UCS2, (const char *)ucs_str, sizeof(ucs_str));
#else //播放英文TTS
char *str_eng = "The price of the shirt is $50, and the price of the computer is $1200";
ql_tts_play_english(str_eng, strlen(str_eng));
#endif
}
void poc_demo_test(void)
{
void *data = NULL;
int size, total_size = 0, cnt=0, write_cnt=0;
ql_pcm_poc_init_ex();
QL_TTS_LOG("##poc start");
ql_pcm_record_init_ex();
data = malloc(100*1024);
if(data == NULL)
{
goto exit;
}
QL_TTS_LOG("start read");
//start record
while(total_size < 80*1024)
{
size = ql_pcm_record_ex(data+total_size, 640);
if(size <= 0)
{
break;
}
total_size += size;
}
ql_pcm_record_deinit_ex();
QL_TTS_LOG("exit record");
if(total_size <= 0)
{
QL_TTS_LOG("read pcm failed");
goto exit;
}
QL_TTS_LOG("size is %d", total_size);
while(write_cnt < total_size)
{
if(total_size - write_cnt > PACKET_WRITE_MAX_SIZE) //单次最多可写 PACKET_WRITE_MAX_SIZE 字节
{
cnt = ql_pcm_play_ex(data+write_cnt, PACKET_WRITE_MAX_SIZE);
}
else
{
cnt = ql_pcm_play_ex(data+write_cnt, total_size - write_cnt);
}
if(cnt <= 0)
{
QL_TTS_LOG("write failed");
goto exit;
}
write_cnt += cnt;
}
while(ql_pcm_buffer_used(ql_player)) //in poc mode, player will not stop if not ql_aud_stop_poc_mode called
{
ql_rtos_task_sleep_ms(20); //wait the write buffer empty
}
ql_pcm_play_stop_ex();
exit:
if(data != NULL)
{
free(data);
data = NULL;
}
ql_pcm_poc_deinit_ex();
}
/*************************************************** Audio API 封装 ***************************************************************************/
void ql_pcm_poc_init_ex(void)
{
#ifdef QL_TTS_RESAMPLE_TEST
QL_PCM_CONFIG_T pcm_config = {1, 8000, 0};
#else
QL_PCM_CONFIG_T pcm_config = {1, 16000, 0};
#endif
if(ql_recorder == NULL)
{
ql_recorder = ql_aud_pcm_open(&pcm_config, QL_AUDIO_FORMAT_PCM, QL_PCM_BLOCK_FLAG|QL_PCM_READ_FLAG, QL_PCM_POC);
if(ql_recorder == NULL)
{
return;
}
}
if(ql_player == NULL)
{
ql_player = ql_aud_pcm_open(&pcm_config, QL_AUDIO_FORMAT_PCM, QL_PCM_BLOCK_FLAG|QL_PCM_WRITE_FLAG, QL_PCM_POC);
if(ql_player == NULL)
{
return;
}
}
ql_aud_start_poc_mode(QL_POC_TYPE_HALF_DUPLEX);
ql_aud_poc_switch(QL_POC_MODE_PLAY);
}
int ql_pcm_play_ex(uint8_t *data, uint32_t count)
{
return ql_pcm_write(ql_player, data, count);
}
void ql_pcm_play_stop_ex(void)
{
ql_pcm_buffer_reset(ql_player);
}
void ql_pcm_record_init_ex(void)
{
ql_aud_poc_switch(QL_POC_MODE_REC);
}
void ql_pcm_record_deinit_ex(void)
{
ql_aud_poc_switch(QL_POC_MODE_PLAY);
}
int ql_pcm_record_ex(void *data, uint32_t count)
{
int cnt_read;
if(ql_recorder)
{
cnt_read = ql_pcm_read(ql_recorder, data, count);
}
else
{
cnt_read = -1;
}
return cnt_read;
}
void ql_pcm_poc_deinit_ex(void)
{
ql_aud_stop_poc_mode();
if(ql_player != NULL)
{
ql_pcm_close(ql_player);
ql_player = NULL;
}
if(ql_recorder != NULL)
{
ql_pcm_close(ql_recorder);
ql_recorder = NULL;
}
}