Commit 5352b4df authored by 957dd's avatar 957dd

加入了tts代码,还没加入使用

parent 4aa470f0
#include "audiotts_play.h"
#include "audioplay.h"
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#define TTS_ADUINO_PTAH "/home/orangepi/car/master/audio.mp3"
// 全局变量:存储 HTTP 响应数据
static char *http_response = NULL;
static size_t http_response_len = 0;
static bool audio_tts_index=0;
// HTTP 响应回调函数:接收服务器返回的 JSON 数据
static size_t http_write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
size_t total = size * nmemb;
// 扩容并拷贝响应数据
char *temp = realloc(http_response, http_response_len + total + 1);
if (!temp) {
my_zlog_error("Realloc response buffer failed");
return 0;
}
http_response = temp;
memcpy(http_response + http_response_len, ptr, total);
http_response_len += total;
http_response[http_response_len] = '\0'; // 末尾加结束符
return total;
}
// 生成 UUID(去除连字符,保证 reqid 唯一性)
void generate_uuid(char *buf, size_t len) {
if (len < 33) return; // UUID 去除连字符后 32 位
uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse(uuid, buf);
// 移除连字符
char *p = strchr(buf, '-');
while (p) {
memmove(p, p + 1, strlen(p));
p = strchr(buf, '-');
}
}
// Base64 解码(将响应中的音频数据解码为二进制)
unsigned char *base64_decode(const char *input, size_t *output_len) {
BIO *bio, *b64;
size_t len = strlen(input);
unsigned char *buffer = (unsigned char *)malloc(len * 3 / 4 + 1024); // 预留足够空间
if (!buffer) return NULL;
// 初始化 OpenSSL Base64 解码
OpenSSL_add_all_algorithms(); // 初始化 OpenSSL 算法(避免解码失败)
b64 = BIO_new(BIO_f_base64());
bio = BIO_new_mem_buf(input, len);
bio = BIO_push(b64, bio);
// 禁用换行符处理(关键:避免 Base64 字符串含换行导致解码失败)
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
*output_len = BIO_read(bio, buffer, len * 3 / 4 + 1024);
// 清理资源
BIO_free_all(bio);
// 调整缓冲区大小为实际解码长度
buffer = realloc(buffer, *output_len);
return buffer;
}
/**
* 保存音频数据到文件(目录已存在)
* @param data 音频数据指针
* @param len 音频数据长度
* @param filepath 完整文件路径
* @return 0成功,1失败
*/
int save_audio_file(const void *data, size_t len, const char *filepath) {
if (!data || len == 0 || !filepath) {
my_zlog_error("Invalid parameters for save_audio_file\n");
return 1;
}
FILE *fp = fopen(filepath, "wb");
if (!fp) {
my_zlog_error( "Open file %s failed: %s\n", filepath, strerror(errno));
return 1;
}
size_t written = fwrite(data, 1, len, fp);
fclose(fp);
if (written != len) {
my_zlog_error("Write file %s incomplete: %zu/%zu bytes", filepath, written, len);
remove(filepath); // 删除不完整的文件
return 1;
}
my_zlog_info("Success! Audio saved to: %s (%zu bytes)", filepath, written);
return 0;
}
int audio_tts(const char *text) {
// 1. 配置参数(替换为你的真实信息,已验证有效)
const char *appid = "5745089507"; // 你的火山引擎 AppID
const char *access_token = "0BeoBb_DOjrxfM4aTsJfUFVVS0k5V9mt"; // 你的访问令牌
const char *voice_type = "zh_female_linjianvhai_moon_bigtts"; // 非流式音色
const char *cluster = "volcano_tts"; // 固定值(必选)
const char *uid = "user123456"; // 任意非空字符串
const char *encoding = "mp3"; // 输出格式(mp3/wav/pcm/ogg_opus)
const char *endpoint = "https://openspeech.bytedance.com/api/v1/tts"; // HTTP 接口地址
// 2. 生成唯一 reqid(每次调用必须不同)
char reqid[33] = {0};
generate_uuid(reqid, sizeof(reqid));
my_zlog_info("Generated ReqID: %s", reqid);
// 3. 构建请求 JSON(严格遵循接口参数格式)
cJSON *root = cJSON_CreateObject();
if (!root) {
my_zlog_error("Create JSON root failed");
return 1;
}
// app 节点(必选)
cJSON *app = cJSON_CreateObject();
cJSON_AddStringToObject(app, "appid", appid);
cJSON_AddStringToObject(app, "token", access_token);
cJSON_AddStringToObject(app, "cluster", cluster);
cJSON_AddItemToObject(root, "app", app);
// user 节点(必选)
cJSON *user = cJSON_CreateObject();
cJSON_AddStringToObject(user, "uid", uid);
cJSON_AddItemToObject(root, "user", user);
// audio 节点(必选)
cJSON *audio = cJSON_CreateObject();
cJSON_AddStringToObject(audio, "voice_type", voice_type);
cJSON_AddStringToObject(audio, "encoding", encoding);
cJSON_AddNumberToObject(audio, "speed_ratio", 1.0); // 语速(0.1~2.0)
cJSON_AddNumberToObject(audio, "rate", 16000); // 采样率(默认24000)
cJSON_AddNumberToObject(audio, "loudness_ratio", 1.0); // 音量(0.5~2.0)
cJSON_AddBoolToObject(audio, "enable_emotion", true); // 开启情感
cJSON_AddStringToObject(audio, "emotion", "gentle"); // 情感类型(gentle=温柔)
cJSON_AddNumberToObject(audio, "emotion_scale", 4.0); // 情感强度(1~5,默认4)
cJSON_AddItemToObject(root, "audio", audio);
// request 节点(必选)
cJSON *request = cJSON_CreateObject();
cJSON_AddStringToObject(request, "reqid", reqid);
cJSON_AddStringToObject(request, "text", text);
cJSON_AddStringToObject(request, "operation", "query"); // HTTP 非流式固定为 query
cJSON_AddStringToObject(request, "extra_param", "{\"disable_markdown_filter\":true}"); // 附加参数
cJSON_AddItemToObject(root, "request", request);
// 生成 JSON 字符串(无格式,减少传输体积)
char *payload = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
if (!payload) {
my_zlog_error("Generate JSON payload failed");
return 1;
}
my_zlog_info("Request Payload: %s", payload);
// 4. 初始化 curl 并配置 HTTP 请求
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *curl = curl_easy_init();
if (!curl) {
my_zlog_error("Initialize curl failed");
free(payload);
return 1;
}
// 设置请求头(关键:Authorization 分号分隔,Content-Type 为 JSON)
struct curl_slist *headers = NULL;
char auth_header[256] = {0};
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer;%s", access_token);
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Accept: application/json");
// curl 核心配置
curl_easy_setopt(curl, CURLOPT_URL, endpoint);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L); // POST 方法
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); // 请求体
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(payload));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_write_callback); // 接收响应回调
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 开发环境禁用 SSL 证书校验(生产环境建议启用)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L); // 超时时间 15 秒(避免网络慢导致超时)
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); // 连接超时 10 秒
// 5. 执行 HTTP 请求
printf("Sending request to %s...\n", endpoint);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform failed: %s\n", curl_easy_strerror(res));
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 6. 解析 HTTP 响应(JSON 格式)
printf("Response Length: %zu bytes\n", http_response_len);
if (http_response_len == 0) {
fprintf(stderr, "Empty response from server\n");
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 解析 JSON 响应
cJSON *response_json = cJSON_Parse(http_response);
if (!response_json) {
fprintf(stderr, "Parse response JSON failed: %s\n", cJSON_GetErrorPtr());
free(payload);
free(http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// // 打印完整响应(便于调试,可根据需要注释)
// char *response_str = cJSON_Print(response_json);
// printf("Full Response JSON (abbreviated):\n%s\n", response_str);
// free(response_str);
// 提取响应状态(火山引擎该接口成功码:3000 + message=Success)
cJSON *code = cJSON_GetObjectItem(response_json, "code");
cJSON *message = cJSON_GetObjectItem(response_json, "message");
if (!code || code->valueint != 3000 || !message || strcmp(message->valuestring, "Success") != 0) {
fprintf(stderr, "Request failed! Code: %d, Message: %s\n",
code ? code->valueint : -1,
message ? message->valuestring : "No message");
cJSON_Delete(response_json);
free(payload);
free(http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
printf("Request succeeded! Code: %d, Message: %s\n", code->valueint, message->valuestring);
// 关键:直接提取 data 字段的 Base64 音频字符串(接口实际返回格式)
cJSON *audio_base64 = cJSON_GetObjectItem(response_json, "data");
if (!audio_base64 || !audio_base64->valuestring || strlen(audio_base64->valuestring) == 0) {
fprintf(stderr, "Response missing audio data (data field is empty)\n");
cJSON_Delete(response_json);
free(payload);
free(http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_info("Audio Base64 Length: %zu bytes\n", strlen(audio_base64->valuestring));
// 7. Base64 解码音频数据
size_t audio_len = 0;
unsigned char *audio_data = base64_decode(audio_base64->valuestring, &audio_len);
if (!audio_data || audio_len == 0) {
my_zlog_error("Base64 decode audio failed");
cJSON_Delete(response_json);
free(payload);
free(http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_info("Decoded Audio Length: %zu bytes", audio_len);
if(save_audio_file(audio_data, audio_len, TTS_ADUINO_PTAH) != 0) {//保存文件
my_zlog_error("save_audio_file fail");
}
// 9. 清理资源(避免内存泄漏)
cJSON_Delete(response_json);
free(audio_data);
free(payload);
free(http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 0;
}
void set_audio_tts_index(bool index){
audio_tts_index=index;
}
bool get_audio_tts_index(){
return audio_tts_index;
}
#ifndef AUDIOTTS_PLAY_H
#define AUDIOTTS_PLAY_H
#include "common.h"
void set_audio_tts_index(bool index);
int audio_tts(const char *text);
bool get_audio_tts_index();
......@@ -8,5 +8,6 @@
#include "tank_angle.h"
#include "temperature.h"
#include "warn.h"
#include "audiotts_play.h"
#endif
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment