Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
car-controlserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wenzhongjian
car-controlserver
Commits
5352b4df
Commit
5352b4df
authored
Nov 08, 2025
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
加入了tts代码,还没加入使用
parent
4aa470f0
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
321 additions
and
0 deletions
+321
-0
audiotts_play.c
drivers/sensors/audiotts_play.c
+309
-0
audiotts_play.h
drivers/sensors/audiotts_play.h
+10
-0
sensors_common.h
drivers/sensors/sensors_common.h
+2
-0
No files found.
drivers/sensors/audiotts_play.c
0 → 100644
View file @
5352b4df
#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
;
}
drivers/sensors/audiotts_play.h
0 → 100644
View file @
5352b4df
#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
();
drivers/sensors/sensors_common.h
View file @
5352b4df
...
@@ -8,5 +8,6 @@
...
@@ -8,5 +8,6 @@
#include "tank_angle.h"
#include "tank_angle.h"
#include "temperature.h"
#include "temperature.h"
#include "warn.h"
#include "warn.h"
#include "audiotts_play.h"
#endif
#endif
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment