EC600U_esp32_iap_uart/tts/tts_demo2.c

773 lines
21 KiB
C
Raw Permalink Normal View History

2024-02-05 17:39:56 +08:00
/*================================================================
Copyright (c) 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved.
Quectel Wireless Solution Proprietary and Confidential.
=================================================================*/
/************************************************************************************************************
demo将tts的apiaudio的播放函数一起进行封装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 01CONFIG_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 01CONFIG_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;
}
}