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
dced7c62
Commit
dced7c62
authored
Jul 17, 2026
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
优化了播报与游览器之间的竞争问题
parent
055aa314
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
1612 additions
and
97 deletions
+1612
-97
main
build/main
+0
-0
device_init.c
drivers/gpio/device_init.c
+5
-5
device_init.c.bak-boolcase-20260716-063617
drivers/gpio/device_init.c.bak-boolcase-20260716-063617
+278
-0
audioplay.c
drivers/sensors/audioplay.c
+85
-22
audioplay.c.bak-audio-preempt-20260717
drivers/sensors/audioplay.c.bak-audio-preempt-20260717
+551
-0
audioplay.h
drivers/sensors/audioplay.h
+8
-2
audioplay.h.bak-audio-preempt-20260717
drivers/sensors/audioplay.h.bak-audio-preempt-20260717
+16
-0
audiotts_play.c
drivers/sensors/audiotts_play.c
+9
-41
audiotts_play.c.bak-audio-preempt-20260717
drivers/sensors/audiotts_play.c.bak-audio-preempt-20260717
+536
-0
http_config_mqtt.h
modules/http/http_config_mqtt.h
+1
-1
mqtt_infor_handle.c
modules/mqtt/mqtt_infor_handle.c
+5
-5
mqtt_verify.c
modules/mqtt/mqtt_verify.c
+11
-11
rtc_client.c
modules/webrtcpush/rtc_client.c
+53
-4
webrtcpush_config.h
modules/webrtcpush/webrtcpush_config.h
+5
-3
ws_signaling.c
modules/webrtcpush/ws_signaling.c
+48
-2
zlog.conf
zlog.conf
+1
-1
No files found.
build/main
View file @
dced7c62
No preview for this file type
drivers/gpio/device_init.c
View file @
dced7c62
...
@@ -236,9 +236,9 @@ static const deviceconfig_t s_device_configs[] = {
...
@@ -236,9 +236,9 @@ static const deviceconfig_t s_device_configs[] = {
bool
get_array_length
(
const
int
*
arr
)
{
bool
get_array_length
(
const
int
*
arr
)
{
if
(
arr
[
0
]
!=
-
1
)
{
// 遇到-1停止计数
if
(
arr
[
0
]
!=
-
1
)
{
// 遇到-1停止计数
return
TRUE
;
return
true
;
}
}
return
FALSE
;
return
false
;
}
}
void
device_init
(
int
device_id
)
{
void
device_init
(
int
device_id
)
{
...
@@ -258,9 +258,9 @@ void device_init(int device_id) {
...
@@ -258,9 +258,9 @@ void device_init(int device_id) {
}
}
// 执行初始化流程
// 执行初始化流程
if
(
get_array_length
(
config
->
gpio_pins
)
==
TRUE
)
init_gpiowpi
(
config
->
gpio_pins
);
// GPIO初始化
if
(
get_array_length
(
config
->
gpio_pins
))
init_gpiowpi
(
config
->
gpio_pins
);
// GPIO初始化
if
(
get_array_length
(
config
->
gpio_pwms
)
==
TRUE
)
init_gpiopwm
(
config
->
gpio_pwms
);
// GPIOsoft_pwm初始化
if
(
get_array_length
(
config
->
gpio_pwms
))
init_gpiopwm
(
config
->
gpio_pwms
);
// GPIOsoft_pwm初始化
if
(
get_array_length
(
config
->
gpio_inputs
)
==
TRUE
)
init_gpio_input
(
config
->
gpio_inputs
);
if
(
get_array_length
(
config
->
gpio_inputs
))
init_gpio_input
(
config
->
gpio_inputs
);
g_device_type
=
config
->
emergency_code
;
g_device_type
=
config
->
emergency_code
;
config
->
device_pwm_init
();
// PWM初始化
config
->
device_pwm_init
();
// PWM初始化
config
->
device_control_stop
();
// 速度控制初始化
config
->
device_control_stop
();
// 速度控制初始化
...
...
drivers/gpio/device_init.c.bak-boolcase-20260716-063617
0 → 100644
View file @
dced7c62
#include "device_init.h"
#include "devcontrol_common.h"
#include "common.h"
#include "gpio_init.h"
#include "car0107_idle.h"
int g_device_delay_back_count =0;//设备计时,比如坦克打击倒退逻辑
int g_device_type =0; //设备详细类型,让其他地方判断,比如tank0202、car0101
static const deviceconfig_t s_device_configs[] = {
// car0101配置
{
.device_id = DEVICE_CAR0101,
.device_name = "car0101",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26, 27, -1},
.gpio_pwms = {-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0101_middle_pwm,
.emergency_code = 101
},
// car0102配置
{
.device_id = DEVICE_CAR0102,
.device_name = "car0102",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26, 27, -1},
.gpio_pwms = {5, 7, -1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0102_speed_stop,
.emergency_code = 102
},
// car0103配置(根据您的需求补充)
{
.device_id = DEVICE_CAR0103,
.device_name = "car0103",
.gpio_pins = {5, 6, 7, 9 , 10, 16, 20, 22, 23, 24, 25, 26, 27,-1},/* 补充GPIO引脚 */
.gpio_pwms = {-1},
.gpio_inputs={11,12,14,17,18,20,-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0103_middle,/* 补充速度控制函数 */
.emergency_code = 103
},
// car0104配置(根据您的需求补充)
{
.device_id = DEVICE_CAR0104,
.device_name = "car0104",
.gpio_pins = {5, 6, 7, 9,10, 16, 20, 22, 23, 24, 25, 26, 27,-1},/* 补充GPIO引脚 */
.gpio_pwms = {-1},
.gpio_inputs={11,12,14,20,-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0104_stop,/* 补充速度控制函数 */
.emergency_code = 104
},
// car0105配置(根据您的需求补充)
{
.device_id = DEVICE_CAR0105,
.device_name = "car0105",
.gpio_pins = {5, 6, 7, 9,10, 16, 20, 22, 23, 24, 25, 26, -1},/* 补充GPIO引脚 */
.gpio_pwms = {27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0101_middle_pwm,/* 补充速度控制函数 */
.emergency_code = 105
},
// car0106 F1赛车(双电机大板): 软PWM 5/7/24/26 对应前后左右
{
.device_id = DEVICE_CAR0106,
.device_name = "car0106",
.gpio_pins = {-1},
.gpio_pwms = {5, 7, 24, 26, -1},
.gpio_inputs={-1},
.device_pwm_init = car0106_pwm_init_empty,
.device_control_stop = car0106_stop,
.emergency_code = 106
},
// car0107 大众POLO R(与0101相同硬件,前后方向相反)
{
.device_id = DEVICE_CAR0107,
.device_name = "car0107",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26, 27, -1},
.gpio_pwms = {-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0107_middle_pwm,
.emergency_code = 107
},
// tank0202配置(根据您的需求补充)
{
.device_id = DEVICE_TANK0202,
.device_name = "tank0202",
.gpio_pins = {6, 16, 20, 22,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24,26, 27,-1},
.gpio_inputs={12,-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = tank0202_middle,/* 补充速度控制函数 */
.emergency_code = 202
},
{
.device_id = DEVICE_TANK0203,
.device_name = "tank0203",
.gpio_pins = {6, 16, 20, 22,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24, 26, 27,-1},
.gpio_inputs={12,-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = tank0203_middle,/* 补充速度控制函数 */
.emergency_code = 203
},
{
.device_id = DEVICE_TANK0204,
.device_name = "tank0204",
.gpio_pins = {6, 16, 20, 22,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24, 26, 27,-1},
.gpio_inputs={12,-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = tank0204_stop,/* 补充速度控制函数 */
.emergency_code = 204
},
{
.device_id = DEVICE_TANK0206,
.device_name = "tank0206",
.gpio_pins = {6, 16, 20, 22, 23,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24,26, 27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = tank0206_middle,/* 补充速度控制函数 */
.emergency_code = 206
},
{
.device_id = DEVICE_TANK0207,
.device_name = "tank0207",
.gpio_pins = {6, 16, 20, 22, 23,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24,26, 27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = tank0207_middle,/* 补充速度控制函数 */
.emergency_code = 207
},
{
.device_id = DEVICE_SHIP0301,
.device_name = "ship0301",
.gpio_pins = {6, 16, 20, 22, 23,-1},/* 补充GPIO引脚 */
.gpio_pwms = {5 , 7 ,24,26, 27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = ship0301_stop,/* 补充速度控制函数 */
.emergency_code = 301
},
{
.device_id = DEVICE_SHIP0302,
.device_name = "ship0302",
.gpio_pins = {6, 16, 20, 22, 23,-1},
.gpio_pwms = {5 , 7 ,24,26, 27,-1},
.gpio_inputs={-1},
.device_pwm_init = device_ship0302_init,
.device_control_stop = ship0302_stop,
.emergency_code = 302
},
{
.device_id = DEVICE_PAO_PTZ0401,
.device_name = "ptz0401",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26,27,-1},/* 补充GPIO引脚 */
.gpio_pwms = { -1},
.gpio_inputs={-1},
.device_pwm_init = ptz_driver_pwm_hz,
.device_control_stop = ptz_driver_init,
.emergency_code = 401
},
{
.device_id = DEVICE_PAO_PTZ0404,
.device_name = "ptz0404",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26,27,-1},/* 补充GPIO引脚 */
.gpio_pwms = { -1},
.gpio_inputs={-1},
.device_pwm_init = ptz_driver_pwm_hz,
.device_control_stop = ptz_driver_init,
.emergency_code = 404
},
{
.device_id = DEVICE_PAO_PTZ0405,
.device_name = "ptz0405",
.gpio_pins = {5, 7, 10, 16, 20, 22, 23, 24, 25, 26,27,-1},
.gpio_pwms = {-1},
.gpio_inputs={-1},
.device_pwm_init = ptz_driver_pwm_hz,
.device_control_stop = ptz_driver_init,
.emergency_code = 405
},
{
.device_id = DEVICE_PAO_PTZ0406,
.device_name = "ptz0406",
.gpio_pins = {5, 7, 10, 16, 20, 22, 23, 24, 25, 26,27,-1},
.gpio_pwms = {-1},
.gpio_inputs={-1},
.device_pwm_init = ptz_driver_pwm_hz,
.device_control_stop = ptz_driver_init,
.emergency_code = 406
},
{
.device_id = DEVICE_PG_GPS0403,
.device_name = "gps0403",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26,-1},/* 补充GPIO引脚 */
.gpio_pwms = { 27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0101_middle_pwm,/* 补充速度控制函数 */
.emergency_code = 403
},
{
.device_id = DEVICE_ROBOT_DOG0501,
.device_name = "dog0501",
.gpio_pins = {6, 10, 16, 20, 22, 23, 25,-1},/* 补充GPIO引脚 */
.gpio_pwms = { 5, 7,24, 26,27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = car0101_middle_pwm,/* 补充速度控制函数 */
.emergency_code = 501
},
{
.device_id = DEVICE_ROBOT_ARM0502,
.device_name = "arm0502",
.gpio_pins = {5, 6, 7, 10, 16, 20, 22, 23, 24, 25, 26,-1},
.gpio_pwms = { 27,-1},
.gpio_inputs={-1},
.device_pwm_init = physics_pwm_init,
.device_control_stop = arm0502_control_stop_init,
.emergency_code = 502
},
// 结束标记
{ .device_id = -1 }
};
bool get_array_length(const int* arr) {
if(arr[0] != -1) { // 遇到-1停止计数
return TRUE;
}
return FALSE;
}
void device_init(int device_id) {
const deviceconfig_t *config = NULL;
// 查找设备配置
for(int i = 0; s_device_configs[i].device_id != -1; i++) {
if(s_device_configs[i].device_id == device_id) {
config = &s_device_configs[i];
break;
}
}
if(!config) {
my_zlog_error("Error: Device ID %d not found!", device_id);
return;
}
// 执行初始化流程
if(get_array_length(config->gpio_pins)==TRUE)init_gpiowpi(config->gpio_pins); // GPIO初始化
if(get_array_length(config->gpio_pwms)==TRUE)init_gpiopwm(config->gpio_pwms); // GPIOsoft_pwm初始化
if(get_array_length(config->gpio_inputs)==TRUE) init_gpio_input(config->gpio_inputs);
g_device_type =config->emergency_code;
config->device_pwm_init(); // PWM初始化
config->device_control_stop(); // 速度控制初始化
if (device_id == DEVICE_CAR0102) {
car0102_pin27_startup(device_id);
}
if (device_id == DEVICE_CAR0107) {
car0107_idle_startup(device_id);
}
my_zlog_debug("%s initialized successfully!", config->device_name);
}
\ No newline at end of file
drivers/sensors/audioplay.c
View file @
dced7c62
...
@@ -14,7 +14,7 @@
...
@@ -14,7 +14,7 @@
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_
LOCAL_PLAY_TIMEOUT_SEC 8
#define AUDIO_
ANNOUNCEMENT_TIMEOUT_SEC 20
static
int
s_audio_status
=
7
;
static
int
s_audio_status
=
7
;
static
char
s_urlbuf
[
512
];
static
char
s_urlbuf
[
512
];
...
@@ -189,36 +189,104 @@ static void shell_single_quote(char *out, size_t size, const char *in) {
...
@@ -189,36 +189,104 @@ static void shell_single_quote(char *out, size_t size, const char *in) {
out
[
pos
]
=
'\0'
;
out
[
pos
]
=
'\0'
;
}
}
static
int
play_local_audio_file
(
const
char
*
filepath
)
{
static
int
pulse_suspend_default_output
(
int
suspend
)
{
const
char
*
command
=
suspend
?
"pactl suspend-sink @DEFAULT_SINK@ 1 >/dev/null 2>&1"
:
"pactl suspend-sink @DEFAULT_SINK@ 0 >/dev/null 2>&1"
;
int
exit_code
=
audio_system_exit_code
(
system
(
command
));
if
(
exit_code
!=
0
)
{
my_zlog_warn
(
"%s浏览器音频输出失败 exit=%d"
,
suspend
?
"暂停"
:
"恢复"
,
exit_code
);
}
return
exit_code
;
}
int
audioplay_file_with_browser_preempt
(
const
char
*
filepath
,
double
volume
)
{
char
quoted_path
[
1024
];
char
quoted_path
[
1024
];
char
command
[
2048
];
char
command
[
2048
];
int
ret
;
int
ret
;
int
exit_code
;
int
exit_code
;
int
pulse_suspended
;
if
(
filepath
==
NULL
||
filepath
[
0
]
==
'\0'
)
{
return
-
1
;
}
shell_single_quote
(
quoted_path
,
sizeof
(
quoted_path
),
filepath
);
shell_single_quote
(
quoted_path
,
sizeof
(
quoted_path
),
filepath
);
volume
=
audioplay_volume_clamp
(
volume
);
/*
排队等待USB声卡: 如果audio_sink正在播放手机音频, 等它释放
*/
/*
先和项目内 DataChannel/本地音频排队,再让浏览器临时释放 USB 声卡。
*/
audio_sink_lock_alsa
();
audio_sink_lock_alsa
();
pulse_suspended
=
(
pulse_suspend_default_output
(
1
)
==
0
);
snprintf
(
command
,
sizeof
(
command
),
if
(
pulse_suspended
)
{
/* sync=true 防止 EOS 提前关 ALSA 丢尾部(只播前半段); channels=2 上混规避 USB 声卡单声道 ring_buffer CRITICAL */
snprintf
(
command
,
sizeof
(
command
),
"timeout %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! audioresample ! audio/x-raw,channels=2 ! alsasink device=%s sync=true >/dev/null 2>&1"
,
/* timeout 使用 KILL,保证卡死的播放进程到 20 秒时立即释放 ALSA。 */
AUDIO_LOCAL_PLAY_TIMEOUT_SEC
,
quoted_path
,
AUDIO_LOCAL_ALSA_DEVICE
);
"timeout -s KILL %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! volume volume=%.3f ! audioresample ! audio/x-raw,channels=2 ! alsasink device=%s sync=true >/dev/null 2>&1"
,
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
quoted_path
,
volume
,
AUDIO_LOCAL_ALSA_DEVICE
);
}
else
{
/* PulseAudio 无法暂停时走其默认输出,避免直接打开同一声卡导致 device busy。 */
snprintf
(
command
,
sizeof
(
command
),
"timeout -s KILL %ds ffplay -nodisp -autoexit -loglevel warning -af
\"
volume=%.3f
\"
%s >/dev/null 2>&1"
,
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
volume
,
quoted_path
);
}
ret
=
system
(
command
);
ret
=
system
(
command
);
exit_code
=
audio_system_exit_code
(
ret
);
exit_code
=
audio_system_exit_code
(
ret
);
if
(
exit_code
!=
0
)
{
/* 无论正常结束、播放失败还是 timeout,退出前都恢复浏览器音频。 */
my_zlog_warn
(
"本地音频 GStreamer 播放失败 exit=%d,尝试 ffplay: %s"
,
if
(
pulse_suspended
)
{
exit_code
,
filepath
);
pulse_suspend_default_output
(
0
);
}
audio_sink_unlock_alsa
();
if
(
exit_code
==
137
)
{
my_zlog_warn
(
"播报超过 %d 秒,已强制停止并释放声卡: %s"
,
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
filepath
);
}
return
exit_code
;
}
int
audioplay_url_with_browser_preempt
(
const
char
*
url
,
double
volume
)
{
char
quoted_url
[
1024
];
char
command
[
2048
];
int
exit_code
;
int
pulse_suspended
;
if
(
url
==
NULL
||
url
[
0
]
==
'\0'
)
{
return
-
1
;
}
shell_single_quote
(
quoted_url
,
sizeof
(
quoted_url
),
url
);
volume
=
audioplay_volume_clamp
(
volume
);
audio_sink_lock_alsa
();
pulse_suspended
=
(
pulse_suspend_default_output
(
1
)
==
0
);
if
(
pulse_suspended
)
{
snprintf
(
command
,
sizeof
(
command
),
snprintf
(
command
,
sizeof
(
command
),
"timeout %ds ffplay -nodisp -autoexit -loglevel warning %s"
,
"timeout -s KILL %ds ffmpeg -nostdin -hide_banner -loglevel warning -i %s -af
\"
volume=%.3f
\"
-f alsa %s >/dev/null 2>&1"
,
AUDIO_LOCAL_PLAY_TIMEOUT_SEC
,
quoted_path
);
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
quoted_url
,
volume
,
ret
=
system
(
command
);
AUDIO_LOCAL_ALSA_DEVICE
);
exit_code
=
audio_system_exit_code
(
ret
);
}
else
{
snprintf
(
command
,
sizeof
(
command
),
"timeout -s KILL %ds ffplay -nodisp -autoexit -loglevel warning -af
\"
volume=%.3f
\"
%s >/dev/null 2>&1"
,
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
volume
,
quoted_url
);
}
}
exit_code
=
audio_system_exit_code
(
system
(
command
));
if
(
pulse_suspended
)
{
pulse_suspend_default_output
(
0
);
}
audio_sink_unlock_alsa
();
audio_sink_unlock_alsa
();
if
(
exit_code
==
137
)
{
my_zlog_warn
(
"网络播报超过 %d 秒,已强制停止并释放声卡: %s"
,
AUDIO_ANNOUNCEMENT_TIMEOUT_SEC
,
url
);
}
return
exit_code
;
return
exit_code
;
}
}
...
@@ -291,26 +359,21 @@ void audioplay_send_mqtt() {
...
@@ -291,26 +359,21 @@ void audioplay_send_mqtt() {
//音频播放
//音频播放
void
audioplay_cycle
(){
void
audioplay_cycle
(){
char
command
[
2048
];
int
ret
;
int
ret
;
while
(
1
){
while
(
1
){
if
(
s_audio_status
==
0
){
if
(
s_audio_status
==
0
){
char
*
urlmoddle
=
s_urlbuf
;
char
*
urlmoddle
=
s_urlbuf
;
s_audio_volume
=
audioplay_volume_clamp
(
s_audio_volume
);
s_audio_volume
=
audioplay_volume_clamp
(
s_audio_volume
);
snprintf
(
command
,
sizeof
(
command
),
"sudo ffplay -nodisp -autoexit -loglevel quiet -af
\"
volume=%.3f
\"
\"
%s
\"
"
,
s_audio_volume
,
urlmoddle
);
my_zlog_debug
(
"播放地址: %s"
,
s_urlbuf
);
my_zlog_debug
(
"播放地址: %s"
,
s_urlbuf
);
my_zlog_debug
(
"执行播放命令: %s"
,
command
);
ret
=
audioplay_url_with_browser_preempt
(
urlmoddle
,
s_audio_volume
);
ret
=
system
(
command
);
if
(
ret
!=
0
)
{
if
(
ret
!=
0
)
{
my_zlog_error
(
"播放失败"
);
my_zlog_error
(
"播放失败"
);
s_audio_status
=
2
;
s_audio_status
=
2
;
}
}
if
(
WIFEXITED
(
ret
)
&&
WEXITSTATUS
(
ret
)
==
0
)
{
if
(
ret
==
0
)
{
my_zlog_debug
(
"播放已成功完成 : %s "
,
s_urlbuf
);
my_zlog_debug
(
"播放已成功完成 : %s "
,
s_urlbuf
);
s_audio_status
=
1
;
s_audio_status
=
1
;
}
else
{
}
else
{
...
@@ -334,7 +397,7 @@ void audioplay_cycle(){
...
@@ -334,7 +397,7 @@ void audioplay_cycle(){
if
(
local_play_pending
)
{
if
(
local_play_pending
)
{
int
exit_code
;
int
exit_code
;
my_zlog_info
(
"播放本地音频: %s"
,
local_filepath
);
my_zlog_info
(
"播放本地音频: %s"
,
local_filepath
);
exit_code
=
play_local_audio_file
(
local_filepath
);
exit_code
=
audioplay_file_with_browser_preempt
(
local_filepath
,
1
.
0
);
if
(
exit_code
==
0
)
{
if
(
exit_code
==
0
)
{
my_zlog_debug
(
"本地音频播放完成: %s"
,
local_filepath
);
my_zlog_debug
(
"本地音频播放完成: %s"
,
local_filepath
);
}
else
{
}
else
{
...
...
drivers/sensors/audioplay.c.bak-audio-preempt-20260717
0 → 100644
View file @
dced7c62
#include "common.h"
#include "audioplay.h"
#include "audio_sink.h" /* USB声卡排队锁 */
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include "audiotts_play.h"
#include "wifi_autoconfig.h"
#include <stdio.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_LOCAL_PLAY_TIMEOUT_SEC 8
static int s_audio_status=7;
static char s_urlbuf[512];
static char s_keybuf[256];
static double s_audio_volume=0.8;
#define AUDIO_DRIVING_LEVEL_MAX 30
static int s_local_play_pending = 0;
static char s_local_filepath[512];
static pthread_mutex_t s_local_play_mutex = PTHREAD_MUTEX_INITIALIZER;
static int local_is_cn_lang(const char *lang) {
return lang != NULL && (strcmp(lang, AUDIO_LANG_ZH) == 0 || strcmp(lang, "cn") == 0);
}
static int local_is_en_lang(const char *lang) {
return lang != NULL &&
(strcmp(lang, AUDIO_LANG_EN) == 0 || strcmp(lang, "en") == 0 || strcmp(lang, "EN") == 0);
}
static void local_resolve_lang(const char *language_override, char *out, size_t size) {
if (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0) {
snprintf(out, size, "%s", audio_get_language());
return;
}
if (local_is_cn_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_ZH);
return;
}
if (local_is_en_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_EN);
return;
}
snprintf(out, size, "%s", audio_get_language());
my_zlog_warn("2017 未知 language: %s,使用当前语言: %s", language_override, out);
}
static int local_resolve_filepath(const char *filename, const char *language_override,
char *out, size_t size) {
char lang[8];
int use_default_lang = (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0);
local_resolve_lang(language_override, lang, sizeof(lang));
if (local_is_cn_lang(lang)) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
snprintf(out, size, "%s/%s/%s", AUDIO_LOCAL_BASE, lang, filename);
if (access(out, F_OK) == 0) {
return 1;
}
if (use_default_lang) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
return 0;
}
static void local_queue_play(const char *filename, const char *language) {
char resolved[512];
if (!local_resolve_filepath(filename, language, resolved, sizeof(resolved))) {
my_zlog_warn("2017 本地音频不存在: %s", filename);
return;
}
pthread_mutex_lock(&s_local_play_mutex);
snprintf(s_local_filepath, sizeof(s_local_filepath), "%s", resolved);
s_local_play_pending = 1;
pthread_mutex_unlock(&s_local_play_mutex);
my_zlog_info("2017 已排队本地音频: %s", resolved);
}
void audioplay_local_mqtt_receive(cJSON *body) {
if (!cJSON_IsObject(body)) {
my_zlog_warn("2017 body 无效");
return;
}
cJSON *status = cJSON_GetObjectItemCaseSensitive(body, "status");
cJSON *level_item = cJSON_GetObjectItemCaseSensitive(body, "level");
cJSON *language = cJSON_GetObjectItemCaseSensitive(body, "language");
if (!cJSON_IsString(status) || status->valuestring == NULL) {
my_zlog_warn("2017 缺少 status");
return;
}
const char *lang = "default";
if (cJSON_IsString(language) && language->valuestring != NULL) {
lang = language->valuestring;
}
if (strcmp(status->valuestring, "speed") == 0) {
local_queue_play("Speedupforoneminute.mp3", lang);
return;
}
if (strcmp(status->valuestring, "drivinglevel") == 0) {
int level = 0;
if (cJSON_IsString(level_item) && level_item->valuestring != NULL) {
level = atoi(level_item->valuestring);
} else if (cJSON_IsNumber(level_item)) {
level = level_item->valueint;
} else {
my_zlog_warn("2017 drivinglevel 缺少 level");
return;
}
if (level < 1 || level > AUDIO_DRIVING_LEVEL_MAX) {
my_zlog_warn("2017 drivinglevel level 无效: %d (1-%d)",
level, AUDIO_DRIVING_LEVEL_MAX);
return;
}
char filename[48];
snprintf(filename, sizeof(filename), "DrivingLevel%d.mp3", level);
local_queue_play(filename, lang);
return;
}
my_zlog_warn("2017 未知 status: %s", status->valuestring);
}
static double audioplay_volume_clamp(double v) {
if (v < 0.0) {
return 0.0;
}
if (v > 2.0) {
return 2.0;
}
return v;
}
int audio_wheat_init();
static int audio_system_exit_code(int status) {
if (status == -1) {
return -1;
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
}
return -1;
}
static void shell_single_quote(char *out, size_t size, const char *in) {
size_t pos = 0;
if (!out || size == 0) {
return;
}
out[pos++] = '\'';
if (in) {
for (const char *p = in; *p && pos + 5 < size; p++) {
if (*p == '\'') {
const char *esc = "'\\''";
for (const char *e = esc; *e && pos + 1 < size; e++) {
out[pos++] = *e;
}
} else {
out[pos++] = *p;
}
}
}
if (pos + 1 < size) {
out[pos++] = '\'';
}
out[pos] = '\0';
}
static int play_local_audio_file(const char *filepath) {
char quoted_path[1024];
char command[2048];
int ret;
int exit_code;
shell_single_quote(quoted_path, sizeof(quoted_path), filepath);
/* 排队等待USB声卡: 如果audio_sink正在播放手机音频, 等它释放 */
audio_sink_lock_alsa();
snprintf(command, sizeof(command),
/* sync=true 防止 EOS 提前关 ALSA 丢尾部(只播前半段); channels=2 上混规避 USB 声卡单声道 ring_buffer CRITICAL */
"timeout %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! audioresample ! audio/x-raw,channels=2 ! alsasink device=%s sync=true >/dev/null 2>&1",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path, AUDIO_LOCAL_ALSA_DEVICE);
ret = system(command);
exit_code = audio_system_exit_code(ret);
if (exit_code != 0) {
my_zlog_warn("本地音频 GStreamer 播放失败 exit=%d,尝试 ffplay: %s",
exit_code, filepath);
snprintf(command, sizeof(command),
"timeout %ds ffplay -nodisp -autoexit -loglevel warning %s",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path);
ret = system(command);
exit_code = audio_system_exit_code(ret);
}
audio_sink_unlock_alsa();
return exit_code;
}
//接收音频播放
void audioplay_mqtt_receive(cJSON *json) {
// 解析"audioLink"字段(修正了原始JSON中的拼写错误)
cJSON *audio_link = cJSON_GetObjectItemCaseSensitive(json, "audioLink");
if (cJSON_IsString(audio_link) && (audio_link->valuestring != NULL)) {
my_zlog_debug("音频链接: %s", audio_link->valuestring);
char *url=audio_link->valuestring;
snprintf(s_urlbuf, sizeof(s_urlbuf), "%s", url);
} else {
my_zlog_warn("错误:无法解析音频链接字段");
}
// 解析"key"字段
cJSON *key_char = cJSON_GetObjectItemCaseSensitive(json, "key");
if (cJSON_IsString(key_char) && (key_char->valuestring != NULL)) {
my_zlog_debug("音频链接KEY: %s", key_char->valuestring);
char *key=key_char->valuestring;
snprintf(s_keybuf, sizeof(s_keybuf), "%s", key);
} else {
my_zlog_warn("错误:无法解析音频链接");
s_audio_status=5;
}
// 解析"status"字段
cJSON *s_audio_status_val = cJSON_GetObjectItemCaseSensitive(json, "status");
if (cJSON_IsNumber(s_audio_status_val)) {
my_zlog_debug("标志: %d", s_audio_status_val->valueint);
s_audio_status=s_audio_status_val->valueint;
} else {
my_zlog_warn("错误:无法解析标志字段");
}
// 解析"volume"字段
cJSON *volume = cJSON_GetObjectItemCaseSensitive(json, "volume");
if (cJSON_IsNumber(volume)) {
my_zlog_debug("声量大小: %.3f", volume->valuedouble);
s_audio_volume = audioplay_volume_clamp(volume->valuedouble);
} else {
my_zlog_warn("错误:无法解析声量字段");
}
}
//发送音频播放是否完毕
void audioplay_send_mqtt() {
cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject();
cJSON *head = cJSON_CreateObject();
// 添加各个字段到 JSON 对象
cJSON_AddStringToObject(body, "type", "audio");
cJSON_AddStringToObject(body, "audioLink", s_urlbuf);
cJSON_AddStringToObject(body, "key", s_keybuf);
cJSON_AddNumberToObject(body, "status", s_audio_status);
cJSON_AddNumberToObject(body, "volume", s_audio_volume);
cJSON_AddNumberToObject(head, "message_type",3001);
cJSON_AddItemToObject(root, "body", body);
cJSON_AddItemToObject(root, "head",head);
// 将 JSON 对象转换为字符串
char* json_string = cJSON_PrintUnformatted(root);
my_zlog_debug("%s",json_string);
mqtt_publish_to_all(mqtt_topic_pure_number(), json_string, 0);
free(json_string);
cJSON_Delete(root);
}
//音频播放
void audioplay_cycle(){
char command[2048];
int ret;
while(1){
if(s_audio_status==0){
char *urlmoddle=s_urlbuf;
s_audio_volume = audioplay_volume_clamp(s_audio_volume);
snprintf(command, sizeof(command),
"sudo ffplay -nodisp -autoexit -loglevel quiet -af \"volume=%.3f\" \"%s\"",
s_audio_volume,urlmoddle);
my_zlog_debug("播放地址: %s", s_urlbuf);
my_zlog_debug("执行播放命令: %s", command);
ret = system(command);
if (ret != 0) {
my_zlog_error("播放失败");
s_audio_status=2;
}
if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
my_zlog_debug("播放已成功完成 : %s ", s_urlbuf);
s_audio_status=1;
} else {
my_zlog_warn("播放失败或中断: %s ", s_urlbuf);
s_audio_status=2;
}
audioplay_send_mqtt();
}
char local_filepath[sizeof(s_local_filepath)];
int local_play_pending = 0;
pthread_mutex_lock(&s_local_play_mutex);
if (s_local_play_pending) {
s_local_play_pending = 0;
snprintf(local_filepath, sizeof(local_filepath), "%s", s_local_filepath);
local_play_pending = 1;
}
pthread_mutex_unlock(&s_local_play_mutex);
if (local_play_pending) {
int exit_code;
my_zlog_info("播放本地音频: %s", local_filepath);
exit_code = play_local_audio_file(local_filepath);
if (exit_code == 0) {
my_zlog_debug("本地音频播放完成: %s", local_filepath);
} else {
my_zlog_warn("本地音频播放失败 exit=%d: %s", exit_code, local_filepath);
}
}
video_tts_play();
delay_ms(100);
}
}
static void trim_line(char *s)
{
if (!s)
return;
s[strcspn(s, "\r\n")] = '\0';
}
static int pulse_pactl_exit_code(int status)
{
if (status == -1)
return -1;
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
static int pulse_output_means_already_loaded(const char *output)
{
if (!output || !output[0])
return 0;
return strstr(output, "already loaded") != NULL ||
strstr(output, "Already loaded") != NULL ||
strstr(output, "Module exists") != NULL;
}
static int pulse_module_exists(const char *module_name, const char *device)
{
FILE *fp = popen("pactl list modules short 2>/dev/null", "r");
char line[512];
char needle[96];
if (!fp)
return 0;
snprintf(needle, sizeof(needle), "device=%s", device);
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, module_name) != NULL && strstr(line, needle) != NULL) {
pclose(fp);
return 1;
}
}
pclose(fp);
return 0;
}
static int pulse_load_alsa_module(const char *module_name, const char *label)
{
char cmd[256];
char output[512];
char line[256];
if (pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)",
label, module_name, AUDIO_USB_ALSA_DEVICE);
return 0;
}
snprintf(cmd, sizeof(cmd), "pactl load-module %s device=%s 2>&1",
module_name, AUDIO_USB_ALSA_DEVICE);
FILE *fp = popen(cmd, "r");
if (!fp) {
my_zlog_warn("%s 注册失败: 无法执行 pactl (%s)", label, strerror(errno));
return -1;
}
output[0] = '\0';
while (fgets(line, sizeof(line), fp) != NULL) {
trim_line(line);
if (!line[0])
continue;
if (output[0]) {
strncat(output, "; ", sizeof(output) - strlen(output) - 1);
}
strncat(output, line, sizeof(output) - strlen(output) - 1);
}
int status = pclose(fp);
int exit_code = pulse_pactl_exit_code(status);
if (exit_code == 0) {
my_zlog_info("%s 注册成功 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ", pactl: " : "", output[0] ? output : "");
return 0;
}
if (pulse_output_means_already_loaded(output) ||
pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ": " : "", output[0] ? output : "");
return 0;
}
my_zlog_warn("%s 注册失败 (%s device=%s): exit=%d%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE, exit_code,
output[0] ? ", pactl: " : ", pactl 无输出",
output[0] ? output : "");
return -1;
}
int audio_wheat_init(){
return pulse_load_alsa_module("module-alsa-sink", "喇叭");
}
int audio_speaker_init() {
return pulse_load_alsa_module("module-alsa-source", "麦");
}
int audio_init(){
delay_s(1);
audio_wheat_init();
delay_s(1);
audio_speaker_init();
return 0;
}
#define CONFIG_FILE "/usr/share/pulseaudio/alsa-mixer/profile-sets/default.conf"
static const char *s_yundea_block =
"# 为 Yundea 1076 USB 二合一设备添加配置\n"
"[Mapping yundea-input]\n"
"device-strings = hw:%f,0\n"
"direction = input\n"
"priority = 100\n"
"channel-map = mono\n"
"\n"
"[Mapping yundea-output]\n"
"device-strings = hw:%f,0\n"
"direction = output\n"
"priority = 100\n"
"channel-map = left,right\n"
"\n"
"[Profile yundea-duplex]\n"
"input-mappings = yundea-input\n"
"output-mappings = yundea-output\n"
"priority = 200\n";
/*
*功能:加入usb声卡配置
*
*/
int audio_config_init() {
delay_s(12);
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp) {
perror("无法打开配置文件进行读取");
return 1;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(size + 1);
if (!buffer) {
my_zlog_error("内存分配失败");
fclose(fp);
return 1;
}
size_t result =fread(buffer, 1, size, fp);
if (result != size) {
my_zlog_warn("Error: Failed to read audio data");
}
buffer[size] = '\0';
fclose(fp);
// 检查是否包含完整配置块
if (strstr(buffer, s_yundea_block) != NULL) {
my_zlog_debug("配置文件中已包含指定配置,跳过添加。");
free(buffer);
return 0;
}
free(buffer);
my_zlog_info("未找到指定配置,正在追加到文件末尾...");
// 打开文件追加配置
fp = fopen(CONFIG_FILE, "a");
if (!fp) {
my_zlog_error("无法打开配置文件进行写入");
return 1;
}
fprintf(fp, "\n%s", s_yundea_block);
fclose(fp);
my_zlog_info("配置已成功追加。");
return 0;
}
drivers/sensors/audioplay.h
View file @
dced7c62
...
@@ -6,10 +6,17 @@ void audioplay_mqtt_receive(cJSON *body); //接收音频mqtt播放函数
...
@@ -6,10 +6,17 @@ void audioplay_mqtt_receive(cJSON *body); //接收音频mqtt播放函数
void
audioplay_local_mqtt_receive
(
cJSON
*
body
);
// 2017 有人驾驶本地音频
void
audioplay_local_mqtt_receive
(
cJSON
*
body
);
// 2017 有人驾驶本地音频
void
audioplay_cycle
();
//音频播放线程中函数
void
audioplay_cycle
();
//音频播放线程中函数
/*
* 播放播报文件:与项目内其它 ALSA 播放串行,并在播放期间暂停浏览器使用的
* PulseAudio 默认输出。播放完成或超过 20 秒后会恢复浏览器音频。
*/
int
audioplay_file_with_browser_preempt
(
const
char
*
filepath
,
double
volume
);
int
audioplay_url_with_browser_preempt
(
const
char
*
url
,
double
volume
);
int
audio_wheat_init
();
int
audio_wheat_init
();
int
audio_speaker_init
();
int
audio_speaker_init
();
int
audio_init
();
int
audio_init
();
int
audio_config_init
();
//加入配置
int
audio_config_init
();
//加入配置
#endif
#endif
\ No newline at end of file
drivers/sensors/audioplay.h.bak-audio-preempt-20260717
0 → 100644
View file @
dced7c62
#ifndef AUDIOPLAY_H__
#define AUDIOPLAY_H__
#include <cjson/cJSON.h>
void audioplay_mqtt_receive(cJSON *body); //接收音频mqtt播放函数
void audioplay_local_mqtt_receive(cJSON *body); // 2017 有人驾驶本地音频
void audioplay_cycle();//音频播放线程中函数
int audio_wheat_init();
int audio_speaker_init();
int audio_init();
int audio_config_init();//加入配置
#endif
\ No newline at end of file
drivers/sensors/audiotts_play.c
View file @
dced7c62
...
@@ -470,46 +470,15 @@ void video_tts_play() {
...
@@ -470,46 +470,15 @@ void video_tts_play() {
s_audio_tts_index
=
0
;
s_audio_tts_index
=
0
;
return
;
return
;
}
}
pid_t
pid
=
fork
();
/* audioplay_cycle 本身运行在音频线程,这里同步等待可确保声卡一定被恢复。 */
if
(
pid
==
0
)
{
int
exit_code
=
audioplay_file_with_browser_preempt
(
TTS_ADUINO_PTAH
,
s_volume
);
// 子进程:创建新的会话组,脱离父进程组
if
(
exit_code
==
0
)
{
setsid
();
// 关键!创建新的会话组
my_zlog_info
(
"TTS 播报完成, volume: %.1f"
,
s_volume
);
}
else
{
char
command
[
256
];
my_zlog_warn
(
"TTS 播报失败或超时 exit=%d, volume: %.1f"
,
exit_code
,
s_volume
);
snprintf
(
command
,
sizeof
(
command
),
"ffmpeg -i "
TTS_ADUINO_PTAH
" -af
\"
volume=%.1f
\"
-f alsa default 2>/dev/null"
,
s_volume
);
// 重定向标准输入输出,避免占用终端
if
(
freopen
(
"/dev/null"
,
"r"
,
stdin
)
==
NULL
)
{
my_zlog_error
(
"重定向stdin失败: %s"
,
strerror
(
errno
));
// 可以选择退出或继续
}
if
(
freopen
(
"/dev/null"
,
"w"
,
stdout
)
==
NULL
)
{
my_zlog_error
(
"重定向stdout失败: %s"
,
strerror
(
errno
));
}
if
(
freopen
(
"/dev/null"
,
"w"
,
stderr
)
==
NULL
)
{
my_zlog_error
(
"重定向stderr失败: %s"
,
strerror
(
errno
));
}
int
res
=
system
(
command
);
_exit
(
res
);
// 使用_exit避免清理父进程资源
}
else
if
(
pid
>
0
)
{
// 父进程:非阻塞等待,避免僵尸进程
int
status
;
waitpid
(
pid
,
&
status
,
WNOHANG
);
// 不阻塞的等待
my_zlog_debug
(
"启动独立播放进程 PID: %d"
,
pid
);
s_audio_tts_index
=
0
;
}
else
{
my_zlog_error
(
"fork()失败"
);
s_audio_tts_index
=
0
;
}
}
s_audio_tts_index
=
0
;
}
}
}
}
...
@@ -532,4 +501,4 @@ void video_tts_play() {
...
@@ -532,4 +501,4 @@ void video_tts_play() {
// s_audio_tts_index == 0;
// s_audio_tts_index == 0;
// }
// }
// }
// }
\ No newline at end of file
drivers/sensors/audiotts_play.c.bak-audio-preempt-20260717
0 → 100644
View file @
dced7c62
#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>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define TTS_ADUINO_PTAH PROJECT_ROOT_DIR "/audio.mp3"
// 全局变量:存储 HTTP 响应数据
static char *s_http_response = NULL;
static size_t s_http_response_len = 0;
static bool s_audio_tts_index=0;
static double s_volume=1.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(s_http_response, s_http_response_len + total + 1);
if (!temp) {
my_zlog_error("Realloc response buffer failed");
return 0;
}
s_http_response = temp;
memcpy(s_http_response + s_http_response_len, ptr, total);
s_http_response_len += total;
s_http_response[s_http_response_len] = '\0'; // 末尾加结束符
return total;
}
// 生成 UUID(去除连字符,保证 reqid 唯一性)
static 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 解码(将响应中的音频数据解码为二进制)
static 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失败
*/
static 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_debug("Success! Audio saved to: %s (%zu bytes)", filepath, written);
return 0;
}
/*
*功能:获得随机字符串,不使用uuid
*/
static char* generate_random_string() {
// 设置随机种子(建议在main函数中调用一次即可)
static int seeded = 0;
if (!seeded) {
srand((unsigned int)time(NULL));
seeded = 1;
}
// 动态分配内存(10个字符 + 终止符)
char* str = (char*)malloc(11 * sizeof(char));
if (!str) return NULL;
// 生成数字字符(ASCII 48-57)
for (int i = 0; i < 10; i++) {
str[i] = '0' + (rand() % 10);
}
str[10] = '\0'; // 添加终止符
return str;
}
char *get_audio_text_tts(char *date ,bool flag){
static char text[256] = {0};
if(flag==1){
return text;
}else {
size_t date_len = strlen(date);
if (date) {
strcpy(text, date);
text[date_len] = '\0'; // 确保以null结尾
} else {
text[0] = '\0'; // 如果date为NULL,清空文本
}
}
}
/**
* 保存音频数据到文件(目录已存在)
* @param text 传入需要转化的文字指针
* @return 0成功,1失败
*/
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"; // 固定值(必选)
char *uid = generate_random_string(); // 任意非空字符串
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_debug("Generated ReqID: %s", reqid);
// 3. 构建请求 JSON(严格遵循接口参数格式)
cJSON *root = cJSON_CreateObject();
if (!root) {
my_zlog_error("Create JSON root failed");
free(uid);
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");
free(uid);
return 1;
}
my_zlog_debug("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);
free(uid);
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");
if (s_http_response == NULL) {
s_http_response_len = 0;
}else {
return 1;
}
// 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 请求
my_zlog_debug("Sending request to %s...", endpoint);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
my_zlog_error("curl_easy_perform failed: %s", curl_easy_strerror(res));
free(uid);
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 6. 解析 HTTP 响应(JSON 格式)
my_zlog_debug("Response Length: %zu bytes", s_http_response_len);
if (s_http_response_len == 0) {
my_zlog_error("Empty response from server");
free(uid);
free(payload);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
// 解析 JSON 响应
cJSON *response_json = cJSON_Parse(s_http_response);
if (!response_json) {
my_zlog_error("Parse response JSON failed: %s", cJSON_GetErrorPtr());
free(uid);
free(payload);
free(s_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) {
my_zlog_error("Request failed! Code: %d, Message: %s",
code ? code->valueint : -1,
message ? message->valuestring : "No message");
cJSON_Delete(response_json);
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("Request succeeded! Code: %d, Message: %s", 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) {
my_zlog_error("Response missing audio data (data field is empty)");
cJSON_Delete(response_json);
free(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("Audio Base64 Length: %zu bytes", 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(uid);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return 1;
}
my_zlog_debug("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(uid);
free(audio_data);
free(payload);
free(s_http_response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
// 初始化响应缓冲
s_http_response = NULL;
s_http_response_len = 0;
return 0;
}
/*
*功能: 设置播放标识
*/
void set_audio_tts_index(bool index){
s_audio_tts_index=index;
}
/*
*功能: 发送mqtt
*/
int mqtt_audio_tts_send(int status){
// 创建根对象
cJSON* root = cJSON_CreateObject();
if (root == NULL) {
return 1;
}
// 创建body对象
cJSON* body = cJSON_CreateObject();
if (body == NULL) {
cJSON_Delete(root);
return 1;
}
cJSON *head = cJSON_CreateObject();
if (head == NULL) {
cJSON_Delete(root);
return 1;
}
// 将body添加到root
cJSON_AddItemToObject(root, "body", body);
// 添加type和status字段到body
cJSON_AddStringToObject(body, "type", "tts_audio");
cJSON_AddNumberToObject(body, "status", status);
cJSON_AddNumberToObject(head, "message_type",3020);
cJSON_AddItemToObject(root, "body", body);
cJSON_AddItemToObject(root, "head",head);
// 生成JSON字符串
char* json_str = cJSON_PrintUnformatted(root);
my_zlog_debug("%s",json_str);
mqtt_publish_to_all(mqtt_topic_pure_number(), json_str, 0);
free(json_str);
cJSON_Delete(root);
return 0;
}
int mqtt_audio_tts(cJSON *body){
// 解析type字段
cJSON* type = cJSON_GetObjectItem(body, "type");
if(type == NULL || strcmp(type->valuestring, "tts_audio") != 0){
my_zlog_error("type is tts_audio error");
return 1;
}
// 解析date字段
cJSON* date = cJSON_GetObjectItem(body, "date");
if(date!=NULL){
get_audio_text_tts(date->valuestring,0);
my_zlog_debug("text:%s",date->valuestring);
}else {
return 1;
}
// 解析status字段
cJSON* status = cJSON_GetObjectItem(body, "status");
if(mqtt_audio_tts_send(status->valueint)==0){
set_audio_tts_index(status->valueint);
}else{
set_audio_tts_index(0);
my_zlog_error("statis error");
return 1;
}
cJSON* volume = cJSON_GetObjectItem(body, "volume");
s_volume = volume->valuedouble;
return 0;
}
/*
*功能,tts播放
*/
void video_tts_play() {
if(s_audio_tts_index == 1) {
if(audio_tts(get_audio_text_tts(NULL, 1))!=0){
my_zlog_error("audio_tts fail");
s_audio_tts_index = 0;
return;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程:创建新的会话组,脱离父进程组
setsid(); // 关键!创建新的会话组
char command[256];
snprintf(command, sizeof(command),
"ffmpeg -i " TTS_ADUINO_PTAH " -af \"volume=%.1f\" -f alsa default 2>/dev/null",
s_volume);
// 重定向标准输入输出,避免占用终端
if (freopen("/dev/null", "r", stdin) == NULL) {
my_zlog_error("重定向stdin失败: %s", strerror(errno));
// 可以选择退出或继续
}
if (freopen("/dev/null", "w", stdout) == NULL) {
my_zlog_error("重定向stdout失败: %s", strerror(errno));
}
if (freopen("/dev/null", "w", stderr) == NULL) {
my_zlog_error("重定向stderr失败: %s", strerror(errno));
}
int res = system(command);
_exit(res); // 使用_exit避免清理父进程资源
}
else if (pid > 0) {
// 父进程:非阻塞等待,避免僵尸进程
int status;
waitpid(pid, &status, WNOHANG); // 不阻塞的等待
my_zlog_debug("启动独立播放进程 PID: %d", pid);
s_audio_tts_index = 0;
}
else {
my_zlog_error("fork()失败");
s_audio_tts_index = 0;
}
}
}
// void video_tts_play(){
// if(s_audio_tts_index == 1) {
// audio_tts(get_audio_text_tts(NULL,1));
// char command[256];
// snprintf(command, sizeof(command),
// "ffmpeg -i " TTS_ADUINO_PTAH " -af \"volume=%.1f\" -f alsa default",
// s_volume);
// int res = system(command);
// if(res == 0)
// my_zlog_info("play success, volume: %.1f", s_volume);
// else
// my_zlog_error("system play fail, volume: %.1f", s_volume);
// s_audio_tts_index == 0;
// }
// }
\ No newline at end of file
modules/http/http_config_mqtt.h
View file @
dced7c62
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
#include "common.h"
#include "common.h"
#define DEFAULT_MQTT_BROKER_ADDRESS "
119.45.167.177
"
#define DEFAULT_MQTT_BROKER_ADDRESS "
mqtt177.controlmelive.com
"
#define DEVICE_DEFAULT_VIDEO "https://jywy.yd-ss.com?dev="
#define DEVICE_DEFAULT_VIDEO "https://jywy.yd-ss.com?dev="
typedef
struct
{
typedef
struct
{
...
...
modules/mqtt/mqtt_infor_handle.c
View file @
dced7c62
...
@@ -213,7 +213,7 @@ static void refresh_cam_browser_only(const char *reason)
...
@@ -213,7 +213,7 @@ static void refresh_cam_browser_only(const char *reason)
void
message_2_judyverify
(
cJSON
*
body
)
void
message_2_judyverify
(
cJSON
*
body
)
{
{
if
(
VERIFIED_MODE
==
FALSE
)
if
(
VERIFIED_MODE
==
false
)
{
{
refresh_cam_browser_only
(
"MQTT type2 verify off"
);
refresh_cam_browser_only
(
"MQTT type2 verify off"
);
my_zlog_warn
(
"不使用验证"
);
my_zlog_warn
(
"不使用验证"
);
...
@@ -234,7 +234,7 @@ void message_2_judyverify(cJSON *body)
...
@@ -234,7 +234,7 @@ void message_2_judyverify(cJSON *body)
// 当接收到3时候验证
// 当接收到3时候验证
void
message_3_judyverify
(
cJSON
*
body
)
void
message_3_judyverify
(
cJSON
*
body
)
{
{
if
(
VERIFIED_MODE
==
FALSE
)
if
(
VERIFIED_MODE
==
false
)
{
{
message_3
(
body
);
message_3
(
body
);
my_zlog_warn
(
"不使用验证"
);
my_zlog_warn
(
"不使用验证"
);
...
@@ -255,7 +255,7 @@ void message_3_judyverify(cJSON *body)
...
@@ -255,7 +255,7 @@ void message_3_judyverify(cJSON *body)
// 当接收到4时候验证
// 当接收到4时候验证
void
message_4_judyverify
(
cJSON
*
body
)
void
message_4_judyverify
(
cJSON
*
body
)
{
{
if
(
VERIFIED_MODE
==
FALSE
)
if
(
VERIFIED_MODE
==
false
)
{
{
message_4
(
body
);
message_4
(
body
);
my_zlog_warn
(
"不使用验证"
);
my_zlog_warn
(
"不使用验证"
);
...
@@ -276,7 +276,7 @@ void message_4_judyverify(cJSON *body)
...
@@ -276,7 +276,7 @@ void message_4_judyverify(cJSON *body)
void
message_6_steering_judyverify
(
cJSON
*
body
)
void
message_6_steering_judyverify
(
cJSON
*
body
)
{
{
if
(
VERIFIED_MODE
==
FALSE
)
if
(
VERIFIED_MODE
==
false
)
{
{
steering_mqtt_recv
(
body
);
steering_mqtt_recv
(
body
);
my_zlog_warn
(
"不使用验证"
);
my_zlog_warn
(
"不使用验证"
);
...
@@ -303,7 +303,7 @@ void message_7_joystick_judyverify(cJSON *body)
...
@@ -303,7 +303,7 @@ void message_7_joystick_judyverify(cJSON *body)
return
;
return
;
}
}
if
(
VERIFIED_MODE
==
FALSE
)
if
(
VERIFIED_MODE
==
false
)
{
{
ptz_driver_set_joystick_json
(
joystick_ctrl
);
ptz_driver_set_joystick_json
(
joystick_ctrl
);
my_zlog_warn
(
"不使用验证"
);
my_zlog_warn
(
"不使用验证"
);
...
...
modules/mqtt/mqtt_verify.c
View file @
dced7c62
...
@@ -9,9 +9,9 @@
...
@@ -9,9 +9,9 @@
#include "webrtcpush_run.h"
#include "webrtcpush_run.h"
/*TRUE为打开验证,FALSE为关闭验证*/
/*TRUE为打开验证,FALSE为关闭验证*/
bool
VERIFIED_MODE
=
FALSE
;
bool
VERIFIED_MODE
=
false
;
static
bool
s_service_verify
=
TRUE
;
//验证判断
static
bool
s_service_verify
=
true
;
//验证判断
int
g_verify_count
=
6000
;
//判断是否有一分钟
int
g_verify_count
=
6000
;
//判断是否有一分钟
...
@@ -19,7 +19,7 @@ static char s_only_id_middle[11];
...
@@ -19,7 +19,7 @@ static char s_only_id_middle[11];
static
char
s_secret_key
[
1024
];
//存储上一次的topic
static
char
s_secret_key
[
1024
];
//存储上一次的topic
static
bool
s_secret_key_index
=
TRUE
;
//用于判断是否对比topic
static
bool
s_secret_key_index
=
true
;
//用于判断是否对比topic
/* topic 不同时发后端验证,失败可重试,超过 3 次不通过则判定该 topic 不通过 */
/* topic 不同时发后端验证,失败可重试,超过 3 次不通过则判定该 topic 不通过 */
static
char
s_pending_verify_token
[
1024
];
static
char
s_pending_verify_token
[
1024
];
...
@@ -172,7 +172,7 @@ int receive_jwt(cJSON *body) {
...
@@ -172,7 +172,7 @@ int receive_jwt(cJSON *body) {
my_zlog_debug
(
"token时间戳:%1d"
,(
long
)
token_time_sec
);
my_zlog_debug
(
"token时间戳:%1d"
,(
long
)
token_time_sec
);
if
(
token_time_sec
>
current_verify_time
){
if
(
token_time_sec
>
current_verify_time
){
if
(
s_service_verify
==
TRUE
)
g_verify_index
=
0
;
//验证默认为正确
if
(
s_service_verify
==
true
)
g_verify_index
=
0
;
//验证默认为正确
my_zlog_debug
(
"g_verify_index= %d "
,
g_verify_index
);
my_zlog_debug
(
"g_verify_index= %d "
,
g_verify_index
);
if
(
g_verify_count
>
12000
){
if
(
g_verify_count
>
12000
){
send_jwtser
(
token
);
send_jwtser
(
token
);
...
@@ -205,7 +205,7 @@ int message2006_verify(cJSON *body){
...
@@ -205,7 +205,7 @@ int message2006_verify(cJSON *body){
if
(
json_status
==
NULL
||
json_onlyid
==
NULL
)
{
if
(
json_status
==
NULL
||
json_onlyid
==
NULL
)
{
my_zlog_warn
(
"验证为空"
);
my_zlog_warn
(
"验证为空"
);
s_service_verify
=
FALSE
;
s_service_verify
=
false
;
g_verify_index
=
1
;
g_verify_index
=
1
;
my_zlog_debug
(
"g_verify_index= %d "
,
g_verify_index
);
my_zlog_debug
(
"g_verify_index= %d "
,
g_verify_index
);
return
1
;
return
1
;
...
@@ -219,10 +219,10 @@ int message2006_verify(cJSON *body){
...
@@ -219,10 +219,10 @@ int message2006_verify(cJSON *body){
gboolean
driver_changed
=
(
s_secret_key
[
0
]
!=
'\0'
&&
gboolean
driver_changed
=
(
s_secret_key
[
0
]
!=
'\0'
&&
strcmp
(
s_secret_key
,
s_pending_verify_token
)
!=
0
);
strcmp
(
s_secret_key
,
s_pending_verify_token
)
!=
0
);
my_zlog_info
(
"获得验证正确, onlyid=%s"
,
onlyid
);
my_zlog_info
(
"获得验证正确, onlyid=%s"
,
onlyid
);
s_service_verify
=
TRUE
;
s_service_verify
=
true
;
g_verify_index
=
0
;
/* 后端通过后必须清除失败标志,否则正确 token 仍会提示验证不通过 */
g_verify_index
=
0
;
/* 后端通过后必须清除失败标志,否则正确 token 仍会提示验证不通过 */
strcpy
(
s_secret_key
,
s_pending_verify_token
);
strcpy
(
s_secret_key
,
s_pending_verify_token
);
s_secret_key_index
=
FALSE
;
s_secret_key_index
=
false
;
s_verify_retry_count
=
0
;
s_verify_retry_count
=
0
;
s_verify_retry_after_time
=
0
;
s_verify_retry_after_time
=
0
;
if
(
driver_changed
)
if
(
driver_changed
)
...
@@ -238,13 +238,13 @@ int message2006_verify(cJSON *body){
...
@@ -238,13 +238,13 @@ int message2006_verify(cJSON *body){
}
else
{
}
else
{
my_zlog_warn
(
"topic验证超过%d次不通过,禁止使用, token=%s"
,
VERIFY_RETRY_MAX
,
s_pending_verify_token
);
my_zlog_warn
(
"topic验证超过%d次不通过,禁止使用, token=%s"
,
VERIFY_RETRY_MAX
,
s_pending_verify_token
);
g_verify_index
=
1
;
g_verify_index
=
1
;
s_service_verify
=
FALSE
;
s_service_verify
=
false
;
return
2
;
return
2
;
}
}
}
else
{
}
else
{
my_zlog_warn
(
"获得验证错误,禁止使用, onlyid=%s status=%s"
,
onlyid
,
status
);
my_zlog_warn
(
"获得验证错误,禁止使用, onlyid=%s status=%s"
,
onlyid
,
status
);
g_verify_index
=
1
;
g_verify_index
=
1
;
s_service_verify
=
FALSE
;
s_service_verify
=
false
;
return
2
;
return
2
;
}
}
}
}
...
@@ -297,14 +297,14 @@ int message2013_recverigy_open(cJSON *body_raw)
...
@@ -297,14 +297,14 @@ int message2013_recverigy_open(cJSON *body_raw)
s_verify_open_query_gave_up
=
false
;
s_verify_open_query_gave_up
=
false
;
if
(
verify_status
->
valueint
==
1
)
{
if
(
verify_status
->
valueint
==
1
)
{
VERIFIED_MODE
=
TRUE
;
VERIFIED_MODE
=
true
;
my_zlog_info
(
"开启验证成功(加密校验通过), verify_status=1"
);
my_zlog_info
(
"开启验证成功(加密校验通过), verify_status=1"
);
cJSON_Delete
(
body
);
cJSON_Delete
(
body
);
return
0
;
return
0
;
}
}
if
(
verify_status
->
valueint
==
0
)
{
if
(
verify_status
->
valueint
==
0
)
{
VERIFIED_MODE
=
FALSE
;
VERIFIED_MODE
=
false
;
my_zlog_info
(
"关闭验证成功(加密校验通过), verify_status=0"
);
my_zlog_info
(
"关闭验证成功(加密校验通过), verify_status=0"
);
cJSON_Delete
(
body
);
cJSON_Delete
(
body
);
return
0
;
return
0
;
...
...
modules/webrtcpush/rtc_client.c
View file @
dced7c62
...
@@ -21,6 +21,7 @@
...
@@ -21,6 +21,7 @@
typedef
struct
{
typedef
struct
{
gchar
*
candidate
;
gchar
*
candidate
;
gchar
*
mid
;
gchar
*
mid
;
gchar
*
ufrag
;
}
PendingRemoteIce
;
}
PendingRemoteIce
;
typedef
struct
{
typedef
struct
{
...
@@ -277,6 +278,7 @@ static void free_pending_remote_ice(gpointer data)
...
@@ -277,6 +278,7 @@ static void free_pending_remote_ice(gpointer data)
return
;
return
;
g_free
(
ice
->
candidate
);
g_free
(
ice
->
candidate
);
g_free
(
ice
->
mid
);
g_free
(
ice
->
mid
);
g_free
(
ice
->
ufrag
);
g_free
(
ice
);
g_free
(
ice
);
}
}
...
@@ -1990,14 +1992,24 @@ static void flush_remote_ice(RtcClient *client)
...
@@ -1990,14 +1992,24 @@ static void flush_remote_ice(RtcClient *client)
for
(;;)
{
for
(;;)
{
PendingRemoteIce
*
ice
;
PendingRemoteIce
*
ice
;
int
pc
;
int
pc
;
gboolean
ufrag_match
;
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
ice
=
g_queue_pop_head
(
client
->
pending_remote_ice
);
ice
=
g_queue_pop_head
(
client
->
pending_remote_ice
);
pc
=
client
->
pc
;
pc
=
client
->
pc
;
ufrag_match
=
!
ice
||
!
ice
->
ufrag
||
!
client
->
remote_ice_ufrag
||
g_strcmp0
(
ice
->
ufrag
,
client
->
remote_ice_ufrag
)
==
0
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
!
ice
)
if
(
!
ice
)
break
;
break
;
if
(
pc
>=
0
)
if
(
pc
>=
0
&&
ufrag_match
)
{
rtcAddRemoteCandidate
(
pc
,
ice
->
candidate
,
ice
->
mid
);
if
(
rtcAddRemoteCandidate
(
pc
,
ice
->
candidate
,
ice
->
mid
)
<
0
)
my_zlog_warn
(
"libdatachannel: rejected queued remote ICE candidate ufrag=%s"
,
ice
->
ufrag
?
ice
->
ufrag
:
"?"
);
}
else
if
(
!
ufrag_match
)
{
my_zlog_info
(
"libdatachannel: discard stale remote ICE candidate ufrag=%s current=%s"
,
ice
->
ufrag
?
ice
->
ufrag
:
"?"
,
client
->
remote_ice_ufrag
?
client
->
remote_ice_ufrag
:
"?"
);
}
free_pending_remote_ice
(
ice
);
free_pending_remote_ice
(
ice
);
}
}
}
}
...
@@ -2095,8 +2107,12 @@ int rtc_client_start(AppState *app)
...
@@ -2095,8 +2107,12 @@ int rtc_client_start(AppState *app)
void
rtc_client_reset
(
AppState
*
app
)
void
rtc_client_reset
(
AppState
*
app
)
{
{
RtcClient
*
client
=
client_from_app
(
app
);
RtcClient
*
client
=
client_from_app
(
app
);
if
(
client
)
if
(
client
)
{
destroy_peer
(
client
);
destroy_peer
(
client
);
g_mutex_lock
(
&
client
->
lock
);
g_queue_clear_full
(
client
->
pending_remote_ice
,
free_pending_remote_ice
);
g_mutex_unlock
(
&
client
->
lock
);
}
}
}
void
rtc_client_stop
(
AppState
*
app
)
void
rtc_client_stop
(
AppState
*
app
)
...
@@ -2285,6 +2301,9 @@ void rtc_client_handle_remote_ice(AppState *app, JsonObject *candidate_obj)
...
@@ -2285,6 +2301,9 @@ void rtc_client_handle_remote_ice(AppState *app, JsonObject *candidate_obj)
RtcClient
*
client
=
client_from_app
(
app
);
RtcClient
*
client
=
client_from_app
(
app
);
const
gchar
*
candidate
;
const
gchar
*
candidate
;
const
gchar
*
mid
=
NULL
;
const
gchar
*
mid
=
NULL
;
const
gchar
*
ufrag
=
NULL
;
gchar
*
candidate_ufrag
=
NULL
;
gboolean
queued
=
FALSE
;
int
pc
;
int
pc
;
if
(
!
client
||
!
candidate_obj
||
if
(
!
client
||
!
candidate_obj
||
...
@@ -2299,18 +2318,48 @@ void rtc_client_handle_remote_ice(AppState *app, JsonObject *candidate_obj)
...
@@ -2299,18 +2318,48 @@ void rtc_client_handle_remote_ice(AppState *app, JsonObject *candidate_obj)
json_node_get_value_type
(
node
)
==
G_TYPE_STRING
)
json_node_get_value_type
(
node
)
==
G_TYPE_STRING
)
mid
=
json_object_get_string_member
(
candidate_obj
,
"sdpMid"
);
mid
=
json_object_get_string_member
(
candidate_obj
,
"sdpMid"
);
}
}
if
(
json_object_has_member
(
candidate_obj
,
"usernameFragment"
))
{
JsonNode
*
node
=
json_object_get_member
(
candidate_obj
,
"usernameFragment"
);
if
(
JSON_NODE_HOLDS_VALUE
(
node
)
&&
json_node_get_value_type
(
node
)
==
G_TYPE_STRING
)
ufrag
=
json_object_get_string_member
(
candidate_obj
,
"usernameFragment"
);
}
else
if
(
json_object_has_member
(
candidate_obj
,
"ufrag"
))
{
JsonNode
*
node
=
json_object_get_member
(
candidate_obj
,
"ufrag"
);
if
(
JSON_NODE_HOLDS_VALUE
(
node
)
&&
json_node_get_value_type
(
node
)
==
G_TYPE_STRING
)
ufrag
=
json_object_get_string_member
(
candidate_obj
,
"ufrag"
);
}
if
(
!
ufrag
)
{
const
gchar
*
tag
=
strstr
(
candidate
,
" ufrag "
);
if
(
tag
)
{
const
gchar
*
end
;
tag
+=
strlen
(
" ufrag "
);
end
=
strchr
(
tag
,
' '
);
candidate_ufrag
=
end
?
g_strndup
(
tag
,
(
gsize
)(
end
-
tag
))
:
g_strdup
(
tag
);
ufrag
=
candidate_ufrag
;
}
}
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
pc
=
client
->
pc
;
pc
=
client
->
pc
;
if
(
pc
<
0
)
{
if
(
pc
<
0
||
(
ufrag
&&
client
->
remote_ice_ufrag
&&
g_strcmp0
(
ufrag
,
client
->
remote_ice_ufrag
)
!=
0
))
{
PendingRemoteIce
*
pending
=
g_new0
(
PendingRemoteIce
,
1
);
PendingRemoteIce
*
pending
=
g_new0
(
PendingRemoteIce
,
1
);
pending
->
candidate
=
g_strdup
(
candidate
);
pending
->
candidate
=
g_strdup
(
candidate
);
pending
->
mid
=
g_strdup
(
mid
);
pending
->
mid
=
g_strdup
(
mid
);
pending
->
ufrag
=
g_strdup
(
ufrag
);
g_queue_push_tail
(
client
->
pending_remote_ice
,
pending
);
g_queue_push_tail
(
client
->
pending_remote_ice
,
pending
);
pc
=
-
1
;
queued
=
TRUE
;
}
}
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
pc
>=
0
&&
rtcAddRemoteCandidate
(
pc
,
candidate
,
mid
)
<
0
)
if
(
pc
>=
0
&&
rtcAddRemoteCandidate
(
pc
,
candidate
,
mid
)
<
0
)
my_zlog_warn
(
"libdatachannel: rejected remote ICE candidate"
);
my_zlog_warn
(
"libdatachannel: rejected remote ICE candidate"
);
else
if
(
queued
)
my_zlog_info
(
"libdatachannel: queued remote ICE candidate for ufrag=%s"
,
ufrag
?
ufrag
:
"?"
);
g_free
(
candidate_ufrag
);
}
}
gboolean
rtc_client_is_active
(
AppState
*
app
)
gboolean
rtc_client_is_active
(
AppState
*
app
)
...
...
modules/webrtcpush/webrtcpush_config.h
View file @
dced7c62
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
*
*
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
*/
*/
#define WEBRTCPUSH_USE_MPP
1
#define WEBRTCPUSH_USE_MPP
0
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_FPS 24
...
@@ -17,10 +17,10 @@
...
@@ -17,10 +17,10 @@
/*
/*
* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略。
* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略。
* 首屏先用
900kbps,不像 1.4Mbps
那样猛冲,也不要低到一进来就糊。
* 首屏先用
1.06Mbps,不像高码率
那样猛冲,也不要低到一进来就糊。
* RTCP REMB 只作为码率趋势:小步慢降、慢升、带滞回,尽量接近浏览器的无感自适应。
* RTCP REMB 只作为码率趋势:小步慢降、慢升、带滞回,尽量接近浏览器的无感自适应。
*/
*/
#define WEBRTCPUSH_INITIAL_BITRATE
90
0000U
#define WEBRTCPUSH_INITIAL_BITRATE
106
0000U
#define WEBRTCPUSH_MIN_BITRATE 800000U
#define WEBRTCPUSH_MIN_BITRATE 800000U
#define WEBRTCPUSH_MAX_BITRATE 3000000U
#define WEBRTCPUSH_MAX_BITRATE 3000000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
...
@@ -44,6 +44,8 @@
...
@@ -44,6 +44,8 @@
#define WEBRTCPUSH_REMB_SANE_PACING_MAX_MS 100U
#define WEBRTCPUSH_REMB_SANE_PACING_MAX_MS 100U
/* IDR 会产生短时大包,不能在这个窗口内把瞬时 pacing 峰值误判成网络拥塞。 */
/* IDR 会产生短时大包,不能在这个窗口内把瞬时 pacing 峰值误判成网络拥塞。 */
#define WEBRTCPUSH_REMB_IDR_GRACE_MS 1000U
#define WEBRTCPUSH_REMB_IDR_GRACE_MS 1000U
/* 手机切 Wi-Fi/蜂窝或换手机时,旧成员的 leave 可能晚于新 offer 到达。 */
#define WEBRTCPUSH_PEER_LEAVE_GRACE_MS 8000U
/* 达到 2.3Mbps 后继续发送一段时间,让浏览器有真实流量可重新估计带宽。 */
/* 达到 2.3Mbps 后继续发送一段时间,让浏览器有真实流量可重新估计带宽。 */
#define WEBRTCPUSH_INITIAL_PROBE_HOLD_MS 6000U
#define WEBRTCPUSH_INITIAL_PROBE_HOLD_MS 6000U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 135U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 135U
...
...
modules/webrtcpush/ws_signaling.c
View file @
dced7c62
...
@@ -23,11 +23,15 @@ static gchar *s_ws_url;
...
@@ -23,11 +23,15 @@ static gchar *s_ws_url;
static
GMainContext
*
s_main_ctx
;
static
GMainContext
*
s_main_ctx
;
static
gchar
*
s_debounced_offer_sdp
=
NULL
;
static
gchar
*
s_debounced_offer_sdp
=
NULL
;
static
guint
s_debounce_offer_id
=
0
;
static
guint
s_debounce_offer_id
=
0
;
static
guint
s_peer_leave_reset_id
=
0
;
static
guint
s_offer_generation
=
0
;
static
guint
s_leave_generation
=
0
;
static
gboolean
reconnect_cb
(
gpointer
data
);
static
gboolean
reconnect_cb
(
gpointer
data
);
static
void
ws_connect
(
void
);
static
void
ws_connect
(
void
);
static
void
reset_signaling_session
(
AppState
*
app
,
gboolean
teardown_pipeline
);
static
void
reset_signaling_session
(
AppState
*
app
,
gboolean
teardown_pipeline
);
static
gboolean
idle_pipeline_teardown
(
gpointer
p
);
static
gboolean
idle_pipeline_teardown
(
gpointer
p
);
static
gboolean
delayed_peer_leave_reset
(
gpointer
p
);
static
void
schedule_tx_flush
(
void
);
static
void
schedule_tx_flush
(
void
);
static
void
dispatch_json
(
const
gchar
*
payload
);
static
void
dispatch_json
(
const
gchar
*
payload
);
...
@@ -228,6 +232,12 @@ static gboolean debounced_offer_fire(gpointer user_data) {
...
@@ -228,6 +232,12 @@ static gboolean debounced_offer_fire(gpointer user_data) {
}
}
static
void
schedule_offer
(
AppState
*
app
,
const
gchar
*
sdp
)
{
static
void
schedule_offer
(
AppState
*
app
,
const
gchar
*
sdp
)
{
s_offer_generation
++
;
if
(
s_peer_leave_reset_id
)
{
g_source_remove
(
s_peer_leave_reset_id
);
s_peer_leave_reset_id
=
0
;
my_zlog_info
(
"webrtcpush: new offer cancels pending stale peer-leave reset"
);
}
if
(
!
app
->
member_id
)
{
if
(
!
app
->
member_id
)
{
g_free
(
s_pending_offer_sdp
);
g_free
(
s_pending_offer_sdp
);
s_pending_offer_sdp
=
g_strdup
(
sdp
);
s_pending_offer_sdp
=
g_strdup
(
sdp
);
...
@@ -388,8 +398,14 @@ static void dispatch_json(const gchar *payload) {
...
@@ -388,8 +398,14 @@ static void dispatch_json(const gchar *payload) {
if
(
!
g_strcmp0
(
t
,
"leave"
))
{
if
(
!
g_strcmp0
(
t
,
"leave"
))
{
if
(
rtc_client_is_active
(
s_app
))
{
if
(
rtc_client_is_active
(
s_app
))
{
my_zlog_info
(
"webrtcpush: peer left, reset libdatachannel peer"
);
if
(
s_peer_leave_reset_id
)
g_idle_add
(
idle_pipeline_teardown
,
s_app
);
g_source_remove
(
s_peer_leave_reset_id
);
s_leave_generation
=
s_offer_generation
;
s_peer_leave_reset_id
=
g_timeout_add
(
WEBRTCPUSH_PEER_LEAVE_GRACE_MS
,
delayed_peer_leave_reset
,
s_app
);
my_zlog_info
(
"webrtcpush: peer left, defer reset %ums for network/device switch"
,
WEBRTCPUSH_PEER_LEAVE_GRACE_MS
);
}
}
g_object_unref
(
parser
);
g_object_unref
(
parser
);
return
;
return
;
...
@@ -436,6 +452,12 @@ static void dispatch_json(const gchar *payload) {
...
@@ -436,6 +452,12 @@ static void dispatch_json(const gchar *payload) {
if
(
json_node_get_value_type
(
midn
)
==
G_TYPE_STRING
)
if
(
json_node_get_value_type
(
midn
)
==
G_TYPE_STRING
)
json_object_set_string_member
(
co
,
"sdpMid"
,
json_object_get_string_member
(
root
,
"sdpMid"
));
json_object_set_string_member
(
co
,
"sdpMid"
,
json_object_get_string_member
(
root
,
"sdpMid"
));
}
}
if
(
json_object_has_member
(
root
,
"usernameFragment"
))
{
JsonNode
*
ufn
=
json_object_get_member
(
root
,
"usernameFragment"
);
if
(
json_node_get_value_type
(
ufn
)
==
G_TYPE_STRING
)
json_object_set_string_member
(
co
,
"usernameFragment"
,
json_object_get_string_member
(
root
,
"usernameFragment"
));
}
g_idle_add
(
idle_ice
,
co
);
g_idle_add
(
idle_ice
,
co
);
}
}
}
}
...
@@ -506,6 +528,26 @@ static gboolean idle_pipeline_teardown(gpointer p) {
...
@@ -506,6 +528,26 @@ static gboolean idle_pipeline_teardown(gpointer p) {
return
G_SOURCE_REMOVE
;
return
G_SOURCE_REMOVE
;
}
}
static
gboolean
delayed_peer_leave_reset
(
gpointer
p
)
{
AppState
*
app
=
p
;
guint
generation
=
s_leave_generation
;
s_peer_leave_reset_id
=
0
;
if
(
!
app
||
generation
!=
s_offer_generation
)
{
my_zlog_info
(
"webrtcpush: stale peer-leave reset ignored (new offer generation)"
);
return
G_SOURCE_REMOVE
;
}
if
(
rtc_client_media_open
(
app
))
{
my_zlog_info
(
"webrtcpush: stale peer-leave reset ignored (new media already open)"
);
return
G_SOURCE_REMOVE
;
}
if
(
rtc_client_is_active
(
app
))
{
my_zlog_info
(
"webrtcpush: peer leave confirmed, release inactive libdatachannel peer"
);
rtc_client_reset
(
app
);
}
return
G_SOURCE_REMOVE
;
}
static
void
ws_connect
(
void
)
{
static
void
ws_connect
(
void
)
{
if
(
s_ws_exiting
||
!
s_app
||
!
s_ws_url
)
if
(
s_ws_exiting
||
!
s_app
||
!
s_ws_url
)
return
;
return
;
...
@@ -610,6 +652,10 @@ void ws_signaling_stop(AppState *app) {
...
@@ -610,6 +652,10 @@ void ws_signaling_stop(AppState *app) {
g_source_remove
(
s_reconnect_id
);
g_source_remove
(
s_reconnect_id
);
s_reconnect_id
=
0
;
s_reconnect_id
=
0
;
}
}
if
(
s_peer_leave_reset_id
)
{
g_source_remove
(
s_peer_leave_reset_id
);
s_peer_leave_reset_id
=
0
;
}
if
(
s_ping_id
)
{
if
(
s_ping_id
)
{
g_source_remove
(
s_ping_id
);
g_source_remove
(
s_ping_id
);
s_ping_id
=
0
;
s_ping_id
=
0
;
...
...
zlog.conf
View file @
dced7c62
...
@@ -9,4 +9,4 @@ file perms = 600
...
@@ -9,4 +9,4 @@ file perms = 600
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
[
rules
]
[
rules
]
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-1
1
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-1
4
.log"
;
millisecond
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