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
7f0fd331
Commit
7f0fd331
authored
Jul 02, 2026
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
第三次未改完提交
parent
2eb47ff8
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
3397 additions
and
418 deletions
+3397
-418
device_wifi_manager.c
app/device_change/device_wifi_manager.c
+35
-0
device_wifi_manager.h
app/device_change/device_wifi_manager.h
+7
-0
main.c
app/main/main.c
+7
-0
pthread_open.c
app/main/pthread_open.c
+12
-3
main
build/main
+0
-0
car0107_control.c
drivers/devicecontrol/car/car0107_control.c
+659
-215
car0107_control.h
drivers/devicecontrol/car/car0107_control.h
+5
-0
car0107_idle.c
drivers/devicecontrol/car/car0107_idle.c
+154
-0
car0107_idle.h
drivers/devicecontrol/car/car0107_idle.h
+7
-0
devcontrol_common.c
drivers/devicecontrol/common/devcontrol_common.c
+1
-1
devcontrol_common.h
drivers/devicecontrol/common/devcontrol_common.h
+1
-0
device_init.c
drivers/gpio/device_init.c
+4
-0
audioplay.c
drivers/sensors/audioplay.c
+3
-3
modules_common.h
include/modules_common.h
+3
-0
CMakeLists.txt
modules/CMakeLists.txt
+4
-0
browser_open.c
modules/browser/browser_open.c
+467
-22
browser_open.h
modules/browser/browser_open.h
+12
-1
stream_debug_agent.c
modules/stream_debug/stream_debug_agent.c
+150
-0
stream_debug_agent.h
modules/stream_debug/stream_debug_agent.h
+11
-0
stream_debug_config.h
modules/stream_debug/stream_debug_config.h
+8
-0
audio_source.c
modules/webrtcpush/audio_source.c
+149
-0
audio_source.h
modules/webrtcpush/audio_source.h
+18
-0
mpp_h264_source.c
modules/webrtcpush/mpp_h264_source.c
+497
-80
mpp_h264_source.h
modules/webrtcpush/mpp_h264_source.h
+5
-0
rtc_client.c
modules/webrtcpush/rtc_client.c
+637
-85
webrtcpush_config.h
modules/webrtcpush/webrtcpush_config.h
+37
-6
webrtcpush_run.c
modules/webrtcpush/webrtcpush_run.c
+1
-1
wifi_config_agent.c
modules/wifi_config/wifi_config_agent.c
+483
-0
wifi_config_agent.h
modules/wifi_config/wifi_config_agent.h
+8
-0
wifi_config_config.h
modules/wifi_config/wifi_config_config.h
+11
-0
zlog.conf
zlog.conf
+1
-1
No files found.
app/device_change/device_wifi_manager.c
View file @
7f0fd331
...
...
@@ -725,6 +725,41 @@ int orange_pi_save_wifi(const char *ssid, const char *password) {
return
0
;
}
int
orange_pi_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
)
{
char
command
[
512
];
int
ret
;
if
(
!
ssid
||
!
ssid
[
0
])
return
-
1
;
if
(
password
&&
password
[
0
])
{
if
(
validate_wifi_credentials
(
ssid
,
password
)
!=
0
)
return
-
1
;
}
else
if
(
strlen
(
ssid
)
>=
SSID_MAX_LENGTH
||
strpbrk
(
ssid
,
"
\"
`$|;
\\
"
))
{
my_zlog_error
(
"SSID无效"
);
return
-
1
;
}
if
(
!
wifi_connection_exists
(
ssid
)
&&
enforce_saved_wifi_limit
(
ssid
)
!=
0
)
return
-
1
;
if
(
password
&&
password
[
0
])
{
snprintf
(
command
,
sizeof
(
command
),
"nmcli device wifi connect '%s' password '%s'"
,
ssid
,
password
);
}
else
{
snprintf
(
command
,
sizeof
(
command
),
"nmcli device wifi connect '%s'"
,
ssid
);
}
ret
=
system
(
command
);
if
(
ret
!=
0
)
{
my_zlog_error
(
"nmcli connect 失败: %s (ret=%d)"
,
ssid
,
ret
);
return
-
1
;
}
my_zlog_info
(
"[SUCCESS] 已连接WiFi: %s"
,
ssid
);
return
0
;
}
static
int
ensure_wifi_pool
(
void
)
{
pthread_mutex_lock
(
&
s_wifi_pool_mutex
);
if
(
!
s_wifi_pool
)
...
...
app/device_change/device_wifi_manager.h
View file @
7f0fd331
...
...
@@ -12,4 +12,10 @@ void device_wifi_rec_delete(cJSON *body);
//接收到保存wifi消息
void
device_wifi_rec_sava
(
cJSON
*
body
);
/* 保存 WiFi 配置(含满 10 个自动删最旧逻辑),不立即连接 */
int
orange_pi_save_wifi
(
const
char
*
ssid
,
const
char
*
password
);
/* 保存并立即连接 WiFi(含满 10 个自动删最旧逻辑) */
int
orange_pi_connect_wifi
(
const
char
*
ssid
,
const
char
*
password
);
#endif
\ No newline at end of file
app/main/main.c
View file @
7f0fd331
...
...
@@ -72,6 +72,11 @@ static int init_ipc_unix_server(void)
return
ipc_start_unix_server
(
NULL
);
}
static
int
init_wifi_config_agent
(
void
)
{
return
wifi_config_agent_start
();
}
static
int
init_worker_threads
(
void
)
{
return
thread_start_init
(
thread_exit_time
,
thread_mqtt_beat
,
...
...
@@ -99,6 +104,7 @@ static const AppInitStep s_init_steps[] = {
{
APP_INIT_MODULE_SERVICE
,
"mqtt_video"
,
http_mqtt_video_init
,
1
,
-
6
,
"请求mqtt服务器和video数据失败"
},
{
APP_INIT_MODULE_SERVICE
,
"go_deploy"
,
init_go_deploy
,
0
,
0
,
"Go 程序检查/部署失败"
},
{
APP_INIT_MODULE_SERVICE
,
"ipc_unix_server"
,
init_ipc_unix_server
,
1
,
-
8
,
"IPC Unix Socket Server 启动失败"
},
{
APP_INIT_MODULE_SERVICE
,
"wifi_config_agent"
,
init_wifi_config_agent
,
0
,
0
,
"WiFi配置助手UDP服务启动失败"
},
{
APP_INIT_RUNTIME
,
"worker_threads"
,
init_worker_threads
,
1
,
-
7
,
"多线程初始化失败"
},
};
...
...
@@ -137,6 +143,7 @@ static int run_init_steps(void)
static
void
app_shutdown
(
void
)
{
wifi_config_agent_stop
();
thread_end_close
();
device_end_close
(
g_device_type
);
self_control_thread_close
();
// 销毁自控的线程
...
...
app/main/pthread_open.c
View file @
7f0fd331
...
...
@@ -68,10 +68,13 @@ void *thread_exit_time(void *arg)
}
}
if
(
g_devcontrol_exit_count
>
=
5
)
if
(
g_devcontrol_exit_count
=
=
5
)
{
device_stop
(
g_device_type
);
g_devcontrol_exit_count
=
6
;
}
else
if
(
g_devcontrol_exit_count
>
5
)
{
g_devcontrol_exit_count
=
5
;
}
pthread_mutex_unlock
(
&
g_exit_count_mutex
);
}
...
...
@@ -148,8 +151,14 @@ static void run_chromium_cam_loop(void)
delay_s
(
5
);
while
(
1
)
{
if
(
s_webrtc_index
==
1
)
{
if
(
is_browser_running
()
==
true
)
if
(
!
browser_auto_restart_allowed
())
{
delay_ms
(
200
);
continue
;
}
if
(
is_browser_running
()
==
true
)
{
delay_ms
(
200
);
continue
;
}
my_zlog_info
(
"open cam (Chromium fallback)"
);
opencamsh
();
}
...
...
build/main
View file @
7f0fd331
No preview for this file type
drivers/devicecontrol/car/car0107_control.c
View file @
7f0fd331
...
...
@@ -2,14 +2,76 @@
#include "car0107_control.h"
#include "modules_common.h"
#include "gpio_common.h"
#include "mqtt_infor_handle.h"
#define CTRL_NONE 0
#define CTRL_PHONE 1
#define CTRL_STEERING 2
/* 双向车模电调 RC PWM:50Hz,脉宽 1000~2000us,中位 1500us */
#define CAR0107_ESC_US_MIN 1000
#define CAR0107_ESC_US_MAX 2000
#define CAR0107_ESC_US_NEUTRAL 1500
#define CAR0107_ESC_DEADZONE_LO 1450
#define CAR0107_ESC_DEADZONE_HI 1550
#define CAR0107_ESC_RAMP_STEP_NEUTRAL 8
#define CAR0107_ESC_RAMP_STEP_RUN 4
/* 正常前进/后退线性爬升:约 4us/tick */
#define CAR0107_ESC_NEUTRAL_HOLD_TICKS 8
#define CAR0107_ESC_BRAKE_HOLD_TICKS 20
/* 约 500ms @25ms tick,满刹保持 */
#define CAR0107_PHONE_VAL_MAX 200
/* App 前后油门最大值 */
#define CAR0107_FORWARD_PWM_CAP 83
/* 前进 PWM 封顶 */
#define CAR0107_FWD_US_RUN_MIN (79 * 20)
/* val=53 前进最低行走 ~1580us */
#define CAR0107_FWD_US_RUN_MAX (CAR0107_FORWARD_PWM_CAP * 20)
/* val=200 ~1660us */
#define CAR0107_BACK_US_RUN_MIN (71 * 20)
/* val=53 后退最低行走 ~1420us */
#define CAR0107_BACK_US_RUN_MAX (58 * 20)
/* val=200 后退最深 ~1160us */
typedef
enum
{
CAR0107_ESC_DIR_FORWARD
=
1
,
/* App mode1 前进:高脉宽(与 0101 相反) */
CAR0107_ESC_DIR_BACK
=
2
,
/* App mode2 后退:低脉宽(与 0101 相反) */
}
car0107_esc_dir_t
;
typedef
enum
{
CAR0107_ESC_STOP
=
0
,
CAR0107_ESC_FORWARD
,
CAR0107_ESC_REVERSE
,
CAR0107_ESC_TRANSITION
}
car0107_esc_state_t
;
typedef
enum
{
CAR0107_TRANS_BRAKE
=
0
,
/* 前进松手/换后退:1000us 满刹 */
CAR0107_TRANS_NEUTRAL_HOLD
,
/* 回中位 1500us */
CAR0107_TRANS_RAMP_RUN
/* 慢速爬到目标油门 */
}
car0107_trans_phase_t
;
typedef
struct
{
car0107_esc_state_t
state
;
car0107_trans_phase_t
trans_phase
;
int
current_us
;
int
target_us
;
int
cmd_dir
;
int
pending_dir
;
int
pending_gval
;
int
brake_dir
;
int
brake_ticks
;
int
neutral_hold_ticks
;
}
car0107_esc_ctx_t
;
#define STEER_STEP_TURN 60
#define STEER_STEP_RETURN 90
#define REV_TARGET_SCALE_NUM 6
#define REV_TARGET_SCALE_DEN 10
#define CAR0107_MAX_INPUT_SPEED 200
#define CAR0107_ACTIVE_THRESHOLD 50
#define CAR0107_MAX_TARGET_BACK 100
#define CAR0107_MAX_TARGET_FRONT 80
#define CAR0107_ESC_RELEASE_MAX 52
/* val<=52:松手/刹车/停下 */
#define CAR0107_ESC_GVAL_MIN 53
/* val>=53 才视为有效油门 */
#define STEER_GVAL_MAX 150
static
pthread_mutex_t
s_car0107_hw_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
int
s_control_source
=
CTRL_NONE
;
static
long
long
s_last_control_ms
=
0
;
static
int
s_control_source
=
CTRL_NONE
;
static
int
s_target_angle
=
90
;
static
int
s_current_angle
=
90
;
static
int
s_steering_mode
=
0
;
...
...
@@ -21,37 +83,38 @@ static int s_current_speed = 0;
static
int
s_speed_mode
=
0
;
static
int
s_speed_timeout
=
0
;
static
int
s_phone_speed_mode
=
0
;
static
int
s_phone_current_gval
=
0
;
static
int
s_phone_pending_mode
=
0
;
static
int
s_phone_pending_gval
=
0
;
#define STEER_STEP_TURN 60
#define STEER_STEP_RETURN 90
#define REV_TARGET_SCALE_NUM 6
#define REV_TARGET_SCALE_DEN 10
#define ACCEL_STEP 5
#define DECEL_STEP 6
#define BRAKE_STEP 10
#define CAR0107_MAX_INPUT_SPEED 200
#define CAR0107_ACTIVE_THRESHOLD 50
#define CAR0107_MAX_TARGET_BACK 100
#define CAR0107_MAX_TARGET_FRONT 80
#define STEER_GVAL_MAX 120
#define CAR0107_PWM_FORWARD_MIN 58
#define CAR0107_PWM_BACK_MAX 84
static
int
car0107_clamp_input
(
int
val
)
{
if
(
val
<
0
)
{
return
0
;
}
if
(
val
>
CAR0107_MAX_INPUT_SPEED
)
{
return
CAR0107_MAX_INPUT_SPEED
;
}
/* App 每帧分开发 mode1/mode2 两条 MQTT,必须合并后再决策,避免后退时 mode1 串扰成前进 */
static
int
s_phone_mode1_val
=
0
;
static
int
s_phone_mode2_val
=
0
;
static
int
s_phone_last_mode
=
0
;
/* 最近更新的通道,双通道同时有油时以它为准 */
static
car0107_esc_ctx_t
s_esc
=
{
.
state
=
CAR0107_ESC_STOP
,
.
trans_phase
=
CAR0107_TRANS_BRAKE
,
.
current_us
=
CAR0107_ESC_US_NEUTRAL
,
.
target_us
=
CAR0107_ESC_US_NEUTRAL
,
.
cmd_dir
=
0
,
.
pending_dir
=
0
,
.
pending_gval
=
0
,
.
brake_dir
=
0
,
.
brake_ticks
=
0
,
.
neutral_hold_ticks
=
0
,
};
static
int
car0107_clamp
(
int
val
,
int
lo
,
int
hi
)
{
if
(
val
<
lo
)
return
lo
;
if
(
val
>
hi
)
return
hi
;
return
val
;
}
static
int
car0107_input_to_gval
(
int
val
)
{
static
int
car0107_clamp_input
(
int
val
)
{
return
car0107_clamp
(
val
,
0
,
CAR0107_PHONE_VAL_MAX
);
}
static
int
car0107_input_to_gval
(
int
val
)
{
val
=
car0107_clamp_input
(
val
);
if
(
val
<
CAR0107_ACTIVE_THRESHOLD
)
{
return
0
;
...
...
@@ -61,7 +124,8 @@ static int car0107_input_to_gval(int val) {
(
CAR0107_MAX_INPUT_SPEED
-
CAR0107_ACTIVE_THRESHOLD
);
}
static
int
car0107_input_to_target_speed
(
int
val
,
int
max_target
)
{
static
int
car0107_input_to_target_speed
(
int
val
,
int
max_target
)
{
val
=
car0107_clamp_input
(
val
);
if
(
val
<
CAR0107_ACTIVE_THRESHOLD
)
{
return
0
;
...
...
@@ -70,23 +134,494 @@ static int car0107_input_to_target_speed(int val, int max_target) {
(
CAR0107_MAX_INPUT_SPEED
-
CAR0107_ACTIVE_THRESHOLD
);
}
static
void
car0107_calculate_L_R
(
int
angle
)
{
static
int
car0107_speed_to_gval
(
int
speed
)
{
if
(
speed
<=
0
)
{
return
0
;
}
int
gval
=
50
+
(
speed
*
100
)
/
160
;
return
car0107_clamp
(
gval
,
0
,
STEER_GVAL_MAX
);
}
/* App 前后油门:53~200 线性映射脉宽;val=53 即有最低行走速度,200 封顶 */
static
int
car0107_phone_val_to_us
(
int
dir
,
int
raw_val
)
{
int
span
;
int
t
;
if
(
raw_val
<
CAR0107_ESC_GVAL_MIN
)
{
return
CAR0107_ESC_US_NEUTRAL
;
}
raw_val
=
car0107_clamp
(
raw_val
,
CAR0107_ESC_GVAL_MIN
,
CAR0107_PHONE_VAL_MAX
);
span
=
CAR0107_PHONE_VAL_MAX
-
CAR0107_ESC_GVAL_MIN
;
if
(
span
<=
0
)
{
return
CAR0107_ESC_US_NEUTRAL
;
}
t
=
raw_val
-
CAR0107_ESC_GVAL_MIN
;
if
(
dir
==
CAR0107_ESC_DIR_FORWARD
)
{
return
CAR0107_FWD_US_RUN_MIN
+
t
*
(
CAR0107_FWD_US_RUN_MAX
-
CAR0107_FWD_US_RUN_MIN
)
/
span
;
}
if
(
dir
==
CAR0107_ESC_DIR_BACK
)
{
return
CAR0107_BACK_US_RUN_MIN
-
t
*
(
CAR0107_BACK_US_RUN_MIN
-
CAR0107_BACK_US_RUN_MAX
)
/
span
;
}
return
CAR0107_ESC_US_NEUTRAL
;
}
/* 方向盘内部 gval(约 53~150) */
static
int
car0107_steer_gval_to_us
(
int
dir
,
int
gval
)
{
int
span
;
int
t
;
if
(
gval
<
CAR0107_ESC_GVAL_MIN
)
{
return
CAR0107_ESC_US_NEUTRAL
;
}
gval
=
car0107_clamp
(
gval
,
CAR0107_ESC_GVAL_MIN
,
STEER_GVAL_MAX
);
span
=
STEER_GVAL_MAX
-
CAR0107_ESC_GVAL_MIN
;
if
(
span
<=
0
)
{
return
CAR0107_ESC_US_NEUTRAL
;
}
t
=
gval
-
CAR0107_ESC_GVAL_MIN
;
if
(
dir
==
CAR0107_ESC_DIR_FORWARD
)
{
return
CAR0107_FWD_US_RUN_MIN
+
t
*
(
CAR0107_FWD_US_RUN_MAX
-
CAR0107_FWD_US_RUN_MIN
)
/
span
;
}
if
(
dir
==
CAR0107_ESC_DIR_BACK
)
{
return
CAR0107_BACK_US_RUN_MIN
-
t
*
(
CAR0107_BACK_US_RUN_MIN
-
CAR0107_BACK_US_RUN_MAX
)
/
span
;
}
return
CAR0107_ESC_US_NEUTRAL
;
}
static
int
car0107_throttle_to_us
(
int
dir
,
int
val
)
{
if
(
s_control_source
==
CTRL_PHONE
)
{
return
car0107_phone_val_to_us
(
dir
,
val
);
}
return
car0107_steer_gval_to_us
(
dir
,
val
);
}
static
int
car0107_phone_clamp_val
(
int
raw_val
)
{
if
(
raw_val
<=
CAR0107_ESC_RELEASE_MAX
)
{
return
0
;
}
return
car0107_clamp
(
raw_val
,
CAR0107_ESC_GVAL_MIN
,
CAR0107_PHONE_VAL_MAX
);
}
/*
* 仅前进松手/换后退时打 1000us 刹车。
* 正常后退最深约 1160us,与刹车 1000us 分开。
*/
static
int
car0107_forward_brake_us
(
void
)
{
return
CAR0107_ESC_US_MIN
;
}
static
int
car0107_forward_brake_pwm
(
void
)
{
return
car0107_forward_brake_us
()
/
20
;
}
static
void
car0107_esc_write_pwm
(
int
pwm_val
)
{
pwm_val
=
car0107_clamp
(
pwm_val
,
CAR0107_ESC_US_MIN
/
20
,
CAR0107_ESC_US_MAX
/
20
);
pwmWrite
(
PWM_PIN_SPEED
,
pwm_val
);
s_esc
.
current_us
=
pwm_val
*
20
;
}
static
void
car0107_esc_begin_forward_brake
(
int
pending_dir
,
int
pending_gval
)
{
s_esc
.
state
=
CAR0107_ESC_TRANSITION
;
s_esc
.
trans_phase
=
CAR0107_TRANS_BRAKE
;
s_esc
.
pending_dir
=
pending_dir
;
s_esc
.
pending_gval
=
pending_gval
;
s_esc
.
brake_dir
=
CAR0107_ESC_DIR_BACK
;
s_esc
.
brake_ticks
=
CAR0107_ESC_BRAKE_HOLD_TICKS
;
s_esc
.
neutral_hold_ticks
=
0
;
s_esc
.
target_us
=
car0107_forward_brake_us
();
s_esc
.
current_us
=
s_esc
.
target_us
;
car0107_esc_write_pwm
(
car0107_forward_brake_pwm
());
}
static
void
car0107_esc_begin_neutral_stop
(
void
)
{
s_esc
.
cmd_dir
=
0
;
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
s_esc
.
brake_dir
=
0
;
s_esc
.
brake_ticks
=
0
;
s_esc
.
neutral_hold_ticks
=
0
;
s_esc
.
target_us
=
CAR0107_ESC_US_NEUTRAL
;
s_esc
.
state
=
CAR0107_ESC_STOP
;
}
static
int
car0107_esc_forward_motion_active
(
void
)
{
if
(
s_esc
.
cmd_dir
!=
CAR0107_ESC_DIR_FORWARD
)
{
return
0
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_FORWARD
)
{
return
1
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
trans_phase
==
CAR0107_TRANS_RAMP_RUN
)
{
return
1
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
trans_phase
==
CAR0107_TRANS_BRAKE
&&
s_esc
.
brake_dir
==
CAR0107_ESC_DIR_BACK
)
{
return
1
;
}
return
0
;
}
static
int
car0107_esc_back_motion_active
(
void
)
{
if
(
s_esc
.
cmd_dir
!=
CAR0107_ESC_DIR_BACK
)
{
return
0
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_REVERSE
)
{
return
1
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
trans_phase
==
CAR0107_TRANS_RAMP_RUN
)
{
return
1
;
}
return
0
;
}
static
void
car0107_notify_control_locked
(
void
)
{
s_last_control_ms
=
get_current_time_millis
();
}
void
car0107_notify_control_activity
(
void
)
{
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
car0107_notify_control_locked
();
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
long
long
car0107_last_control_ms
(
void
)
{
long
long
ts
;
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
ts
=
s_last_control_ms
;
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
return
ts
;
}
int
car0107_esc_is_active
(
void
)
{
int
active
;
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
)
{
active
=
(
s_esc
.
trans_phase
==
CAR0107_TRANS_BRAKE
);
}
else
if
(
s_esc
.
state
==
CAR0107_ESC_STOP
)
{
active
=
0
;
}
else
{
active
=
(
s_esc
.
current_us
<
CAR0107_ESC_DEADZONE_LO
||
s_esc
.
current_us
>
CAR0107_ESC_DEADZONE_HI
);
}
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
return
active
;
}
static
void
car0107_esc_output_us
(
int
us
,
int
snap_deadzone
)
{
int
pwm_val
;
us
=
car0107_clamp
(
us
,
CAR0107_ESC_US_MIN
,
CAR0107_ESC_US_MAX
);
if
(
snap_deadzone
&&
us
>=
CAR0107_ESC_DEADZONE_LO
&&
us
<=
CAR0107_ESC_DEADZONE_HI
)
{
us
=
CAR0107_ESC_US_NEUTRAL
;
}
pwm_val
=
us
/
20
;
car0107_esc_write_pwm
(
pwm_val
);
}
static
void
car0107_esc_write_us
(
int
us
)
{
car0107_esc_output_us
(
us
,
1
);
}
static
void
car0107_esc_start_ramp
(
int
dir
,
int
throttle_val
)
{
int
from_stop
=
(
s_esc
.
state
==
CAR0107_ESC_STOP
);
s_esc
.
cmd_dir
=
dir
;
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
s_esc
.
brake_dir
=
0
;
s_esc
.
target_us
=
car0107_throttle_to_us
(
dir
,
throttle_val
);
s_esc
.
state
=
CAR0107_ESC_TRANSITION
;
s_esc
.
trans_phase
=
CAR0107_TRANS_RAMP_RUN
;
if
(
from_stop
)
{
/* val>52 首帧立即到对应脉宽(53=最低行走速度) */
s_esc
.
current_us
=
s_esc
.
target_us
;
}
}
static
void
car0107_esc_begin_transition
(
int
pending_dir
,
int
pending_gval
)
{
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
pending_dir
==
pending_dir
&&
s_esc
.
trans_phase
!=
CAR0107_TRANS_BRAKE
)
{
s_esc
.
pending_gval
=
pending_gval
;
return
;
}
/* 前进中换后退:先 1000us 刹车 */
car0107_esc_begin_forward_brake
(
pending_dir
,
pending_gval
);
}
static
void
car0107_esc_request_stop
(
void
)
{
if
(
car0107_esc_forward_motion_active
())
{
car0107_esc_begin_forward_brake
(
0
,
0
);
return
;
}
if
(
car0107_esc_back_motion_active
())
{
car0107_esc_begin_neutral_stop
();
return
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
trans_phase
==
CAR0107_TRANS_BRAKE
)
{
return
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
&&
s_esc
.
trans_phase
==
CAR0107_TRANS_NEUTRAL_HOLD
)
{
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
return
;
}
car0107_esc_begin_neutral_stop
();
}
static
void
car0107_esc_request
(
int
dir
,
int
gval
)
{
if
(
dir
==
0
||
gval
<
CAR0107_ESC_GVAL_MIN
)
{
car0107_esc_request_stop
();
return
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_STOP
)
{
car0107_esc_start_ramp
(
dir
,
gval
);
return
;
}
if
(
s_esc
.
state
==
CAR0107_ESC_TRANSITION
)
{
if
(
s_esc
.
trans_phase
==
CAR0107_TRANS_BRAKE
||
s_esc
.
trans_phase
==
CAR0107_TRANS_NEUTRAL_HOLD
)
{
s_esc
.
pending_dir
=
dir
;
s_esc
.
pending_gval
=
gval
;
return
;
}
if
(
s_esc
.
trans_phase
==
CAR0107_TRANS_RAMP_RUN
)
{
s_esc
.
target_us
=
car0107_throttle_to_us
(
s_esc
.
cmd_dir
,
gval
);
return
;
}
return
;
}
/* 后退切前进:不刹车,直接爬升 */
if
(
s_esc
.
cmd_dir
==
CAR0107_ESC_DIR_BACK
&&
dir
==
CAR0107_ESC_DIR_FORWARD
)
{
car0107_esc_start_ramp
(
dir
,
gval
);
return
;
}
/* 前进切后退:先 1000us 刹车 */
if
(
s_esc
.
cmd_dir
==
CAR0107_ESC_DIR_FORWARD
&&
dir
==
CAR0107_ESC_DIR_BACK
)
{
car0107_esc_begin_transition
(
dir
,
gval
);
return
;
}
s_esc
.
cmd_dir
=
dir
;
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
s_esc
.
target_us
=
car0107_throttle_to_us
(
dir
,
gval
);
s_esc
.
state
=
(
dir
==
CAR0107_ESC_DIR_FORWARD
)
?
CAR0107_ESC_FORWARD
:
CAR0107_ESC_REVERSE
;
}
static
int
car0107_esc_ramp_step_for
(
int
current
,
int
target
)
{
(
void
)
current
;
(
void
)
target
;
return
CAR0107_ESC_RAMP_STEP_RUN
;
}
static
void
car0107_esc_apply_phone
(
int
dir
,
int
gval
)
{
if
(
gval
<
CAR0107_ESC_GVAL_MIN
)
{
return
;
}
car0107_esc_request
(
dir
,
gval
);
/* 不在此处瞬间写满 PWM;刹车/回中/慢爬均由 esc_tick 输出 */
}
static
void
car0107_phone_apply_merged
(
void
)
{
int
fwd
=
s_phone_mode1_val
;
int
back
=
s_phone_mode2_val
;
int
fwd_val
=
car0107_phone_clamp_val
(
fwd
);
int
back_val
=
car0107_phone_clamp_val
(
back
);
/* mode1/mode2 收到 val=0 或 <=52 都表示停下;以最近更新的通道为准 */
if
(
s_phone_last_mode
==
2
)
{
if
(
back_val
>
0
)
{
car0107_esc_apply_phone
(
CAR0107_ESC_DIR_BACK
,
back_val
);
}
else
{
car0107_esc_request_stop
();
}
return
;
}
if
(
s_phone_last_mode
==
1
)
{
if
(
fwd_val
>
0
)
{
car0107_esc_apply_phone
(
CAR0107_ESC_DIR_FORWARD
,
fwd_val
);
}
else
{
car0107_esc_request_stop
();
}
return
;
}
if
(
fwd_val
==
0
&&
back_val
==
0
)
{
car0107_esc_request_stop
();
}
}
static
int
car0107_esc_ramp_toward
(
int
current
,
int
target
,
int
step
)
{
if
(
current
<
target
)
{
current
+=
step
;
if
(
current
>
target
)
{
current
=
target
;
}
}
else
if
(
current
>
target
)
{
current
-=
step
;
if
(
current
<
target
)
{
current
=
target
;
}
}
return
current
;
}
static
void
car0107_esc_tick
(
void
)
{
switch
(
s_esc
.
state
)
{
case
CAR0107_ESC_STOP
:
s_esc
.
current_us
=
car0107_esc_ramp_toward
(
s_esc
.
current_us
,
CAR0107_ESC_US_NEUTRAL
,
CAR0107_ESC_RAMP_STEP_NEUTRAL
);
break
;
case
CAR0107_ESC_FORWARD
:
case
CAR0107_ESC_REVERSE
:
s_esc
.
current_us
=
car0107_esc_ramp_toward
(
s_esc
.
current_us
,
s_esc
.
target_us
,
car0107_esc_ramp_step_for
(
s_esc
.
current_us
,
s_esc
.
target_us
));
break
;
case
CAR0107_ESC_TRANSITION
:
if
(
s_esc
.
trans_phase
==
CAR0107_TRANS_BRAKE
)
{
car0107_esc_write_pwm
(
car0107_forward_brake_pwm
());
if
(
s_esc
.
brake_ticks
>
0
)
{
s_esc
.
brake_ticks
--
;
}
if
(
s_esc
.
brake_ticks
<=
0
)
{
s_esc
.
trans_phase
=
CAR0107_TRANS_NEUTRAL_HOLD
;
s_esc
.
neutral_hold_ticks
=
CAR0107_ESC_NEUTRAL_HOLD_TICKS
;
s_esc
.
current_us
=
CAR0107_ESC_US_NEUTRAL
;
s_esc
.
target_us
=
CAR0107_ESC_US_NEUTRAL
;
}
}
else
if
(
s_esc
.
trans_phase
==
CAR0107_TRANS_NEUTRAL_HOLD
)
{
s_esc
.
current_us
=
CAR0107_ESC_US_NEUTRAL
;
if
(
s_esc
.
neutral_hold_ticks
>
0
)
{
s_esc
.
neutral_hold_ticks
--
;
}
if
(
s_esc
.
neutral_hold_ticks
<=
0
)
{
if
(
s_esc
.
pending_dir
==
0
)
{
s_esc
.
state
=
CAR0107_ESC_STOP
;
s_esc
.
cmd_dir
=
0
;
s_esc
.
target_us
=
CAR0107_ESC_US_NEUTRAL
;
s_esc
.
trans_phase
=
CAR0107_TRANS_BRAKE
;
}
else
{
s_esc
.
cmd_dir
=
s_esc
.
pending_dir
;
s_esc
.
target_us
=
car0107_throttle_to_us
(
s_esc
.
pending_dir
,
s_esc
.
pending_gval
);
s_esc
.
current_us
=
CAR0107_ESC_US_NEUTRAL
;
s_esc
.
trans_phase
=
CAR0107_TRANS_RAMP_RUN
;
}
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
}
}
else
if
(
s_esc
.
trans_phase
==
CAR0107_TRANS_RAMP_RUN
)
{
s_esc
.
current_us
=
car0107_esc_ramp_toward
(
s_esc
.
current_us
,
s_esc
.
target_us
,
CAR0107_ESC_RAMP_STEP_RUN
);
if
(
s_esc
.
current_us
==
s_esc
.
target_us
)
{
s_esc
.
state
=
(
s_esc
.
cmd_dir
==
CAR0107_ESC_DIR_FORWARD
)
?
CAR0107_ESC_FORWARD
:
CAR0107_ESC_REVERSE
;
s_esc
.
trans_phase
=
CAR0107_TRANS_BRAKE
;
}
}
break
;
default:
break
;
}
if
(
s_esc
.
state
!=
CAR0107_ESC_TRANSITION
||
s_esc
.
trans_phase
!=
CAR0107_TRANS_BRAKE
)
{
int
snap
=
(
s_esc
.
state
==
CAR0107_ESC_STOP
);
car0107_esc_output_us
(
s_esc
.
current_us
,
snap
);
}
}
static
void
car0107_calculate_L_R
(
int
angle
)
{
if
(
angle
<
0
)
{
angle
=
0
;
}
else
if
(
angle
>
180
)
{
angle
=
180
;
}
float
pulse_width
=
(
angle
/
180
.
0
)
*
2
+
0
.
5
;
float
pulse_width
=
(
angle
/
180
.
0
)
*
2
+
0
.
5
;
float
period
=
1000
.
0
/
50
;
int
val
=
(
int
)((
pulse_width
/
period
)
*
1000
);
pwmWrite
(
PWM_PIN_CHANGE
,
val
);
int
val
=
(
int
)((
pulse_width
/
period
)
*
1000
);
pwmWrite
(
PWM_PIN_CHANGE
,
val
);
}
void
car0107_steering_set_angle_sync
(
int
angle
)
{
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
s_current_angle
=
angle
;
s_target_angle
=
angle
;
car0107_calculate_L_R
(
angle
);
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
static
void
car0107_reset_steering_state
(
void
)
{
static
void
car0107_reset_steering_state
(
void
)
{
s_control_source
=
CTRL_NONE
;
s_target_speed
=
0
;
s_current_speed
=
0
;
s_speed_mode
=
0
;
s_phone_mode1_val
=
0
;
s_phone_mode2_val
=
0
;
s_phone_last_mode
=
0
;
s_target_angle
=
90
;
s_current_angle
=
90
;
s_steering_mode
=
0
;
...
...
@@ -95,147 +630,77 @@ static void car0107_reset_steering_state(void) {
s_steering_timeout
=
0
;
}
static
void
car0107_reset_phone_state
(
void
)
{
s_phone_speed_mode
=
0
;
s_phone_current_gval
=
0
;
s_phone_pending_mode
=
0
;
s_phone_pending_gval
=
0
;
static
void
car0107_reset_esc_state
(
void
)
{
s_esc
.
state
=
CAR0107_ESC_STOP
;
s_esc
.
trans_phase
=
CAR0107_TRANS_BRAKE
;
s_esc
.
target_us
=
CAR0107_ESC_US_NEUTRAL
;
s_esc
.
cmd_dir
=
0
;
s_esc
.
pending_dir
=
0
;
s_esc
.
pending_gval
=
0
;
s_esc
.
brake_dir
=
0
;
s_esc
.
brake_ticks
=
0
;
s_esc
.
neutral_hold_ticks
=
0
;
}
static
void
car0107_apply_stop
(
void
)
{
pwmWrite
(
PWM_PIN_SPEED
,
75
);
static
void
car0107_apply_stop
(
void
)
{
car0107_reset_esc_state
();
car0107_esc_write_us
(
CAR0107_ESC_US_NEUTRAL
);
car0107_calculate_L_R
(
90
);
}
static
void
car0107_stop_speed_now
(
void
)
{
static
void
car0107_stop_speed_now
(
void
)
{
s_target_speed
=
0
;
s_current_speed
=
0
;
s_speed_mode
=
0
;
s_speed_timeout
=
0
;
car0107_reset_phone_state
();
car0107_apply_stop
();
}
static
int
car0107_speed_to_gval
(
int
speed
)
{
if
(
speed
<=
0
)
{
return
0
;
}
int
gval
=
50
+
(
speed
*
(
STEER_GVAL_MAX
-
50
))
/
CAR0107_MAX_TARGET_BACK
;
if
(
gval
>
STEER_GVAL_MAX
)
{
gval
=
STEER_GVAL_MAX
;
}
return
gval
;
car0107_esc_request
(
0
,
0
);
}
/*车中间值,停止舵机中间函数*/
void
car0107_middle_pwm
()
{
void
car0107_middle_pwm
(
void
)
{
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
car0107_reset_steering_state
();
car0107_reset_phone_state
();
car0107_apply_stop
();
/* 异常停止不算用户控制,不刷新 idle 保活计时 */
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
static
void
car0107_mode_1_flont
(
int
gval
)
{
int
a
=
0
,
b
=
1
;
if
(
gval
<
50
)
{
pwmWrite
(
PWM_PIN_SPEED
,
75
);
}
else
if
(
gval
<=
60
)
{
pwmWrite
(
PWM_PIN_SPEED
,
71
-
b
+
a
);
}
else
if
(
gval
<=
70
)
{
pwmWrite
(
PWM_PIN_SPEED
,
70
-
b
+
a
);
}
else
if
(
gval
>
70
){
int
back_speed
=
70
-
(
gval
-
70
)
/
10
-
b
+
a
;
if
(
back_speed
<
CAR0107_PWM_FORWARD_MIN
)
{
back_speed
=
CAR0107_PWM_FORWARD_MIN
;
}
pwmWrite
(
PWM_PIN_SPEED
,
back_speed
);
}
}
static
void
car0107_mode_2_back
(
int
gval
)
{
int
b
=
0
,
a
=
1
;
if
(
gval
<
50
)
{
pwmWrite
(
PWM_PIN_SPEED
,
75
);
}
else
if
(
gval
<=
60
)
{
pwmWrite
(
PWM_PIN_SPEED
,
79
+
b
-
a
);
}
else
if
(
gval
<=
70
)
{
pwmWrite
(
PWM_PIN_SPEED
,
80
+
b
-
a
);
}
else
if
(
gval
>
70
){
int
flont_speed
=
80
+
(
gval
-
70
)
/
10
+
b
-
a
;
if
(
flont_speed
>
CAR0107_PWM_BACK_MAX
)
{
flont_speed
=
CAR0107_PWM_BACK_MAX
;
}
pwmWrite
(
PWM_PIN_SPEED
,
flont_speed
);
}
}
static
void
car0107_mode_3_left
(
int
gval
)
{
int
b
=
15
;
if
(
gval
<
45
){
car0107_calculate_L_R
(
90
);
}
else
if
(
gval
<
70
){
car0107_calculate_L_R
(
50
+
gval
+
b
);
}
else
if
(
gval
>=
70
){
car0107_calculate_L_R
(
135
);
}
}
static
void
car0107_mode_4_right
(
int
gval
)
{
int
b
=
10
;
if
(
gval
<
45
){
car0107_calculate_L_R
(
90
);
}
else
if
(
gval
<
70
){
car0107_calculate_L_R
(
130
-
gval
-
b
);
}
else
if
(
gval
>=
70
){
car0107_calculate_L_R
(
35
);
}
}
static
void
car0107_phone_apply_speed
(
void
)
{
if
(
s_phone_speed_mode
==
1
)
{
car0107_mode_2_back
(
s_phone_current_gval
);
}
else
if
(
s_phone_speed_mode
==
2
)
{
car0107_mode_1_flont
(
s_phone_current_gval
);
static
void
car0107_mode_3_left
(
int
gval
)
{
int
b
=
15
;
if
(
gval
<
45
)
{
car0107_calculate_L_R
(
90
);
}
else
if
(
gval
<
70
)
{
car0107_calculate_L_R
(
50
+
gval
+
b
);
}
else
{
pwmWrite
(
PWM_PIN_SPEED
,
7
5
);
car0107_calculate_L_R
(
13
5
);
}
}
static
void
car0107_set_phone_speed_command
(
int
new_mode
,
int
new_gval
)
{
if
(
new_gval
<
CAR0107_ACTIVE_THRESHOLD
)
{
car0107_reset_phone_state
();
car0107_apply_stop
();
return
;
}
if
(
s_phone_current_gval
>
CAR0107_ACTIVE_THRESHOLD
&&
s_phone_speed_mode
!=
0
&&
new_mode
!=
s_phone_speed_mode
)
{
s_phone_pending_mode
=
new_mode
;
s_phone_pending_gval
=
new_gval
;
s_phone_speed_mode
=
0
;
pwmWrite
(
PWM_PIN_SPEED
,
75
);
return
;
}
if
(
s_phone_speed_mode
==
0
&&
s_phone_pending_mode
!=
0
)
{
s_phone_pending_mode
=
new_mode
;
s_phone_pending_gval
=
new_gval
;
return
;
static
void
car0107_mode_4_right
(
int
gval
)
{
int
b
=
10
;
if
(
gval
<
45
)
{
car0107_calculate_L_R
(
90
);
}
else
if
(
gval
<
70
)
{
car0107_calculate_L_R
(
130
-
gval
-
b
);
}
else
{
car0107_calculate_L_R
(
35
);
}
s_phone_pending_mode
=
0
;
s_phone_pending_gval
=
0
;
s_phone_speed_mode
=
new_mode
;
s_phone_current_gval
=
new_gval
;
car0107_phone_apply_speed
();
}
void
car0107_control_change
(
int
*
buf
)
{
void
car0107_control_change
(
int
*
buf
)
{
int
mode
=
buf
[
1
];
int
raw_val
=
car0107_clamp_input
(
buf
[
2
]);
pthread_mutex_lock
(
&
g_exit_count_mutex
);
g_devcontrol_exit_count
=
0
;
pthread_mutex_unlock
(
&
g_exit_count_mutex
);
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
s_control_source
=
CTRL_PHONE
;
s_target_speed
=
0
;
...
...
@@ -243,65 +708,40 @@ void car0107_control_change(int *buf) {
s_speed_mode
=
0
;
s_speed_timeout
=
0
;
s_steering_timeout
=
0
;
car0107_notify_control_locked
();
if
((
mode
==
1
||
mode
==
2
)
&&
raw_val
==
0
)
{
car0107_stop_speed_now
();
if
(
mode
==
1
)
{
s_phone_last_mode
=
1
;
s_phone_mode1_val
=
raw_val
;
car0107_phone_apply_merged
();
car0107_esc_tick
();
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
return
;
}
int
val
=
car0107_input_to_gval
(
raw_val
);
switch
(
mode
){
case
1
:
car0107_set_phone_speed_command
(
1
,
val
);
break
;
case
2
:
car0107_set_phone_speed_command
(
2
,
val
);
break
;
case
3
:
car0107_mode_3_left
(
val
);
break
;
case
4
:
car0107_mode_4_right
(
val
);
break
;
default:
break
;
}
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
static
void
car0107_phone_smooth_process
(
void
)
{
if
(
s_control_source
!=
CTRL_PHONE
)
{
return
;
}
if
(
s_phone_speed_mode
!=
0
||
s_phone_pending_mode
==
0
)
{
return
;
}
if
(
s_phone_current_gval
<
CAR0107_ACTIVE_THRESHOLD
)
{
s_phone_speed_mode
=
s_phone_pending_mode
;
s_phone_current_gval
=
s_phone_pending_gval
;
s_phone_pending_mode
=
0
;
s_phone_pending_gval
=
0
;
car0107_phone_apply_speed
();
if
(
mode
==
2
)
{
s_phone_last_mode
=
2
;
s_phone_mode2_val
=
raw_val
;
car0107_phone_apply_merged
();
car0107_esc_tick
();
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
return
;
}
s_phone_current_gval
-=
BRAKE_STEP
;
if
(
s_phone_current_gval
<
CAR0107_ACTIVE_THRESHOLD
)
{
s_phone_current_gval
=
0
;
pwmWrite
(
PWM_PIN_SPEED
,
75
);
s_phone_speed_mode
=
s_phone_pending_mode
;
s_phone_current_gval
=
s_phone_pending_gval
;
s_phone_pending_mode
=
0
;
s_phone_pending_gval
=
0
;
car0107_phone_apply_speed
();
}
else
{
pwmWrite
(
PWM_PIN_SPEED
,
75
);
switch
(
mode
)
{
case
3
:
car0107_mode_3_left
(
raw_val
);
break
;
case
4
:
car0107_mode_4_right
(
raw_val
);
break
;
default:
break
;
}
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
static
void
car0107_speed_smooth_process
(
void
)
{
static
void
car0107_speed_smooth_process
(
void
)
{
if
(
s_control_source
!=
CTRL_STEERING
)
{
return
;
}
...
...
@@ -309,7 +749,7 @@ static void car0107_speed_smooth_process(void) {
s_speed_timeout
--
;
if
(
s_speed_timeout
<=
0
)
{
s_current_speed
=
0
;
car0107_
apply_stop
(
);
car0107_
esc_request
(
0
,
0
);
if
(
s_steering_timeout
<=
0
)
{
s_control_source
=
CTRL_NONE
;
}
...
...
@@ -317,35 +757,37 @@ static void car0107_speed_smooth_process(void) {
}
int
limited_target
=
s_target_speed
;
if
(
s_speed_mode
==
2
)
{
if
(
s_speed_mode
==
1
)
{
limited_target
=
(
limited_target
*
REV_TARGET_SCALE_NUM
)
/
REV_TARGET_SCALE_DEN
;
}
if
(
s_speed_mode
==
0
)
{
if
(
s_current_speed
>
0
)
{
s_current_speed
-=
DECEL_STEP
;
s_current_speed
-=
6
;
if
(
s_current_speed
<
0
)
{
s_current_speed
=
0
;
}
}
car0107_esc_request
(
0
,
0
);
}
else
if
(
s_speed_mode
==
1
||
s_speed_mode
==
2
)
{
if
(
s_current_speed
<
limited_target
)
{
s_current_speed
+=
ACCEL_STEP
;
s_current_speed
+=
5
;
}
else
if
(
s_current_speed
>
limited_target
)
{
s_current_speed
-=
DECEL_STEP
;
s_current_speed
-=
6
;
}
}
if
(
s_current_speed
<=
0
||
s_speed_mode
==
0
)
{
car0107_apply_stop
();
}
else
if
(
s_speed_mode
==
1
)
{
car0107_mode_2_back
(
car0107_speed_to_gval
(
s_current_speed
));
}
else
if
(
s_speed_mode
==
2
)
{
car0107_mode_1_flont
(
car0107_speed_to_gval
(
s_current_speed
));
if
(
s_current_speed
<=
0
)
{
car0107_esc_request
(
0
,
0
);
}
else
{
int
gval
=
car0107_speed_to_gval
(
s_current_speed
);
int
dir
=
(
s_speed_mode
==
1
)
?
CAR0107_ESC_DIR_BACK
:
CAR0107_ESC_DIR_FORWARD
;
car0107_esc_request
(
dir
,
gval
);
}
}
}
static
void
car0107_steering_angle_smooth_process
(
void
)
{
static
void
car0107_steering_angle_smooth_process
(
void
)
{
if
(
s_control_source
!=
CTRL_STEERING
)
{
return
;
}
...
...
@@ -398,23 +840,25 @@ static void car0107_steering_angle_smooth_process(void) {
car0107_calculate_L_R
(
s_current_angle
);
}
void
car0107_steering_smooth_all
(
void
)
{
void
car0107_steering_smooth_all
(
void
)
{
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
car0107_
phone_smooth_process
();
car0107_
esc_tick
();
car0107_speed_smooth_process
();
car0107_steering_angle_smooth_process
();
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
}
void
car0107_control_steering_change_process
(
int
*
buf
)
{
void
car0107_control_steering_change_process
(
int
*
buf
)
{
int
mode
=
buf
[
1
];
int
val
=
buf
[
2
];
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
s_control_source
=
CTRL_STEERING
;
car0107_reset_phone_state
();
s_steering_timeout
=
200
;
s_speed_timeout
=
200
;
car0107_notify_control_locked
();
if
(
mode
==
1
)
{
val
=
car0107_clamp_input
(
val
);
...
...
drivers/devicecontrol/car/car0107_control.h
View file @
7f0fd331
...
...
@@ -6,4 +6,9 @@ void car0107_middle_pwm(void);
void
car0107_control_steering_change_process
(
int
*
buf
);
void
car0107_steering_smooth_all
(
void
);
void
car0107_notify_control_activity
(
void
);
long
long
car0107_last_control_ms
(
void
);
int
car0107_esc_is_active
(
void
);
void
car0107_steering_set_angle_sync
(
int
angle
);
#endif
drivers/devicecontrol/car/car0107_idle.c
0 → 100644
View file @
7f0fd331
#include "car0107_idle.h"
#include "car0107_control.h"
#include "devcontrol_common.h"
#include "modules_common.h"
#include "gpio_common.h"
#define CAR0107_IDLE_CHECK_MS 10000
#define CAR0107_IDLE_TIMEOUT_MS (5 * 60 * 1000)
#define CAR0107_IDLE_STEER_HOLD_MS 450
#define CAR0107_IDLE_STEER_LEFT 115
#define CAR0107_IDLE_STEER_RIGHT 65
typedef
struct
{
pthread_mutex_t
mutex
;
ThreadPool_t
*
pool
;
bool
shutdown
;
bool
pool_ready
;
int
next_left
;
}
car0107_idle_ctx_t
;
static
car0107_idle_ctx_t
s_idle_ctx
=
{
.
mutex
=
PTHREAD_MUTEX_INITIALIZER
,
.
pool
=
NULL
,
.
shutdown
=
false
,
.
pool_ready
=
false
,
.
next_left
=
1
,
};
static
void
car0107_idle_lock
(
void
)
{
pthread_mutex_lock
(
&
s_idle_ctx
.
mutex
);
}
static
void
car0107_idle_unlock
(
void
)
{
pthread_mutex_unlock
(
&
s_idle_ctx
.
mutex
);
}
static
bool
car0107_idle_shutdown_get
(
void
)
{
bool
shutdown
;
car0107_idle_lock
();
shutdown
=
s_idle_ctx
.
shutdown
;
car0107_idle_unlock
();
return
shutdown
;
}
static
int
car0107_idle_pool_init
(
void
)
{
car0107_idle_lock
();
if
(
s_idle_ctx
.
pool_ready
)
{
car0107_idle_unlock
();
return
0
;
}
s_idle_ctx
.
pool
=
thread_pool_init
(
1
,
1
);
if
(
!
s_idle_ctx
.
pool
)
{
car0107_idle_unlock
();
my_zlog_error
(
"car0107 idle thread pool init failed"
);
return
-
1
;
}
s_idle_ctx
.
pool_ready
=
true
;
car0107_idle_unlock
();
my_zlog_info
(
"car0107 idle monitor thread pool opened"
);
return
0
;
}
static
void
car0107_idle_nudge_steering
(
int
turn_left
)
{
int
angle
=
turn_left
?
CAR0107_IDLE_STEER_LEFT
:
CAR0107_IDLE_STEER_RIGHT
;
/* 刷新电调中位,避免长时间无 PWM 后接收机休眠 */
pwmWrite
(
PWM_PIN_SPEED
,
75
);
car0107_steering_set_angle_sync
(
angle
);
delay_ms
(
CAR0107_IDLE_STEER_HOLD_MS
);
car0107_steering_set_angle_sync
(
90
);
pwmWrite
(
PWM_PIN_SPEED
,
75
);
}
static
void
car0107_idle_monitor_task
(
void
*
arg
)
{
(
void
)
arg
;
my_zlog_info
(
"car0107 idle monitor task start"
);
while
(
!
car0107_idle_shutdown_get
())
{
long
long
now
;
long
long
last
;
int
turn_left
;
delay_ms
(
CAR0107_IDLE_CHECK_MS
);
if
(
car0107_idle_shutdown_get
()
||
g_device_type
!=
DEVICE_CAR0107
)
{
break
;
}
now
=
get_current_time_millis
();
last
=
car0107_last_control_ms
();
if
(
last
<=
0
||
(
now
-
last
)
<
CAR0107_IDLE_TIMEOUT_MS
)
{
continue
;
}
if
(
car0107_esc_is_active
())
{
continue
;
}
car0107_idle_lock
();
turn_left
=
s_idle_ctx
.
next_left
;
s_idle_ctx
.
next_left
=
!
s_idle_ctx
.
next_left
;
car0107_idle_unlock
();
my_zlog_info
(
"car0107 idle %dmin: nudge steer %s"
,
CAR0107_IDLE_TIMEOUT_MS
/
60000
,
turn_left
?
"left"
:
"right"
);
car0107_idle_nudge_steering
(
turn_left
);
car0107_notify_control_activity
();
}
my_zlog_info
(
"car0107 idle monitor task exit"
);
}
void
car0107_idle_startup
(
int
device_id
)
{
if
(
device_id
!=
DEVICE_CAR0107
)
{
return
;
}
if
(
car0107_idle_pool_init
()
!=
0
)
{
return
;
}
car0107_idle_lock
();
if
(
thread_pool_add_task
(
s_idle_ctx
.
pool
,
car0107_idle_monitor_task
,
NULL
)
!=
0
)
{
my_zlog_error
(
"car0107 idle monitor task submit failed"
);
}
car0107_idle_unlock
();
car0107_notify_control_activity
();
my_zlog_info
(
"car0107 idle monitor enabled: steer nudge every %dmin when idle"
,
CAR0107_IDLE_TIMEOUT_MS
/
60000
);
}
void
car0107_idle_thread_close
(
void
)
{
car0107_idle_lock
();
s_idle_ctx
.
shutdown
=
true
;
car0107_idle_unlock
();
if
(
s_idle_ctx
.
pool
)
{
thread_pool_destroy
(
s_idle_ctx
.
pool
);
s_idle_ctx
.
pool
=
NULL
;
s_idle_ctx
.
pool_ready
=
false
;
}
my_zlog_info
(
"car0107 idle monitor thread pool closed"
);
}
drivers/devicecontrol/car/car0107_idle.h
0 → 100644
View file @
7f0fd331
#ifndef CAR0107_IDLE_H__
#define CAR0107_IDLE_H__
void
car0107_idle_startup
(
int
device_id
);
void
car0107_idle_thread_close
(
void
);
#endif
drivers/devicecontrol/common/devcontrol_common.c
View file @
7f0fd331
...
...
@@ -136,7 +136,7 @@ static const device_abnormal_close_t s_devcontrol_config[]= {
{
.
device_id
=
DEVICE_CAR0107
,
.
device_abnormal_stop
=
car0107_middle_pwm
,
.
device_close
=
NULL
,
.
device_close
=
car0107_idle_thread_close
,
.
gpio_pin_pulled
=
pin_all_default
,
.
gpio_pwm_pulled
=
pwm_all_default
},
...
...
drivers/devicecontrol/common/devcontrol_common.h
View file @
7f0fd331
...
...
@@ -10,6 +10,7 @@
#include "car0105_control.h"
#include "car0106_control.h"
#include "car0107_control.h"
#include "car0107_idle.h"
#include "ptz_common.h"
#include "tank0202_control.h"
#include "tank0203_control.h"
...
...
drivers/gpio/device_init.c
View file @
7f0fd331
...
...
@@ -2,6 +2,7 @@
#include "devcontrol_common.h"
#include "common.h"
#include "gpio_init.h"
#include "car0107_idle.h"
int
g_device_delay_back_count
=
0
;
//设备计时,比如坦克打击倒退逻辑
...
...
@@ -267,6 +268,9 @@ void device_init(int device_id) {
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
);
...
...
drivers/sensors/audioplay.c
View file @
7f0fd331
...
...
@@ -374,9 +374,9 @@ int audio_speaker_init() {
int
audio_init
(){
delay_s
(
10
);
delay_s
(
5
);
audio_wheat_init
();
delay_s
(
3
);
delay_s
(
1
);
audio_speaker_init
();
return
0
;
...
...
@@ -410,7 +410,7 @@ static const char *s_yundea_block =
*/
int
audio_config_init
()
{
delay_s
(
20
);
//延迟30s
delay_s
(
12
);
FILE
*
fp
=
fopen
(
CONFIG_FILE
,
"r"
);
if
(
!
fp
)
{
...
...
include/modules_common.h
View file @
7f0fd331
...
...
@@ -11,6 +11,8 @@
#include "go_deploy.h"
#include "tailscale_deploy.h"
#include "runtime_deps.h"
#include "wifi_config_agent.h"
#include "stream_debug_agent.h"
#include "webrtcpush_run.h"
#endif
\ No newline at end of file
modules/CMakeLists.txt
View file @
7f0fd331
...
...
@@ -21,6 +21,8 @@ file(GLOB_RECURSE MODULES_SOURCES
${
CMAKE_CURRENT_SOURCE_DIR
}
/tailscale_deploy/*.c
${
CMAKE_CURRENT_SOURCE_DIR
}
/webrtcpush/*.c
${
CMAKE_CURRENT_SOURCE_DIR
}
/runtime_deps/*.c
${
CMAKE_CURRENT_SOURCE_DIR
}
/wifi_config/*.c
${
CMAKE_CURRENT_SOURCE_DIR
}
/stream_debug/*.c
)
set
(
MODULES_SOURCES
...
...
@@ -40,6 +42,8 @@ set(MODULES_INCLUDE_DIRS
${
CMAKE_CURRENT_SOURCE_DIR
}
/tailscale_deploy
${
CMAKE_CURRENT_SOURCE_DIR
}
/webrtcpush
${
CMAKE_CURRENT_SOURCE_DIR
}
/runtime_deps
${
CMAKE_CURRENT_SOURCE_DIR
}
/wifi_config
${
CMAKE_CURRENT_SOURCE_DIR
}
/stream_debug
${
WEBRTCPUSH_GST_INCLUDE_DIRS
}
${
WEBRTCPUSH_JSON_INCLUDE_DIRS
}
${
WEBRTCPUSH_SOUP_INCLUDE_DIRS
}
...
...
modules/browser/browser_open.c
View file @
7f0fd331
...
...
@@ -4,8 +4,12 @@
#include "device_identity.h"
#include "http_config_mqtt.h"
#include "pthrpoll.h"
#include "delay.h"
#include <dirent.h>
static
char
s_webcam
[
2048
];
//存放启动火狐网站命令
static
char
s_active_stream_url
[
768
];
/* 最近一次启动 chromium 的完整推流网址 */
static
int
s_open_cam_enter_count
=
0
;
// 记录 opencamsh 进入次数
static
int
s_open_cam_fail_streak
=
0
;
// 连续启动失败次数
static
int
s_refresh_cam_enter_count
=
0
;
// 记录 refresh_cam 进入次数
...
...
@@ -17,37 +21,54 @@ static pthread_mutex_t s_refresh_stat_mutex = PTHREAD_MUTEX_INITIALIZER;
static
pthread_mutex_t
s_refresh_run_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
s_refresh_cool_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
s_refresh_pool_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
pthread_mutex_t
s_browser_ops_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
time_t
s_refresh_next_allowed
=
0
;
/* time(NULL) 之前不允许再提交刷新 */
static
time_t
s_browser_retry_after
=
0
;
static
volatile
int
s_browser_external_switch
=
0
;
static
ThreadPool_t
*
s_refresh_pool
=
NULL
;
#define BROWSER_AUTO_RESTART_FAIL_SEC 5
static
int
browser_launch_chromium
(
const
char
*
launch_url
);
static
int
browser_chromium_found_cb
(
int
pid
,
const
char
*
cmdline
,
void
*
ctx
);
int
opencamsh
()
{
char
urls
[
512
];
int
cmd_ret
=
0
;
int
rc
;
s_open_cam_enter_count
++
;
my_zlog_info
(
"opencamsh enter count:%d"
,
s_open_cam_enter_count
);
snprintf
(
urls
,
sizeof
(
urls
),
"%s%s"
,
g_mqtt_cam_config_t
->
videourl
,
mqtt_topic_pure_number
());
//setenv("DISPLAY", ":0", 1);//设置环境变量https://jywy.yd-ss.com?dev=controcar0004 --new-window sudo
snprintf
(
s_webcam
,
sizeof
(
s_webcam
),
"su - orangepi -c
\"
chromium-browser --use-fake-ui-for-media-stream %s
\"
"
,
urls
);
cmd_ret
=
system
(
s_webcam
);
if
(
cmd_ret
!=
0
)
{
pthread_mutex_lock
(
&
s_browser_ops_mutex
);
if
(
s_browser_external_switch
)
{
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
my_zlog_debug
(
"opencamsh skipped: external stream switch in progress"
);
return
-
1
;
}
rc
=
browser_launch_chromium
(
urls
);
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
if
(
rc
!=
0
)
{
s_open_cam_fail_streak
++
;
s_browser_retry_after
=
time
(
NULL
)
+
BROWSER_AUTO_RESTART_FAIL_SEC
;
if
(
s_open_cam_fail_streak
>=
10
&&
(
s_open_cam_fail_streak
%
10
)
==
0
)
{
my_zlog_error
(
"chromium start fail,
ret:%d,
continuous fail:%d, enter count:%d"
,
cmd_ret
,
s_open_cam_fail_streak
,
s_open_cam_enter_count
);
my_zlog_error
(
"chromium start fail, continuous fail:%d, enter count:%d"
,
s_open_cam_fail_streak
,
s_open_cam_enter_count
);
}
else
{
my_zlog_warn
(
"chromium start fail,
ret:%d,
continuous fail:%d, enter count:%d"
,
cmd_ret
,
s_open_cam_fail_streak
,
s_open_cam_enter_count
);
my_zlog_warn
(
"chromium start fail, continuous fail:%d, enter count:%d"
,
s_open_cam_fail_streak
,
s_open_cam_enter_count
);
}
}
else
{
if
(
s_open_cam_fail_streak
>
0
)
{
my_zlog_info
(
"chromium start recovered, previous continuous fail:%d"
,
s_open_cam_fail_streak
);
}
s_open_cam_fail_streak
=
0
;
return
-
1
;
}
if
(
s_open_cam_fail_streak
>
0
)
{
my_zlog_info
(
"chromium start recovered, previous continuous fail:%d"
,
s_open_cam_fail_streak
)
;
}
my_zlog_debug
(
"%s"
,
s_webcam
);
s_open_cam_fail_streak
=
0
;
s_browser_retry_after
=
0
;
my_zlog_debug
(
"open cam"
);
return
0
;
}
...
...
@@ -197,13 +218,437 @@ void refresh_cam(void)
REFRESH_CAM_COOLDOWN_SEC
);
}
// 检查Chromium进程是否已运行
bool
is_browser_running
()
{
FILE
*
fp
=
popen
(
"pgrep chromium"
,
"r"
);
if
(
fp
==
NULL
)
return
false
;
// 检查Chromium进程是否已运行(直接扫 /proc,避免 UDP 线程 popen 死锁)
static
int
browser_read_pid_cmdline
(
int
pid
,
char
*
cmdline
,
size_t
cmdline_size
)
{
char
path
[
64
];
FILE
*
cf
;
size_t
n
;
size_t
i
;
snprintf
(
path
,
sizeof
(
path
),
"/proc/%d/cmdline"
,
pid
);
cf
=
fopen
(
path
,
"r"
);
if
(
!
cf
)
{
return
-
1
;
}
n
=
fread
(
cmdline
,
1
,
cmdline_size
-
1
,
cf
);
fclose
(
cf
);
if
(
n
==
0
)
{
return
-
1
;
}
cmdline
[
n
]
=
'\0'
;
for
(
i
=
0
;
i
<
n
;
i
++
)
{
if
(
cmdline
[
i
]
==
'\0'
)
{
cmdline
[
i
]
=
' '
;
}
}
return
0
;
}
char
buf
[
16
];
bool
running
=
(
fgets
(
buf
,
sizeof
(
buf
),
fp
)
!=
NULL
);
pclose
(
fp
);
static
int
browser_is_chromium_cmdline
(
const
char
*
cmdline
)
{
if
(
!
cmdline
||
!
cmdline
[
0
])
{
return
0
;
}
if
(
strstr
(
cmdline
,
"chromium-browser"
)
!=
NULL
)
{
return
1
;
}
if
(
strstr
(
cmdline
,
"/chromium-browser"
)
!=
NULL
)
{
return
1
;
}
return
0
;
}
/* 仅认推流主进程,排除 zygote/gpu 等子进程 */
static
int
browser_is_stream_chromium_cmdline
(
const
char
*
cmdline
)
{
if
(
!
browser_is_chromium_cmdline
(
cmdline
))
{
return
0
;
}
if
(
strstr
(
cmdline
,
"--type="
)
!=
NULL
)
{
return
0
;
}
if
(
strstr
(
cmdline
,
"--use-fake-ui-for-media-stream"
)
!=
NULL
)
{
return
1
;
}
if
(
strstr
(
cmdline
,
"https://"
)
!=
NULL
||
strstr
(
cmdline
,
"http://"
)
!=
NULL
)
{
return
1
;
}
return
0
;
}
void
browser_begin_external_switch
(
void
)
{
pthread_mutex_lock
(
&
s_browser_ops_mutex
);
s_browser_external_switch
=
1
;
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
}
void
browser_end_external_switch
(
void
)
{
pthread_mutex_lock
(
&
s_browser_ops_mutex
);
s_browser_external_switch
=
0
;
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
}
int
browser_auto_restart_allowed
(
void
)
{
time_t
now
=
time
(
NULL
);
pthread_mutex_lock
(
&
s_browser_ops_mutex
);
if
(
s_browser_external_switch
)
{
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
return
0
;
}
if
(
s_browser_retry_after
>
0
&&
now
<
s_browser_retry_after
)
{
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
return
0
;
}
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
return
1
;
}
typedef
int
(
*
browser_chromium_cb
)(
int
pid
,
const
char
*
cmdline
,
void
*
ctx
);
static
int
browser_chromium_url_cb
(
int
pid
,
const
char
*
cmdline
,
void
*
ctx
);
static
int
browser_foreach_chromium
(
browser_chromium_cb
cb
,
void
*
ctx
)
{
DIR
*
dir
;
struct
dirent
*
ent
;
if
(
!
cb
)
{
return
-
1
;
}
dir
=
opendir
(
"/proc"
);
if
(
!
dir
)
{
return
-
1
;
}
while
((
ent
=
readdir
(
dir
))
!=
NULL
)
{
char
cmdline
[
2048
];
int
pid
;
int
rc
;
if
(
ent
->
d_name
[
0
]
<
'1'
||
ent
->
d_name
[
0
]
>
'9'
)
{
continue
;
}
pid
=
atoi
(
ent
->
d_name
);
if
(
pid
<=
0
)
{
continue
;
}
if
(
browser_read_pid_cmdline
(
pid
,
cmdline
,
sizeof
(
cmdline
))
!=
0
)
{
continue
;
}
if
(
!
browser_is_chromium_cmdline
(
cmdline
))
{
continue
;
}
if
(
cb
==
browser_chromium_found_cb
&&
!
browser_is_stream_chromium_cmdline
(
cmdline
))
{
continue
;
}
rc
=
cb
(
pid
,
cmdline
,
ctx
);
if
(
rc
!=
0
)
{
closedir
(
dir
);
return
rc
;
}
}
closedir
(
dir
);
return
0
;
}
static
int
browser_chromium_found_cb
(
int
pid
,
const
char
*
cmdline
,
void
*
ctx
)
{
(
void
)
pid
;
(
void
)
cmdline
;
*
(
bool
*
)
ctx
=
true
;
return
1
;
}
bool
is_browser_running
()
{
bool
running
=
false
;
(
void
)
browser_foreach_chromium
(
browser_chromium_found_cb
,
&
running
);
return
running
;
}
static
void
browser_prepare_display_env
(
void
)
{
setenv
(
"DISPLAY"
,
":0"
,
1
);
setenv
(
"XAUTHORITY"
,
"/home/orangepi/.Xauthority"
,
1
);
}
static
void
browser_run_shell_cmd
(
const
char
*
cmd
)
{
int
status
;
if
(
!
cmd
||
!
cmd
[
0
])
{
return
;
}
status
=
system
(
cmd
);
if
(
status
!=
0
)
{
my_zlog_debug
(
"browser shell cmd status=%d: %s"
,
status
,
cmd
);
}
}
static
int
browser_kill_chromium
(
void
)
{
int
i
;
for
(
i
=
0
;
i
<
12
;
i
++
)
{
if
(
!
is_browser_running
())
{
return
0
;
}
if
(
i
<
3
)
{
browser_run_shell_cmd
(
"pkill -TERM -f 'chromium-browser --use-fake-ui' >/dev/null 2>&1"
);
browser_run_shell_cmd
(
"pkill -TERM -f 'chromium-browser.*http' >/dev/null 2>&1"
);
browser_run_shell_cmd
(
"su - orangepi -c
\"
pkill -TERM -f 'chromium-browser --use-fake-ui'; pkill -TERM chromium
\"
>/dev/null 2>&1"
);
}
else
{
browser_run_shell_cmd
(
"pkill -9 -f 'chromium-browser --use-fake-ui' >/dev/null 2>&1"
);
browser_run_shell_cmd
(
"pkill -9 -f 'chromium-browser.*http' >/dev/null 2>&1"
);
browser_run_shell_cmd
(
"su - orangepi -c
\"
pkill -9 -f 'chromium-browser --use-fake-ui'; pkill -9 chromium
\"
>/dev/null 2>&1"
);
}
delay_ms
(
250
);
}
return
is_browser_running
()
?
-
1
:
0
;
}
static
int
browser_wait_chromium_running
(
int
timeout_ms
)
{
int
elapsed
=
0
;
while
(
elapsed
<
timeout_ms
)
{
if
(
is_browser_running
())
{
return
0
;
}
delay_ms
(
200
);
elapsed
+=
200
;
}
return
-
1
;
}
static
int
browser_read_url_from_proc
(
char
*
url
,
size_t
url_size
)
{
struct
{
char
*
url
;
size_t
url_size
;
int
found
;
}
ctx
;
if
(
!
url
||
url_size
==
0
)
{
return
-
1
;
}
ctx
.
url
=
url
;
ctx
.
url_size
=
url_size
;
ctx
.
found
=
0
;
browser_foreach_chromium
(
browser_chromium_url_cb
,
&
ctx
);
return
ctx
.
found
?
0
:
-
1
;
}
static
int
browser_chromium_url_cb
(
int
pid
,
const
char
*
cmdline
,
void
*
ctx
)
{
struct
{
char
*
url
;
size_t
url_size
;
int
found
;
}
*
search
=
ctx
;
const
char
*
http_pos
;
size_t
len
;
(
void
)
pid
;
http_pos
=
strstr
(
cmdline
,
"https://"
);
if
(
!
http_pos
)
{
http_pos
=
strstr
(
cmdline
,
"http://"
);
}
if
(
!
http_pos
)
{
return
0
;
}
len
=
0
;
while
(
http_pos
[
len
]
!=
'\0'
&&
http_pos
[
len
]
!=
' '
&&
len
+
1
<
search
->
url_size
)
{
len
++
;
}
memcpy
(
search
->
url
,
http_pos
,
len
);
search
->
url
[
len
]
=
'\0'
;
search
->
found
=
1
;
return
1
;
}
int
browser_get_stream_url_info
(
char
*
url
,
size_t
url_size
,
bool
*
running
)
{
bool
chromium_running
=
is_browser_running
();
if
(
running
)
{
*
running
=
chromium_running
;
}
if
(
!
url
||
url_size
==
0
)
{
return
chromium_running
?
0
:
-
1
;
}
url
[
0
]
=
'\0'
;
if
(
!
chromium_running
)
{
return
0
;
}
if
(
s_active_stream_url
[
0
]
!=
'\0'
)
{
snprintf
(
url
,
url_size
,
"%s"
,
s_active_stream_url
);
return
0
;
}
if
(
browser_read_url_from_proc
(
url
,
url_size
)
==
0
)
{
return
0
;
}
if
(
g_mqtt_cam_config_t
&&
g_mqtt_cam_config_t
->
videourl
[
0
]
!=
'\0'
)
{
const
char
*
device_no
=
mqtt_topic_pure_number
();
snprintf
(
url
,
url_size
,
"%s%s"
,
g_mqtt_cam_config_t
->
videourl
,
device_no
?
device_no
:
""
);
}
return
0
;
}
static
void
shell_escape_single_quoted
(
const
char
*
input
,
char
*
out
,
size_t
out_size
)
{
size_t
i
=
0
;
size_t
j
=
0
;
if
(
!
out
||
out_size
==
0
)
{
return
;
}
out
[
0
]
=
'\0'
;
if
(
!
input
)
{
return
;
}
for
(
i
=
0
;
input
[
i
]
!=
'\0'
&&
j
+
4
<
out_size
;
i
++
)
{
if
(
input
[
i
]
==
'\''
)
{
out
[
j
++
]
=
'\''
;
out
[
j
++
]
=
'\\'
;
out
[
j
++
]
=
'\''
;
out
[
j
++
]
=
'\''
;
}
else
{
out
[
j
++
]
=
input
[
i
];
}
}
out
[
j
]
=
'\0'
;
}
static
int
browser_validate_stream_url
(
const
char
*
url
)
{
size_t
len
;
if
(
!
url
||
!
url
[
0
])
{
return
-
1
;
}
len
=
strlen
(
url
);
if
(
len
>=
sizeof
(
g_mqtt_cam_config_t
->
videourl
))
{
return
-
1
;
}
if
(
strncmp
(
url
,
"http://"
,
7
)
!=
0
&&
strncmp
(
url
,
"https://"
,
8
)
!=
0
)
{
return
-
1
;
}
return
0
;
}
static
void
browser_build_launch_url
(
const
char
*
url
,
char
*
launch_url
,
size_t
launch_size
)
{
size_t
len
;
const
char
*
device_no
;
if
(
!
url
||
!
launch_url
||
launch_size
==
0
)
{
return
;
}
len
=
strlen
(
url
);
device_no
=
mqtt_topic_pure_number
();
if
(
len
>
0
&&
url
[
len
-
1
]
==
'='
)
{
snprintf
(
launch_url
,
launch_size
,
"%s%s"
,
url
,
device_no
?
device_no
:
""
);
return
;
}
if
(
len
>=
5
&&
strcmp
(
url
+
len
-
5
,
"?dev="
)
==
0
)
{
snprintf
(
launch_url
,
launch_size
,
"%s%s"
,
url
,
device_no
?
device_no
:
""
);
return
;
}
snprintf
(
launch_url
,
launch_size
,
"%s"
,
url
);
}
static
int
browser_launch_chromium
(
const
char
*
launch_url
)
{
char
escaped_url
[
1024
];
char
cmd
[
2048
];
int
ret
;
if
(
!
launch_url
||
!
launch_url
[
0
])
{
return
-
1
;
}
browser_prepare_display_env
();
shell_escape_single_quoted
(
launch_url
,
escaped_url
,
sizeof
(
escaped_url
));
snprintf
(
cmd
,
sizeof
(
cmd
),
"su - orangepi -c 'export DISPLAY=:0 XAUTHORITY=/home/orangepi/.Xauthority; "
"nohup chromium-browser --use-fake-ui-for-media-stream %s "
">/tmp/chromium-stream.log 2>&1 &'"
,
escaped_url
);
my_zlog_info
(
"browser_launch_chromium: %s"
,
launch_url
);
ret
=
system
(
cmd
);
if
(
ret
!=
0
)
{
my_zlog_error
(
"browser_launch_chromium failed ret=%d"
,
ret
);
return
-
1
;
}
if
(
browser_wait_chromium_running
(
5000
)
!=
0
)
{
my_zlog_error
(
"browser_launch_chromium: chromium not running after launch"
);
return
-
1
;
}
strncpy
(
s_active_stream_url
,
launch_url
,
sizeof
(
s_active_stream_url
)
-
1
);
s_active_stream_url
[
sizeof
(
s_active_stream_url
)
-
1
]
=
'\0'
;
return
0
;
}
int
browser_switch_stream_url
(
const
char
*
url
,
char
*
effective_url
,
size_t
effective_size
)
{
char
launch_url
[
768
];
int
ret
;
if
(
browser_validate_stream_url
(
url
)
!=
0
)
{
my_zlog_error
(
"browser_switch_stream_url: invalid url"
);
return
-
1
;
}
browser_build_launch_url
(
url
,
launch_url
,
sizeof
(
launch_url
));
strncpy
(
g_mqtt_cam_config_t
->
videourl
,
url
,
sizeof
(
g_mqtt_cam_config_t
->
videourl
)
-
1
);
g_mqtt_cam_config_t
->
videourl
[
sizeof
(
g_mqtt_cam_config_t
->
videourl
)
-
1
]
=
'\0'
;
my_zlog_info
(
"browser_switch_stream_url: kill old chromium, launch %s"
,
launch_url
);
browser_begin_external_switch
();
if
(
browser_kill_chromium
()
!=
0
)
{
my_zlog_warn
(
"browser_switch_stream_url: old chromium may still be exiting, continue launch"
);
}
delay_ms
(
1000
);
pthread_mutex_lock
(
&
s_browser_ops_mutex
);
ret
=
browser_launch_chromium
(
launch_url
);
pthread_mutex_unlock
(
&
s_browser_ops_mutex
);
browser_end_external_switch
();
if
(
ret
!=
0
)
{
s_browser_retry_after
=
time
(
NULL
)
+
BROWSER_AUTO_RESTART_FAIL_SEC
;
return
-
1
;
}
if
(
effective_url
&&
effective_size
>
0
)
{
snprintf
(
effective_url
,
effective_size
,
"%s"
,
launch_url
);
}
my_zlog_info
(
"browser_switch_stream_url: ok %s"
,
launch_url
);
return
0
;
}
modules/browser/browser_open.h
View file @
7f0fd331
...
...
@@ -7,8 +7,18 @@
int
opencamsh
();
void
refresh_cam
();
//刷新摄像头函数
void
refresh_cam
(
void
);
//刷新摄像头函数
bool
is_browser_running
();
void
browser_begin_external_switch
(
void
);
void
browser_end_external_switch
(
void
);
int
browser_auto_restart_allowed
(
void
);
/* 推流调试:仅浏览器 webrtc 模式可切换;effective_url 输出实际打开的完整网址 */
int
browser_switch_stream_url
(
const
char
*
url
,
char
*
effective_url
,
size_t
effective_size
);
/* 查询 chromium 运行状态与当前推流网址(url 缓冲区未运行时置空串) */
int
browser_get_stream_url_info
(
char
*
url
,
size_t
url_size
,
bool
*
running
);
#endif
\ No newline at end of file
modules/stream_debug/stream_debug_agent.c
0 → 100644
View file @
7f0fd331
#include "stream_debug_agent.h"
#include "stream_debug_config.h"
#include "browser_open.h"
#include "webrtcpush_run.h"
#include "wifi_config_agent.h"
#include "http_config_mqtt.h"
#include "common.h"
static
const
char
*
stream_debug_current_mode
(
void
)
{
if
(
webrtcpush_should_use_mpp
())
{
return
"c_lang"
;
}
return
"webrtc"
;
}
static
int
stream_debug_send_json
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
const
char
*
json
)
{
if
(
!
json
||
!
addr
||
sock
<
0
)
{
return
-
1
;
}
return
sendto
(
sock
,
json
,
strlen
(
json
),
0
,
(
const
struct
sockaddr
*
)
addr
,
sizeof
(
*
addr
))
>=
0
?
0
:
-
1
;
}
void
stream_debug_handle_mode_query
(
int
sock
,
const
struct
sockaddr_in
*
addr
)
{
cJSON
*
root
=
cJSON_CreateObject
();
const
char
*
mode
=
stream_debug_current_mode
();
char
*
payload
;
if
(
!
root
)
{
return
;
}
cJSON_AddStringToObject
(
root
,
"cmd"
,
"stream_mode_response"
);
cJSON_AddStringToObject
(
root
,
"mode"
,
mode
);
payload
=
cJSON_PrintUnformatted
(
root
);
if
(
payload
)
{
stream_debug_send_json
(
sock
,
addr
,
payload
);
my_zlog_info
(
"推流调试: stream_mode_query -> mode=%s"
,
mode
);
free
(
payload
);
}
cJSON_Delete
(
root
);
}
static
void
stream_debug_send_url_result
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
int
success
,
const
char
*
message
,
const
char
*
url
)
{
cJSON
*
root
=
cJSON_CreateObject
();
char
*
payload
;
if
(
!
root
)
{
return
;
}
cJSON_AddStringToObject
(
root
,
"cmd"
,
"stream_url_result"
);
cJSON_AddBoolToObject
(
root
,
"success"
,
success
?
1
:
0
);
if
(
message
&&
message
[
0
])
{
cJSON_AddStringToObject
(
root
,
"message"
,
message
);
}
if
(
url
&&
url
[
0
])
{
cJSON_AddStringToObject
(
root
,
"url"
,
url
);
}
payload
=
cJSON_PrintUnformatted
(
root
);
if
(
payload
)
{
stream_debug_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
root
);
}
void
stream_debug_handle_url_update
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
)
{
cJSON
*
url_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"url"
);
const
char
*
url
;
char
effective_url
[
STREAM_DEBUG_URL_MAX
];
int
rc
;
if
(
strcmp
(
stream_debug_current_mode
(),
"webrtc"
)
!=
0
)
{
stream_debug_send_url_result
(
sock
,
addr
,
0
,
"当前为 C 语言推流,不支持切换网址,请修改设备配置"
,
NULL
);
my_zlog_warn
(
"推流调试: stream_url_update 拒绝(c_lang 模式)"
);
return
;
}
if
(
!
cJSON_IsString
(
url_item
)
||
!
url_item
->
valuestring
||
!
url_item
->
valuestring
[
0
])
{
stream_debug_send_url_result
(
sock
,
addr
,
0
,
"缺少有效的 url 字段"
,
NULL
);
return
;
}
url
=
url_item
->
valuestring
;
my_zlog_info
(
"推流调试: 收到 stream_url_update url=%s"
,
url
);
rc
=
browser_switch_stream_url
(
url
,
effective_url
,
sizeof
(
effective_url
));
if
(
rc
==
0
)
{
stream_debug_send_url_result
(
sock
,
addr
,
1
,
"推流网址已切换"
,
effective_url
);
my_zlog_info
(
"推流调试: 推流网址切换成功 -> %s"
,
effective_url
);
}
else
{
stream_debug_send_url_result
(
sock
,
addr
,
0
,
"推流网址切换失败"
,
NULL
);
my_zlog_warn
(
"推流调试: 推流网址切换失败"
);
}
wifi_config_agent_rebind_socket
();
}
void
stream_debug_handle_url_query
(
int
sock
,
const
struct
sockaddr_in
*
addr
)
{
cJSON
*
root
=
cJSON_CreateObject
();
char
url_buf
[
STREAM_DEBUG_URL_MAX
];
bool
running
=
false
;
char
*
payload
;
my_zlog_info
(
"推流调试: 收到 stream_url_query"
);
if
(
!
root
)
{
return
;
}
cJSON_AddStringToObject
(
root
,
"cmd"
,
"stream_url_response"
);
if
(
strcmp
(
stream_debug_current_mode
(),
"webrtc"
)
!=
0
)
{
cJSON_AddBoolToObject
(
root
,
"success"
,
0
);
cJSON_AddBoolToObject
(
root
,
"running"
,
0
);
cJSON_AddStringToObject
(
root
,
"message"
,
"当前为 C 语言推流,不支持查询推流网址"
);
my_zlog_warn
(
"推流调试: stream_url_query 拒绝(c_lang 模式)"
);
}
else
{
url_buf
[
0
]
=
'\0'
;
(
void
)
browser_get_stream_url_info
(
url_buf
,
sizeof
(
url_buf
),
&
running
);
cJSON_AddBoolToObject
(
root
,
"success"
,
1
);
cJSON_AddBoolToObject
(
root
,
"running"
,
running
?
1
:
0
);
cJSON_AddStringToObject
(
root
,
"url"
,
running
?
url_buf
:
""
);
cJSON_AddStringToObject
(
root
,
"message"
,
running
?
"当前推流网址"
:
"浏览器未启动"
);
my_zlog_info
(
"推流调试: stream_url_query -> running=%d url=%s"
,
running
?
1
:
0
,
running
?
url_buf
:
""
);
}
payload
=
cJSON_PrintUnformatted
(
root
);
if
(
payload
)
{
stream_debug_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
root
);
wifi_config_agent_rebind_socket
();
}
modules/stream_debug/stream_debug_agent.h
0 → 100644
View file @
7f0fd331
#ifndef STREAM_DEBUG_AGENT_H__
#define STREAM_DEBUG_AGENT_H__
#include <netinet/in.h>
#include <cJSON.h>
void
stream_debug_handle_mode_query
(
int
sock
,
const
struct
sockaddr_in
*
addr
);
void
stream_debug_handle_url_update
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
);
void
stream_debug_handle_url_query
(
int
sock
,
const
struct
sockaddr_in
*
addr
);
#endif
modules/stream_debug/stream_debug_config.h
0 → 100644
View file @
7f0fd331
#ifndef STREAM_DEBUG_CONFIG_H__
#define STREAM_DEBUG_CONFIG_H__
#define STREAM_DEBUG_URL_MAX 512
#define STREAM_DEBUG_MESSAGE_MAX 256
#define STREAM_DEBUG_CHROMIUM_STOP_MS 1000
#endif
modules/webrtcpush/audio_source.c
0 → 100644
View file @
7f0fd331
#include "audio_source.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
struct
AudioSource
{
GstElement
*
pipeline
;
GstElement
*
appsink
;
GstElement
*
enc
;
guint8
*
frame_buf
;
/* 缓存最新帧数据,避免 sample 生命周期问题 */
};
AudioSource
*
audio_source_start
(
const
char
*
alsa_device
,
char
**
error_message
)
{
AudioSource
*
src
;
GstElement
*
pipe
,
*
asrc
,
*
conv
,
*
resample
,
*
caps_f
,
*
enc
,
*
sink
;
GstCaps
*
caps
;
GstStateChangeReturn
ret
;
if
(
!
alsa_device
||
!
alsa_device
[
0
])
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"no ALSA device"
);
return
NULL
;
}
src
=
g_new0
(
AudioSource
,
1
);
pipe
=
gst_pipeline_new
(
"audio-pipe"
);
if
(
!
g_strcmp0
(
alsa_device
,
"test"
))
{
asrc
=
gst_element_factory_make
(
"audiotestsrc"
,
"asrc"
);
if
(
asrc
)
g_object_set
(
asrc
,
"wave"
,
0
,
"freq"
,
440
,
NULL
);
}
else
{
asrc
=
gst_element_factory_make
(
"alsasrc"
,
"asrc"
);
if
(
asrc
)
g_object_set
(
asrc
,
"device"
,
alsa_device
,
NULL
);
}
conv
=
gst_element_factory_make
(
"audioconvert"
,
"conv"
);
resample
=
gst_element_factory_make
(
"audioresample"
,
"resample"
);
caps_f
=
gst_element_factory_make
(
"capsfilter"
,
"caps_f"
);
enc
=
gst_element_factory_make
(
"opusenc"
,
"enc"
);
sink
=
gst_element_factory_make
(
"appsink"
,
"sink"
);
if
(
!
pipe
||
!
asrc
||
!
conv
||
!
resample
||
!
caps_f
||
!
enc
||
!
sink
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to create audio GStreamer elements"
);
if
(
pipe
)
gst_object_unref
(
pipe
);
g_free
(
src
);
return
NULL
;
}
/* Opus 要求 48000Hz;单声道足够车机麦 */
caps
=
gst_caps_new_simple
(
"audio/x-raw"
,
"rate"
,
G_TYPE_INT
,
WEBRTCPUSH_OPUS_CLOCKRATE
,
"channels"
,
G_TYPE_INT
,
WEBRTCPUSH_OPUS_CHANNELS
,
"format"
,
G_TYPE_STRING
,
"S16LE"
,
NULL
);
g_object_set
(
caps_f
,
"caps"
,
caps
,
NULL
);
gst_caps_unref
(
caps
);
g_object_set
(
enc
,
"bitrate"
,
WEBRTCPUSH_OPUS_BITRATE
,
"cbr"
,
TRUE
,
"framesize"
,
20
,
/* 20ms 帧 */
"inband-fec"
,
TRUE
,
NULL
);
g_object_set
(
sink
,
"emit-signals"
,
FALSE
,
"drop"
,
FALSE
,
"max-buffers"
,
WEBRTCPUSH_AUDIO_QUEUE_DEPTH
,
NULL
);
gst_bin_add_many
(
GST_BIN
(
pipe
),
asrc
,
conv
,
resample
,
caps_f
,
enc
,
sink
,
NULL
);
if
(
!
gst_element_link_many
(
asrc
,
conv
,
resample
,
caps_f
,
enc
,
sink
,
NULL
))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link audio chain"
);
gst_object_unref
(
pipe
);
g_free
(
src
);
return
NULL
;
}
src
->
pipeline
=
pipe
;
src
->
appsink
=
sink
;
src
->
enc
=
enc
;
ret
=
gst_element_set_state
(
pipe
,
GST_STATE_PLAYING
);
if
(
ret
==
GST_STATE_CHANGE_FAILURE
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"audio pipeline failed to reach PLAYING"
);
gst_element_set_state
(
pipe
,
GST_STATE_NULL
);
gst_object_unref
(
pipe
);
g_free
(
src
);
return
NULL
;
}
my_zlog_info
(
"audio_source: started device=%s opus=%uch %uHz %ukbps"
,
alsa_device
,
WEBRTCPUSH_OPUS_CHANNELS
,
WEBRTCPUSH_OPUS_CLOCKRATE
,
WEBRTCPUSH_OPUS_BITRATE
/
1000
);
return
src
;
}
void
audio_source_stop
(
AudioSource
*
src
)
{
if
(
!
src
)
return
;
if
(
src
->
pipeline
)
{
gst_element_set_state
(
src
->
pipeline
,
GST_STATE_NULL
);
gst_object_unref
(
src
->
pipeline
);
}
g_free
(
src
->
frame_buf
);
g_free
(
src
);
}
gboolean
audio_source_pull
(
AudioSource
*
src
,
const
uint8_t
**
data
,
size_t
*
size
,
guint64
*
pts_ns
,
gint
timeout_ms
)
{
GstAppSink
*
appsink
;
GstSample
*
sample
;
GstBuffer
*
buffer
;
GstMapInfo
map
;
if
(
!
src
||
!
src
->
appsink
||
!
data
||
!
size
||
timeout_ms
<
1
)
return
FALSE
;
appsink
=
GST_APP_SINK
(
src
->
appsink
);
sample
=
gst_app_sink_try_pull_sample
(
appsink
,
(
guint64
)
timeout_ms
*
GST_MSECOND
);
if
(
!
sample
)
return
FALSE
;
buffer
=
gst_sample_get_buffer
(
sample
);
if
(
!
buffer
||
!
gst_buffer_map
(
buffer
,
&
map
,
GST_MAP_READ
))
{
gst_sample_unref
(
sample
);
return
FALSE
;
}
g_free
(
src
->
frame_buf
);
src
->
frame_buf
=
g_memdup2
(
map
.
data
,
map
.
size
);
*
data
=
src
->
frame_buf
;
*
size
=
map
.
size
;
if
(
pts_ns
)
*
pts_ns
=
GST_BUFFER_PTS
(
buffer
);
gst_buffer_unmap
(
buffer
,
&
map
);
gst_sample_unref
(
sample
);
return
TRUE
;
}
modules/webrtcpush/audio_source.h
0 → 100644
View file @
7f0fd331
#ifndef AUDIO_SOURCE_H
#define AUDIO_SOURCE_H
#include <glib.h>
#include <stddef.h>
#include <stdint.h>
typedef
struct
AudioSource
AudioSource
;
/* alsa_device: plughw:X,Y | "test"(audiotestsrc 测试音) */
AudioSource
*
audio_source_start
(
const
char
*
alsa_device
,
char
**
error_message
);
void
audio_source_stop
(
AudioSource
*
src
);
/* 拉一帧 Opus payload;数据在下次 pull 前有效。timeout_ms:appsink 等待毫秒。 */
gboolean
audio_source_pull
(
AudioSource
*
src
,
const
uint8_t
**
data
,
size_t
*
size
,
guint64
*
pts_ns
,
gint
timeout_ms
);
#endif
modules/webrtcpush/mpp_h264_source.c
View file @
7f0fd331
...
...
@@ -5,30 +5,90 @@
#include <gst/app/gstappsink.h>
#include <gst/video/video-event.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define MPP_VIDEO_WIDTH 1280
#define MPP_VIDEO_HEIGHT 720
#define MPP_VIDEO_GOP WEBRTCPUSH_H264_GOP
#define MPP_VIDEO_START_BPS
1400000u
#define MPP_VIDEO_START_BPS
WEBRTCPUSH_INITIAL_BITRATE
#define MPP_VIDEO_MIN_BPS WEBRTCPUSH_MIN_BITRATE
#define MPP_VIDEO_MAX_BPS WEBRTCPUSH_MAX_BITRATE
#define MPP_H264_PROFILE 66
/* Baseline:板端 mpph264enc 最稳 */
#define WRTC_V4L2_CAP_VIDEO_CAPTURE (1u << 0)
#define WRTC_V4L2_CAP_VIDEO_CAPTURE_MPLANE (1u << 12)
typedef
enum
{
CAPTURE_NONE
=
0
,
CAPTURE_RAW_NV12_720P
,
CAPTURE_MJPEG_720P24
,
CAPTURE_MJPEG_720P30
,
}
CaptureMode
;
struct
MppH264Source
{
GstElement
*
pipeline
;
GstElement
*
enc
;
GstElement
*
parse
;
GstElement
*
eq
;
GstElement
*
appsink
;
gboolean
use_mpp
;
guint8
*
frame_buf
;
size_t
frame_size
;
size_t
frame_capacity
;
guint
keyframe_req
;
volatile
gboolean
pending_idr
;
gulong
enc_sink_probe_id
;
guint
bus_watch_id
;
gchar
*
sprop_parameter_sets
;
};
static
gboolean
pull_sample
(
MppH264Source
*
src
,
gboolean
require_idr
,
gint
timeout_ms
,
const
uint8_t
**
data
,
size_t
*
size
,
gboolean
*
is_idr
,
guint64
*
pts_ns
);
static
gboolean
bus_watch_cb
(
GstBus
*
bus
,
GstMessage
*
msg
,
gpointer
user_data
)
{
MppH264Source
*
src
=
user_data
;
(
void
)
bus
;
if
(
!
src
)
return
TRUE
;
switch
(
GST_MESSAGE_TYPE
(
msg
))
{
case
GST_MESSAGE_ERROR
:
{
GError
*
err
=
NULL
;
gchar
*
dbg
=
NULL
;
gst_message_parse_error
(
msg
,
&
err
,
&
dbg
);
my_zlog_error
(
"mpp_h264_source: pipeline error: %s (%s)"
,
err
?
err
->
message
:
"unknown"
,
dbg
?
dbg
:
""
);
g_clear_error
(
&
err
);
g_free
(
dbg
);
break
;
}
case
GST_MESSAGE_WARNING
:
{
GError
*
err
=
NULL
;
gchar
*
dbg
=
NULL
;
gst_message_parse_warning
(
msg
,
&
err
,
&
dbg
);
my_zlog_warn
(
"mpp_h264_source: pipeline warning: %s (%s)"
,
err
?
err
->
message
:
"unknown"
,
dbg
?
dbg
:
""
);
g_clear_error
(
&
err
);
g_free
(
dbg
);
break
;
}
case
GST_MESSAGE_EOS
:
my_zlog_warn
(
"mpp_h264_source: pipeline EOS"
);
break
;
default:
break
;
}
return
TRUE
;
}
static
gchar
*
pick_v4l2_capture_device
(
void
)
{
for
(
int
i
=
0
;
i
<
32
;
i
++
)
{
...
...
@@ -61,23 +121,164 @@ static gchar *pick_v4l2_capture_device(void)
return
NULL
;
}
static
void
configure_leaky_queue
(
GstElement
*
q
)
static
guint
frmival_to_fps
(
const
struct
v4l2_frmivalenum
*
fi
)
{
if
(
!
fi
)
return
0
;
if
(
fi
->
type
==
V4L2_FRMIVAL_TYPE_DISCRETE
)
{
if
(
!
fi
->
discrete
.
denominator
||
!
fi
->
discrete
.
numerator
)
return
0
;
return
fi
->
discrete
.
denominator
/
fi
->
discrete
.
numerator
;
}
if
(
fi
->
type
==
V4L2_FRMIVAL_TYPE_STEPWISE
||
fi
->
type
==
V4L2_FRMIVAL_TYPE_CONTINUOUS
)
{
if
(
!
fi
->
stepwise
.
min
.
denominator
||
!
fi
->
stepwise
.
min
.
numerator
)
return
0
;
return
fi
->
stepwise
.
min
.
denominator
/
fi
->
stepwise
.
min
.
numerator
;
}
return
0
;
}
static
gboolean
frmival_meets_fps
(
const
struct
v4l2_frmivalenum
*
fi
,
guint
min_fps
)
{
guint
fps
=
frmival_to_fps
(
fi
);
return
fps
>=
min_fps
;
}
static
gboolean
size_is_720p
(
const
struct
v4l2_frmsizeenum
*
fs
)
{
if
(
fs
->
type
==
V4L2_FRMSIZE_TYPE_DISCRETE
)
return
fs
->
discrete
.
width
==
MPP_VIDEO_WIDTH
&&
fs
->
discrete
.
height
==
MPP_VIDEO_HEIGHT
;
if
(
fs
->
type
==
V4L2_FRMSIZE_TYPE_STEPWISE
||
fs
->
type
==
V4L2_FRMSIZE_TYPE_CONTINUOUS
)
return
fs
->
stepwise
.
min_width
<=
MPP_VIDEO_WIDTH
&&
fs
->
stepwise
.
max_width
>=
MPP_VIDEO_WIDTH
&&
fs
->
stepwise
.
min_height
<=
MPP_VIDEO_HEIGHT
&&
fs
->
stepwise
.
max_height
>=
MPP_VIDEO_HEIGHT
;
return
FALSE
;
}
/* 探测 720p 采集:优先 NV12/YUYV@24fps+,否则 MJPEG@30(本机 USB 头常见情况) */
static
CaptureMode
probe_720p_capture_mode
(
const
char
*
device
)
{
int
fd
;
struct
v4l2_fmtdesc
fmt
;
gboolean
raw_ok
=
FALSE
;
gboolean
mjpg24_ok
=
FALSE
;
gboolean
mjpg30_ok
=
FALSE
;
if
(
!
device
||
!
device
[
0
])
return
CAPTURE_NONE
;
fd
=
open
(
device
,
O_RDONLY
|
O_NONBLOCK
);
if
(
fd
<
0
)
return
CAPTURE_NONE
;
memset
(
&
fmt
,
0
,
sizeof
(
fmt
));
fmt
.
type
=
V4L2_BUF_TYPE_VIDEO_CAPTURE
;
for
(;
ioctl
(
fd
,
VIDIOC_ENUM_FMT
,
&
fmt
)
==
0
;
fmt
.
index
++
)
{
struct
v4l2_frmsizeenum
fs
;
memset
(
&
fs
,
0
,
sizeof
(
fs
));
fs
.
pixel_format
=
fmt
.
pixelformat
;
fs
.
index
=
0
;
for
(;
ioctl
(
fd
,
VIDIOC_ENUM_FRAMESIZES
,
&
fs
)
==
0
;
fs
.
index
++
)
{
struct
v4l2_frmivalenum
fi
;
if
(
!
size_is_720p
(
&
fs
))
continue
;
memset
(
&
fi
,
0
,
sizeof
(
fi
));
fi
.
pixel_format
=
fmt
.
pixelformat
;
fi
.
index
=
0
;
if
(
fs
.
type
==
V4L2_FRMSIZE_TYPE_DISCRETE
)
{
fi
.
width
=
fs
.
discrete
.
width
;
fi
.
height
=
fs
.
discrete
.
height
;
}
else
{
fi
.
width
=
MPP_VIDEO_WIDTH
;
fi
.
height
=
MPP_VIDEO_HEIGHT
;
}
for
(;
ioctl
(
fd
,
VIDIOC_ENUM_FRAMEINTERVALS
,
&
fi
)
==
0
;
fi
.
index
++
)
{
guint
fps
=
frmival_to_fps
(
&
fi
);
if
(
fmt
.
pixelformat
==
V4L2_PIX_FMT_MJPEG
)
{
/* 必须能直出目标帧率;30fps 不能冒充 24fps(v4l2 会 not-negotiated) */
if
(
fps
==
WEBRTCPUSH_H264_FPS
)
mjpg24_ok
=
TRUE
;
if
(
fps
>=
30
)
mjpg30_ok
=
TRUE
;
}
if
((
fmt
.
pixelformat
==
V4L2_PIX_FMT_NV12
||
fmt
.
pixelformat
==
V4L2_PIX_FMT_YUYV
)
&&
fps
>=
WEBRTCPUSH_H264_FPS
)
{
raw_ok
=
TRUE
;
}
}
}
}
close
(
fd
);
if
(
raw_ok
)
{
my_zlog_info
(
"mpp_h264_source: %s supports 720p raw >=%dfps, use NV12/YUYV direct"
,
device
,
WEBRTCPUSH_H264_FPS
);
return
CAPTURE_RAW_NV12_720P
;
}
if
(
mjpg24_ok
)
{
my_zlog_info
(
"mpp_h264_source: %s supports 720p MJPEG@%dfps direct"
,
device
,
WEBRTCPUSH_H264_FPS
);
return
CAPTURE_MJPEG_720P24
;
}
if
(
mjpg30_ok
)
{
my_zlog_info
(
"mpp_h264_source: %s 720p MJPEG@30fps -> mppjpegdec -> videorate@%dfps"
,
device
,
WEBRTCPUSH_H264_FPS
);
return
CAPTURE_MJPEG_720P30
;
}
my_zlog_warn
(
"mpp_h264_source: %s has no suitable 720p capture mode"
,
device
);
return
CAPTURE_NONE
;
}
static
void
configure_pre_enc_queue
(
GstElement
*
q
)
{
if
(
!
q
)
return
;
g_object_set
(
q
,
"max-size-buffers"
,
1
,
"max-size-buffers"
,
WEBRTCPUSH_MPP_PRE_ENC_BUFFERS
,
"max-size-bytes"
,
0
,
"max-size-time"
,
(
guint64
)
0
,
"max-size-time"
,
(
guint64
)
GST_SECOND
*
2
/
WEBRTCPUSH_H264_FPS
,
"leaky"
,
2
,
NULL
);
}
static
void
configure_v4l2_low_latency
(
GstElement
*
src
)
static
void
configure_post_enc_queue
(
GstElement
*
q
)
{
if
(
!
q
)
return
;
g_object_set
(
q
,
"max-size-buffers"
,
WEBRTCPUSH_MPP_POST_ENC_BUFFERS
,
"max-size-bytes"
,
0
,
"max-size-time"
,
(
guint64
)
GST_SECOND
/
WEBRTCPUSH_H264_FPS
,
"leaky"
,
0
,
NULL
);
}
static
void
configure_v4l2_src
(
GstElement
*
src
,
gboolean
compressed_mjpeg
)
{
if
(
!
src
)
return
;
g_object_set
(
src
,
"do-timestamp"
,
TRUE
,
NULL
);
/*
* MJPEG 压缩流用 mmap;原始 NV12/YUYV 再尝试 DMABUF 零拷贝进 MPP。
*/
if
(
compressed_mjpeg
)
g_object_set
(
src
,
"io-mode"
,
2
,
NULL
);
else
g_object_set
(
src
,
"io-mode"
,
4
,
NULL
);
}
static
GstElement
*
make_h264_encoder
(
gboolean
prefer_mpp
,
gboolean
use_test
,
...
...
@@ -91,17 +292,17 @@ static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
*
use_mpp_out
=
TRUE
;
g_object_set
(
enc
,
"gop"
,
MPP_VIDEO_GOP
,
"rc-mode"
,
1
,
"rc-mode"
,
1
,
/* CBR */
"bps"
,
MPP_VIDEO_START_BPS
,
"bps-min"
,
(
guint
)(
MPP_VIDEO_START_BPS
*
0
.
80
),
"bps-max"
,
(
guint
)(
MPP_VIDEO_START_BPS
*
1
.
08
),
"header-mode"
,
1
,
"profile"
,
66
,
"profile"
,
MPP_H264_PROFILE
,
"level"
,
31
,
"qos"
,
TRUE
,
"zero-copy-pkt"
,
TRUE
,
NULL
);
my_zlog_info
(
"mpp_h264_source: mpph264enc %dx%d@%dfps GOP=%d"
,
my_zlog_info
(
"mpp_h264_source: mpph264enc %dx%d@%dfps GOP=%d
Baseline/CBR
"
,
MPP_VIDEO_WIDTH
,
MPP_VIDEO_HEIGHT
,
WEBRTCPUSH_H264_FPS
,
MPP_VIDEO_GOP
);
return
enc
;
...
...
@@ -117,7 +318,7 @@ static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
"bitrate"
,
MPP_VIDEO_START_BPS
/
1000
,
"key-int-max"
,
MPP_VIDEO_GOP
,
"byte-stream"
,
TRUE
,
"profile"
,
"
baseline
"
,
"profile"
,
"
main
"
,
NULL
);
my_zlog_info
(
"mpp_h264_source: x264enc software fallback"
);
return
enc
;
...
...
@@ -132,6 +333,8 @@ static gboolean buffer_is_idr(const guint8 *data, size_t size)
while
(
i
+
3
<
size
)
{
size_t
sc
=
0
;
guint8
nal_type
;
if
(
i
+
4
<=
size
&&
data
[
i
]
==
0
&&
data
[
i
+
1
]
==
0
&&
data
[
i
+
2
]
==
0
&&
data
[
i
+
3
]
==
1
)
sc
=
4
;
...
...
@@ -145,44 +348,69 @@ static gboolean buffer_is_idr(const guint8 *data, size_t size)
i
+=
sc
;
if
(
i
>=
size
)
break
;
if
((
data
[
i
]
&
0x1f
)
==
5
)
nal_type
=
data
[
i
]
&
0x1f
;
if
(
nal_type
==
5
)
return
TRUE
;
if
(
nal_type
==
1
)
return
FALSE
;
}
return
FALSE
;
}
static
gboolean
send_force_key_unit_on_pad
(
GstPad
*
pad
)
static
GstPadProbeReturn
enc_sink_idr_probe
(
GstPad
*
pad
,
GstPadProbeInfo
*
info
,
gpointer
user_data
)
{
GstEvent
*
event
;
MppH264Source
*
src
=
user_data
;
if
(
!
pad
)
return
FALSE
;
event
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
0
);
return
gst_pad_send_event
(
pad
,
event
);
if
(
!
(
GST_PAD_PROBE_INFO_TYPE
(
info
)
&
GST_PAD_PROBE_TYPE_BUFFER
))
return
GST_PAD_PROBE_OK
;
if
(
!
src
||
!
src
->
pending_idr
)
return
GST_PAD_PROBE_OK
;
{
GstEvent
*
ev
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
gboolean
ok
=
gst_pad_send_event
(
pad
,
ev
);
if
(
!
ok
&&
src
->
enc
)
{
ev
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
ok
=
gst_element_send_event
(
src
->
enc
,
ev
);
}
if
(
ok
)
{
src
->
pending_idr
=
FALSE
;
my_zlog_info
(
"mpp_h264_source: IDR armed in-stream (%u)"
,
src
->
keyframe_req
);
}
else
{
my_zlog_warn
(
"mpp_h264_source: in-stream IDR not handled"
);
}
}
return
GST_PAD_PROBE_OK
;
}
static
void
request_keyframe
(
MppH264Source
*
src
)
{
gboolean
ok
=
FALSE
;
if
(
!
src
)
return
;
if
(
src
->
pending_idr
)
return
;
src
->
keyframe_req
++
;
/*
Upstream force-key-unit 必须交给编码器的 src pad 处理。
*/
src
->
pending_idr
=
TRUE
;
/*
兜底:直接向编码器发 force-key-unit,不依赖 enc_sink_probe 触发时机
*/
if
(
src
->
enc
)
{
GstPad
*
pad
=
gst_element_get_static_pad
(
src
->
enc
,
"src"
);
ok
=
send_force_key_unit_on_pad
(
pad
);
if
(
pad
)
gst_object_unref
(
pad
);
GstEvent
*
ev
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
if
(
!
gst_element_send_event
(
src
->
enc
,
ev
))
{
GstPad
*
sink_pad
=
gst_element_get_static_pad
(
src
->
enc
,
"sink"
);
if
(
sink_pad
)
{
ev
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
gst_pad_send_event
(
sink_pad
,
ev
);
gst_object_unref
(
sink_pad
);
}
}
}
if
(
ok
)
my_zlog_info
(
"mpp_h264_source: requested IDR (%u)"
,
src
->
keyframe_req
);
else
my_zlog_warn
(
"mpp_h264_source: IDR request not handled"
);
my_zlog_info
(
"mpp_h264_source: IDR queued (%u)"
,
src
->
keyframe_req
);
}
void
mpp_h264_source_request_keyframe
(
MppH264Source
*
src
)
...
...
@@ -190,6 +418,63 @@ void mpp_h264_source_request_keyframe(MppH264Source *src)
request_keyframe
(
src
);
}
static
gboolean
extract_sprop_from_au
(
MppH264Source
*
src
,
const
guint8
*
data
,
size_t
size
)
{
gchar
*
sps_b64
=
NULL
;
gchar
*
pps_b64
=
NULL
;
if
(
!
src
||
!
data
||
size
<
5
||
src
->
sprop_parameter_sets
)
return
src
&&
src
->
sprop_parameter_sets
!=
NULL
;
for
(
size_t
i
=
0
;
i
+
4
<
size
;)
{
size_t
nal_start
=
0
;
guint8
nal_type
;
if
(
data
[
i
]
==
0
&&
data
[
i
+
1
]
==
0
&&
data
[
i
+
2
]
==
1
)
{
nal_start
=
i
+
3
;
i
+=
3
;
}
else
if
(
data
[
i
]
==
0
&&
data
[
i
+
1
]
==
0
&&
data
[
i
+
2
]
==
0
&&
data
[
i
+
3
]
==
1
)
{
nal_start
=
i
+
4
;
i
+=
4
;
}
else
{
i
++
;
continue
;
}
nal_type
=
data
[
nal_start
]
&
0x1f
;
{
size_t
j
=
nal_start
+
1
;
while
(
j
+
3
<
size
)
{
if
(
data
[
j
]
==
0
&&
data
[
j
+
1
]
==
0
&&
(
data
[
j
+
2
]
==
1
||
(
data
[
j
+
2
]
==
0
&&
data
[
j
+
3
]
==
1
)))
break
;
j
++
;
}
if
(
j
+
3
>=
size
)
j
=
size
;
if
(
nal_type
==
7
&&
!
sps_b64
)
sps_b64
=
g_base64_encode
(
data
+
nal_start
,
j
-
nal_start
);
else
if
(
nal_type
==
8
&&
!
pps_b64
)
pps_b64
=
g_base64_encode
(
data
+
nal_start
,
j
-
nal_start
);
i
=
j
;
}
if
(
sps_b64
&&
pps_b64
)
break
;
}
if
(
sps_b64
&&
pps_b64
)
{
g_free
(
src
->
sprop_parameter_sets
);
src
->
sprop_parameter_sets
=
g_strdup_printf
(
"%s,%s"
,
sps_b64
,
pps_b64
);
my_zlog_info
(
"mpp_h264_source: captured sprop for SDP (%zu+%zu b64)"
,
strlen
(
sps_b64
),
strlen
(
pps_b64
));
}
g_free
(
sps_b64
);
g_free
(
pps_b64
);
return
src
->
sprop_parameter_sets
!=
NULL
;
}
static
gboolean
copy_frame
(
MppH264Source
*
src
,
GstBuffer
*
buffer
)
{
GstMapInfo
map
;
...
...
@@ -203,22 +488,41 @@ static gboolean copy_frame(MppH264Source *src, GstBuffer *buffer)
}
memcpy
(
src
->
frame_buf
,
map
.
data
,
map
.
size
);
src
->
frame_size
=
map
.
size
;
extract_sprop_from_au
(
src
,
src
->
frame_buf
,
src
->
frame_size
);
gst_buffer_unmap
(
buffer
,
&
map
);
return
TRUE
;
}
static
gboolean
link_pipeline
(
GstElement
*
pipe
,
GstElement
**
elements
,
guint
count
)
{
guint
i
;
if
(
!
pipe
||
!
elements
||
count
<
2
)
return
FALSE
;
for
(
i
=
0
;
i
<
count
;
i
++
)
gst_bin_add
(
GST_BIN
(
pipe
),
elements
[
i
]);
for
(
i
=
0
;
i
+
1
<
count
;
i
++
)
{
if
(
!
gst_element_link
(
elements
[
i
],
elements
[
i
+
1
]))
return
FALSE
;
}
return
TRUE
;
}
MppH264Source
*
mpp_h264_source_start
(
const
char
*
video_device
,
char
**
error_message
)
{
MppH264Source
*
src
;
GstElement
*
pipe
,
*
vsrc
,
*
caps_
jpeg
=
NULL
,
*
jpegparse
=
NULL
,
*
jpegdec
=
NULL
;
GstElement
*
vrate
,
*
conv
,
*
caps1
,
*
enc
,
*
parse
,
*
q
,
*
caps_h264
,
*
sink
;
GstElement
*
pipe
,
*
vsrc
,
*
caps_
in
=
NULL
,
*
jpegparse
=
NULL
,
*
jpegdec
=
NULL
;
GstElement
*
conv
=
NULL
,
*
vrate
,
*
caps_nv12
,
*
enc
,
*
parse
,
*
q
,
*
e
q
,
*
caps_h264
,
*
sink
;
gchar
*
auto_v4l
=
NULL
;
const
gchar
*
vdev
=
video_device
;
gboolean
use_test
=
FALSE
;
gboolean
use_auto
=
FALSE
;
gboolean
use_mjpg
=
FALSE
;
gboolean
use_mpp
=
FALSE
;
gboolean
use_mpp_jpeg
=
FALSE
;
gint
mjpeg_fps
=
WEBRTCPUSH_H264_FPS
;
CaptureMode
capture_mode
=
CAPTURE_NONE
;
GstStateChangeReturn
ret
;
if
(
!
gst_is_initialized
())
...
...
@@ -234,21 +538,55 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
if
(
auto_v4l
)
{
my_zlog_info
(
"mpp_h264_source: auto V4L2 %s"
,
auto_v4l
);
vdev
=
auto_v4l
;
capture_mode
=
probe_720p_capture_mode
(
vdev
);
}
else
{
my_zlog_warn
(
"mpp_h264_source: no V4L2 device, videotestsrc"
);
use_test
=
TRUE
;
}
}
else
if
(
!
use_test
)
{
capture_mode
=
probe_720p_capture_mode
(
vdev
);
}
if
(
!
use_test
&&
capture_mode
==
CAPTURE_NONE
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"no 720p capture mode on camera"
);
g_free
(
auto_v4l
);
g_free
(
src
);
return
NULL
;
}
pipe
=
gst_pipeline_new
(
"mpp-h264-pipe"
);
vrate
=
gst_element_factory_make
(
"videorate"
,
"vrate"
);
caps_nv12
=
gst_element_factory_make
(
"capsfilter"
,
"caps_nv12"
);
q
=
gst_element_factory_make
(
"queue"
,
"vq"
);
eq
=
gst_element_factory_make
(
"queue"
,
"eq"
);
enc
=
make_h264_encoder
(
TRUE
,
use_test
,
&
use_mpp
);
parse
=
gst_element_factory_make
(
"h264parse"
,
"parse"
);
caps_h264
=
gst_element_factory_make
(
"capsfilter"
,
"caps_h264"
);
sink
=
gst_element_factory_make
(
"appsink"
,
"sink"
);
if
(
use_test
)
{
vsrc
=
gst_element_factory_make
(
"videotestsrc"
,
"vsrc"
);
g_object_set
(
vsrc
,
"is-live"
,
TRUE
,
NULL
);
}
else
{
conv
=
gst_element_factory_make
(
"videoconvert"
,
"conv"
);
}
else
if
(
capture_mode
==
CAPTURE_RAW_NV12_720P
)
{
vsrc
=
gst_element_factory_make
(
"v4l2src"
,
"vsrc"
);
caps_in
=
gst_element_factory_make
(
"capsfilter"
,
"caps_in"
);
g_object_set
(
vsrc
,
"device"
,
vdev
,
NULL
);
configure_v4l2_low_latency
(
vsrc
);
caps_jpeg
=
gst_element_factory_make
(
"capsfilter"
,
"caps_jpeg"
);
configure_v4l2_src
(
vsrc
,
FALSE
);
{
gchar
*
cap_str
=
g_strdup_printf
(
"video/x-raw,format=NV12,width=%d,height=%d,framerate=%d/1"
,
MPP_VIDEO_WIDTH
,
MPP_VIDEO_HEIGHT
,
WEBRTCPUSH_H264_FPS
);
GstCaps
*
caps
=
gst_caps_from_string
(
cap_str
);
g_object_set
(
caps_in
,
"caps"
,
caps
,
NULL
);
gst_caps_unref
(
caps
);
g_free
(
cap_str
);
}
my_zlog_info
(
"mpp_h264_source: 720p NV12 direct capture (no JPEG decode)"
);
}
else
{
vsrc
=
gst_element_factory_make
(
"v4l2src"
,
"vsrc"
);
caps_in
=
gst_element_factory_make
(
"capsfilter"
,
"caps_in"
);
jpegparse
=
gst_element_factory_make
(
"jpegparse"
,
"jpegparse"
);
jpegdec
=
gst_element_factory_make
(
"mppjpegdec"
,
"jpegdec"
);
if
(
jpegdec
)
{
...
...
@@ -257,32 +595,26 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
}
else
{
jpegdec
=
gst_element_factory_make
(
"jpegdec"
,
"jpegdec"
);
}
if
(
caps_jpeg
&&
jpegparse
&&
jpegdec
)
{
gchar
*
jc_str
=
g_strdup_printf
(
"image/jpeg,width=%d,height=%d,framerate=30/1"
,
MPP_VIDEO_WIDTH
,
MPP_VIDEO_HEIGHT
);
GstCaps
*
jc
=
gst_caps_from_string
(
jc_str
);
g_object_set
(
caps_jpeg
,
"caps"
,
jc
,
NULL
);
gst_caps_unref
(
jc
);
g_free
(
jc
_str
);
use_mjpg
=
TRUE
;
my_zlog_info
(
"mpp_h264_source: JPEG decoder=%s"
,
use_mpp_jpeg
?
"mppjpegdec"
:
"jpegdec software fallback"
);
mjpeg_fps
=
(
capture_mode
==
CAPTURE_MJPEG_720P24
)
?
WEBRTCPUSH_H264_FPS
:
30
;
g_object_set
(
vsrc
,
"device"
,
vdev
,
NULL
);
configure_v4l2_src
(
vsrc
,
TRUE
);
{
gchar
*
cap_str
=
g_strdup_printf
(
"image/jpeg,width=%d,height=%d,framerate=%d/1"
,
MPP_VIDEO_WIDTH
,
MPP_VIDEO_HEIGHT
,
mjpeg_fps
);
GstCaps
*
caps
=
gst_caps_from_string
(
cap
_str
);
g_object_set
(
caps_in
,
"caps"
,
caps
,
NULL
)
;
gst_caps_unref
(
caps
);
g_free
(
cap_str
);
}
my_zlog_info
(
"mpp_h264_source: 720p MJPEG@%dfps -> %s -> NV12@%dfps"
,
mjpeg_fps
,
use_mpp_jpeg
?
"mppjpegdec"
:
"jpegdec"
,
WEBRTCPUSH_H264_FPS
);
}
conv
=
gst_element_factory_make
(
"videoconvert"
,
"conv"
);
vrate
=
gst_element_factory_make
(
"videorate"
,
"vrate"
);
caps1
=
gst_element_factory_make
(
"capsfilter"
,
"caps1"
);
q
=
gst_element_factory_make
(
"queue"
,
"vq"
);
enc
=
make_h264_encoder
(
TRUE
,
use_test
,
&
use_mpp
);
parse
=
gst_element_factory_make
(
"h264parse"
,
"parse"
);
caps_h264
=
gst_element_factory_make
(
"capsfilter"
,
"caps_h264"
);
sink
=
gst_element_factory_make
(
"appsink"
,
"sink"
);
if
(
!
pipe
||
!
vsrc
||
!
conv
||
!
vrate
||
!
caps1
||
!
q
||
!
enc
||
!
parse
||
!
caps_h264
||
!
sink
||
(
!
use_test
&&
!
use_mjpg
))
{
g_set_error_literal
(
NULL
,
0
,
0
,
"GStreamer element create failed"
);
if
(
!
pipe
||
!
vsrc
||
!
vrate
||
!
caps_nv12
||
!
q
||
!
eq
||
!
enc
||
!
parse
||
!
caps_h264
||
!
sink
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"GStreamer element create failed"
);
g_free
(
auto_v4l
);
...
...
@@ -291,18 +623,18 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
}
g_object_set
(
vrate
,
"max-rate"
,
WEBRTCPUSH_H264_FPS
,
"drop-only"
,
TRUE
,
NULL
);
configure_leaky_queue
(
q
);
configure_pre_enc_queue
(
q
);
configure_post_enc_queue
(
eq
);
g_object_set
(
parse
,
"config-interval"
,
-
1
,
NULL
);
{
gchar
*
cap_str
=
g_strdup_printf
(
"video/x-raw,format=%s,width=%d,height=%d,framerate=%d/1"
,
use_mpp
?
"NV12"
:
"I420"
,
"video/x-raw,format=NV12,width=%d,height=%d,framerate=%d/1"
,
use_test
?
640
:
MPP_VIDEO_WIDTH
,
use_test
?
480
:
MPP_VIDEO_HEIGHT
,
WEBRTCPUSH_H264_FPS
);
GstCaps
*
caps
=
gst_caps_from_string
(
cap_str
);
g_object_set
(
caps
1
,
"caps"
,
caps
,
NULL
);
g_object_set
(
caps
_nv12
,
"caps"
,
caps
,
NULL
);
gst_caps_unref
(
caps
);
g_free
(
cap_str
);
}
...
...
@@ -317,29 +649,37 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
g_object_set
(
sink
,
"emit-signals"
,
FALSE
,
"sync"
,
FALSE
,
"max-buffers"
,
1
,
"max-buffers"
,
WEBRTCPUSH_APPSINK_MAX_BUFFERS
,
"drop"
,
TRUE
,
NULL
);
if
(
use_mjpg
)
{
gst_bin_add_many
(
GST_BIN
(
pipe
),
vsrc
,
caps_jpeg
,
jpegparse
,
jpegdec
,
conv
,
vrate
,
caps1
,
q
,
enc
,
parse
,
caps_h264
,
sink
,
NULL
);
if
(
!
gst_element_link_many
(
vsrc
,
caps_jpeg
,
jpegparse
,
jpegdec
,
conv
,
vrate
,
caps1
,
q
,
enc
,
parse
,
caps_h264
,
sink
,
NULL
))
{
if
(
use_test
)
{
GstElement
*
elems
[]
=
{
vsrc
,
conv
,
vrate
,
caps_nv12
,
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
};
if
(
!
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
)))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link test chain"
);
gst_object_unref
(
pipe
);
g_free
(
auto_v4l
);
g_free
(
src
);
return
NULL
;
}
}
else
if
(
capture_mode
==
CAPTURE_RAW_NV12_720P
)
{
GstElement
*
elems
[]
=
{
vsrc
,
caps_in
,
vrate
,
caps_nv12
,
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
};
if
(
!
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
)))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link
MJPG capture
chain"
);
*
error_message
=
g_strdup
(
"failed to link
raw NV12
chain"
);
gst_object_unref
(
pipe
);
g_free
(
auto_v4l
);
g_free
(
src
);
return
NULL
;
}
}
else
{
gst_bin_add_many
(
GST_BIN
(
pipe
),
vsrc
,
conv
,
vrate
,
caps1
,
q
,
enc
,
parse
,
caps_h264
,
sink
,
NULL
)
;
if
(
!
gst_element_link_many
(
vsrc
,
conv
,
vrate
,
caps1
,
q
,
enc
,
parse
,
caps_h264
,
sink
,
NULL
))
{
GstElement
*
elems
[]
=
{
vsrc
,
caps_in
,
jpegparse
,
jpegdec
,
vrate
,
caps_nv12
,
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
}
;
if
(
!
jpegparse
||
!
jpegdec
||
!
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
)
))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link
video
chain"
);
*
error_message
=
g_strdup
(
"failed to link
MJPEG 720p
chain"
);
gst_object_unref
(
pipe
);
g_free
(
auto_v4l
);
g_free
(
src
);
...
...
@@ -350,9 +690,19 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
src
->
pipeline
=
pipe
;
src
->
enc
=
enc
;
src
->
parse
=
parse
;
src
->
eq
=
eq
;
src
->
appsink
=
sink
;
src
->
use_mpp
=
use_mpp
;
{
GstPad
*
enc_sink
=
gst_element_get_static_pad
(
enc
,
"sink"
);
if
(
enc_sink
)
{
src
->
enc_sink_probe_id
=
gst_pad_add_probe
(
enc_sink
,
GST_PAD_PROBE_TYPE_BUFFER
,
enc_sink_idr_probe
,
src
,
NULL
);
gst_object_unref
(
enc_sink
);
}
}
gst_pipeline_set_latency
(
GST_PIPELINE
(
pipe
),
0
);
ret
=
gst_element_set_state
(
pipe
,
GST_STATE_PLAYING
);
if
(
ret
==
GST_STATE_CHANGE_FAILURE
)
{
...
...
@@ -363,7 +713,34 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
return
NULL
;
}
my_zlog_info
(
"mpp_h264_source: live encoder started (mpp=%d)"
,
use_mpp
?
1
:
0
);
{
GstBus
*
bus
=
gst_element_get_bus
(
pipe
);
if
(
bus
)
{
src
->
bus_watch_id
=
gst_bus_add_watch
(
bus
,
bus_watch_cb
,
src
);
gst_object_unref
(
bus
);
}
}
{
const
guint8
*
warm_data
;
size_t
warm_size
;
gboolean
warm_idr
;
if
(
!
pull_sample
(
src
,
FALSE
,
8000
,
&
warm_data
,
&
warm_size
,
&
warm_idr
,
NULL
))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"encoder warmup timeout (no frame in 8s)"
);
my_zlog_error
(
"mpp_h264_source: warmup failed, check camera /dev/video*"
);
mpp_h264_source_stop
(
src
);
g_free
(
auto_v4l
);
return
NULL
;
}
my_zlog_info
(
"mpp_h264_source: warmup OK first_au=%zu bytes idr=%d"
,
warm_size
,
warm_idr
?
1
:
0
);
}
my_zlog_info
(
"mpp_h264_source: live encoder started 720p (mpp=%d pre_buf=%d post_buf=%d)"
,
use_mpp
?
1
:
0
,
WEBRTCPUSH_MPP_PRE_ENC_BUFFERS
,
WEBRTCPUSH_MPP_POST_ENC_BUFFERS
);
g_free
(
auto_v4l
);
return
src
;
}
...
...
@@ -387,7 +764,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
appsink
=
GST_APP_SINK
(
src
->
appsink
);
deadline
=
g_get_monotonic_time
()
+
(
gint64
)
timeout_ms
*
1000
;
while
(
attempts
<
4
0
)
{
while
(
attempts
<
2
0
)
{
gint
wait_ms
=
(
gint
)((
deadline
-
g_get_monotonic_time
())
/
1000
);
if
(
wait_ms
<
1
)
...
...
@@ -439,15 +816,17 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
return
FALSE
;
if
(
timeout_ms
<
1
)
timeout_ms
=
8
0
;
timeout_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
6
0
;
if
(
force_idr
)
{
idr_timeout
=
timeout_ms
<
2000
?
2000
:
timeout_ms
;
request_keyframe
(
src
);
if
(
pull_sample
(
src
,
TRUE
,
idr_timeout
,
data
,
size
,
is_idr
,
pts_ns
))
return
TRUE
;
my_zlog_warn
(
"mpp_h264_source: IDR wait timeout, fallback to latest frame"
);
return
pull_sample
(
src
,
FALSE
,
200
,
data
,
size
,
is_idr
,
pts_ns
);
my_zlog_warn
(
"mpp_h264_source: IDR wait timeout, drain latest frame"
);
if
(
pull_sample
(
src
,
FALSE
,
500
,
data
,
size
,
is_idr
,
pts_ns
))
return
TRUE
;
return
FALSE
;
}
return
pull_sample
(
src
,
FALSE
,
timeout_ms
,
data
,
size
,
is_idr
,
pts_ns
);
...
...
@@ -481,16 +860,52 @@ void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
}
}
gboolean
mpp_h264_source_recover
(
MppH264Source
*
src
)
{
const
guint8
*
data
;
size_t
size
;
gboolean
idr
;
GstStateChangeReturn
ret
;
if
(
!
src
||
!
src
->
pipeline
)
return
FALSE
;
my_zlog_warn
(
"mpp_h264_source: recovering pipeline (NULL -> PLAYING)"
);
src
->
pending_idr
=
FALSE
;
gst_element_set_state
(
src
->
pipeline
,
GST_STATE_NULL
);
g_usleep
(
300000
);
ret
=
gst_element_set_state
(
src
->
pipeline
,
GST_STATE_PLAYING
);
if
(
ret
==
GST_STATE_CHANGE_FAILURE
)
{
my_zlog_error
(
"mpp_h264_source: recover failed to reach PLAYING"
);
return
FALSE
;
}
if
(
!
pull_sample
(
src
,
FALSE
,
5000
,
&
data
,
&
size
,
&
idr
,
NULL
))
{
my_zlog_error
(
"mpp_h264_source: recover warmup failed"
);
return
FALSE
;
}
my_zlog_info
(
"mpp_h264_source: recover OK first_au=%zu bytes"
,
size
);
return
TRUE
;
}
gboolean
mpp_h264_source_is_mpp_encoder
(
const
MppH264Source
*
src
)
{
return
src
&&
src
->
use_mpp
;
}
const
gchar
*
mpp_h264_source_get_sprop
(
const
MppH264Source
*
src
)
{
return
src
?
src
->
sprop_parameter_sets
:
NULL
;
}
void
mpp_h264_source_stop
(
MppH264Source
*
src
)
{
if
(
!
src
)
return
;
if
(
src
->
bus_watch_id
)
{
g_source_remove
(
src
->
bus_watch_id
);
src
->
bus_watch_id
=
0
;
}
if
(
src
->
pipeline
)
{
gst_element_set_state
(
src
->
pipeline
,
GST_STATE_NULL
);
gst_object_unref
(
src
->
pipeline
);
...
...
@@ -498,7 +913,9 @@ void mpp_h264_source_stop(MppH264Source *src)
}
src
->
enc
=
NULL
;
src
->
parse
=
NULL
;
src
->
eq
=
NULL
;
src
->
appsink
=
NULL
;
g_free
(
src
->
sprop_parameter_sets
);
g_free
(
src
->
frame_buf
);
g_free
(
src
);
}
modules/webrtcpush/mpp_h264_source.h
View file @
7f0fd331
...
...
@@ -22,4 +22,9 @@ void mpp_h264_source_request_keyframe(MppH264Source *src);
gboolean
mpp_h264_source_is_mpp_encoder
(
const
MppH264Source
*
src
);
const
gchar
*
mpp_h264_source_get_sprop
(
const
MppH264Source
*
src
);
/* 管道无帧时尝试 NULL→PLAYING 恢复 */
gboolean
mpp_h264_source_recover
(
MppH264Source
*
src
);
#endif
modules/webrtcpush/rtc_client.c
View file @
7f0fd331
#include "rtc_client.h"
#include "mpp_h264_source.h"
#include "audio_source.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include "ws_signaling.h"
...
...
@@ -23,10 +24,35 @@ typedef struct {
}
OfferVideo
;
typedef
struct
{
gint
payload_type
;
gchar
*
mid
;
gboolean
found
;
}
OfferAudio
;
typedef
struct
{
guint8
*
data
;
gsize
size
;
gboolean
is_idr
;
guint64
pts_ns
;
}
RtcH264Frame
;
typedef
struct
{
GMutex
lock
;
GCond
not_empty
;
GQueue
queue
;
guint
max_depth
;
volatile
gboolean
stopping
;
guint
dropped_total
;
}
RtcFrameQueue
;
typedef
struct
{
AppState
*
app
;
GMutex
lock
;
GMutex
send_lock
;
GThread
*
sender_thread
;
GCond
track_cond
;
GThread
*
pull_thread
;
GThread
*
send_thread
;
RtcFrameQueue
frame_queue
;
MppH264Source
*
h264_source
;
gboolean
stopping
;
gboolean
track_open
;
...
...
@@ -42,9 +68,22 @@ typedef struct {
guint
target_bitrate
;
guint
remb_ceiling
;
gint64
last_bitrate_ramp_us
;
gint64
last_idr_request_us
;
gint64
last_stats_log_us
;
gchar
*
remote_ice_ufrag
;
gchar
*
video_mid
;
gint
video_mline_index
;
/* 音频 Opus track */
AudioSource
*
audio_source
;
int
audio_track
;
gboolean
audio_track_open
;
GThread
*
audio_send_thread
;
guint32
audio_rtp_timestamp
;
guint64
audio_pts_base
;
guint64
audio_last_pts
;
gboolean
audio_pts_valid
;
GQueue
*
pending_local_ice
;
GQueue
*
pending_remote_ice
;
}
RtcClient
;
...
...
@@ -61,6 +100,106 @@ static RtcClient *client_from_app(AppState *app)
return
app
?
(
RtcClient
*
)
app
->
rtc_client
:
NULL
;
}
static
RtcH264Frame
*
rtc_h264_frame_new
(
const
guint8
*
data
,
gsize
size
,
gboolean
is_idr
,
guint64
pts_ns
)
{
RtcH264Frame
*
frame
=
g_new
(
RtcH264Frame
,
1
);
frame
->
data
=
g_memdup2
(
data
,
size
);
frame
->
size
=
size
;
frame
->
is_idr
=
is_idr
;
frame
->
pts_ns
=
pts_ns
;
return
frame
;
}
static
void
rtc_h264_frame_free
(
RtcH264Frame
*
frame
)
{
if
(
!
frame
)
return
;
g_free
(
frame
->
data
);
g_free
(
frame
);
}
static
void
frame_queue_init
(
RtcFrameQueue
*
q
,
guint
max_depth
)
{
g_mutex_init
(
&
q
->
lock
);
g_cond_init
(
&
q
->
not_empty
);
g_queue_init
(
&
q
->
queue
);
q
->
max_depth
=
max_depth
;
q
->
stopping
=
FALSE
;
q
->
dropped_total
=
0
;
}
static
void
frame_queue_fini
(
RtcFrameQueue
*
q
)
{
RtcH264Frame
*
frame
;
g_mutex_lock
(
&
q
->
lock
);
while
((
frame
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
rtc_h264_frame_free
(
frame
);
g_mutex_unlock
(
&
q
->
lock
);
g_queue_clear
(
&
q
->
queue
);
g_cond_clear
(
&
q
->
not_empty
);
g_mutex_clear
(
&
q
->
lock
);
}
static
void
frame_queue_stop
(
RtcFrameQueue
*
q
)
{
g_mutex_lock
(
&
q
->
lock
);
q
->
stopping
=
TRUE
;
g_cond_broadcast
(
&
q
->
not_empty
);
g_mutex_unlock
(
&
q
->
lock
);
}
static
void
frame_queue_clear
(
RtcClient
*
client
)
{
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcH264Frame
*
frame
;
g_mutex_lock
(
&
q
->
lock
);
while
((
frame
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
rtc_h264_frame_free
(
frame
);
g_mutex_unlock
(
&
q
->
lock
);
}
static
void
frame_queue_push
(
RtcClient
*
client
,
RtcH264Frame
*
frame
)
{
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcH264Frame
*
old
;
g_mutex_lock
(
&
q
->
lock
);
if
(
q
->
stopping
)
{
g_mutex_unlock
(
&
q
->
lock
);
rtc_h264_frame_free
(
frame
);
return
;
}
while
(
g_queue_get_length
(
&
q
->
queue
)
>=
q
->
max_depth
)
{
old
=
g_queue_pop_head
(
&
q
->
queue
);
q
->
dropped_total
++
;
rtc_h264_frame_free
(
old
);
}
g_queue_push_tail
(
&
q
->
queue
,
frame
);
g_cond_signal
(
&
q
->
not_empty
);
g_mutex_unlock
(
&
q
->
lock
);
}
static
RtcH264Frame
*
frame_queue_pop
(
RtcClient
*
client
,
gint
timeout_ms
)
{
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcH264Frame
*
frame
=
NULL
;
gint64
end_us
=
g_get_monotonic_time
()
+
(
gint64
)
timeout_ms
*
1000
;
g_mutex_lock
(
&
q
->
lock
);
while
(
g_queue_is_empty
(
&
q
->
queue
)
&&
!
q
->
stopping
)
{
if
(
!
g_cond_wait_until
(
&
q
->
not_empty
,
&
q
->
lock
,
end_us
))
break
;
}
if
(
!
g_queue_is_empty
(
&
q
->
queue
))
frame
=
g_queue_pop_head
(
&
q
->
queue
);
g_mutex_unlock
(
&
q
->
lock
);
return
frame
;
}
static
void
free_pending_remote_ice
(
gpointer
data
)
{
PendingRemoteIce
*
ice
=
data
;
...
...
@@ -177,16 +316,68 @@ static void flush_local_ice(RtcClient *client)
ws_signaling_service_burst
(
1
);
}
static
gchar
*
sdp_replace_first
(
const
gchar
*
haystack
,
const
gchar
*
needle
,
const
gchar
*
replacement
)
{
const
gchar
*
pos
;
if
(
!
haystack
||
!
needle
||
!
replacement
)
return
g_strdup
(
haystack
);
pos
=
strstr
(
haystack
,
needle
);
if
(
!
pos
)
return
g_strdup
(
haystack
);
{
gsize
prefix
=
(
gsize
)(
pos
-
haystack
);
GString
*
out
=
g_string_new_len
(
haystack
,
prefix
);
g_string_append
(
out
,
replacement
);
g_string_append
(
out
,
pos
+
strlen
(
needle
));
return
g_string_free
(
out
,
FALSE
);
}
}
static
gchar
*
sdp_patch_h264_answer
(
const
gchar
*
sdp
,
const
gchar
*
sprop
)
{
gchar
*
out
;
gchar
*
needle
;
gchar
*
insert
;
if
(
!
sdp
)
return
NULL
;
out
=
g_strdup
(
sdp
);
if
(
!
sprop
||
!
sprop
[
0
]
||
strstr
(
out
,
"sprop-parameter-sets"
))
return
out
;
needle
=
g_strdup_printf
(
"profile-level-id=%s"
,
WEBRTCPUSH_H264_PROFILE_LEVEL_ID
);
insert
=
g_strdup_printf
(
"profile-level-id=%s;sprop-parameter-sets=%s"
,
WEBRTCPUSH_H264_PROFILE_LEVEL_ID
,
sprop
);
if
(
strstr
(
out
,
needle
))
{
gchar
*
replaced
=
sdp_replace_first
(
out
,
needle
,
insert
);
g_free
(
out
);
out
=
replaced
;
my_zlog_info
(
"libdatachannel: SDP answer patched with sprop-parameter-sets"
);
}
else
{
my_zlog_warn
(
"libdatachannel: SDP answer missing profile-level-id, sprop not patched"
);
}
g_free
(
needle
);
g_free
(
insert
);
return
out
;
}
static
void
RTC_API
on_local_description
(
int
pc
,
const
char
*
sdp
,
const
char
*
type
,
void
*
ptr
)
{
RtcClient
*
client
=
ptr
;
gchar
*
json
;
gchar
*
patched
=
NULL
;
const
gchar
*
sprop
;
(
void
)
pc
;
if
(
!
client
||
!
sdp
||
g_strcmp0
(
type
,
"answer"
)
!=
0
)
return
;
json
=
build_description_json
(
client
->
app
,
"answer"
,
sdp
);
sprop
=
mpp_h264_source_get_sprop
(
client
->
h264_source
);
patched
=
sdp_patch_h264_answer
(
sdp
,
sprop
);
json
=
build_description_json
(
client
->
app
,
"answer"
,
patched
?
patched
:
sdp
);
g_free
(
patched
);
if
(
!
json
)
{
my_zlog_error
(
"libdatachannel: cannot send answer without memberId"
);
return
;
...
...
@@ -238,6 +429,33 @@ static const char *rtc_state_name(rtcState state)
}
}
static
void
signal_track_ready
(
RtcClient
*
client
)
{
if
(
!
client
)
return
;
g_cond_broadcast
(
&
client
->
track_cond
);
}
static
gboolean
request_idr
(
RtcClient
*
client
,
gboolean
bypass_throttle
)
{
gint64
now
;
if
(
!
client
)
return
FALSE
;
now
=
g_get_monotonic_time
();
g_mutex_lock
(
&
client
->
lock
);
if
(
!
bypass_throttle
&&
client
->
last_idr_request_us
>
0
&&
now
-
client
->
last_idr_request_us
<
(
gint64
)
WEBRTCPUSH_PLI_IDR_THROTTLE_MS
*
1000
)
{
g_mutex_unlock
(
&
client
->
lock
);
return
FALSE
;
}
client
->
need_idr
=
TRUE
;
client
->
last_idr_request_us
=
now
;
g_mutex_unlock
(
&
client
->
lock
);
return
TRUE
;
}
static
void
RTC_API
on_state_change
(
int
pc
,
rtcState
state
,
void
*
ptr
)
{
RtcClient
*
client
=
ptr
;
...
...
@@ -247,9 +465,9 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr)
return
;
if
(
state
==
RTC_CONNECTED
)
{
g_mutex_lock
(
&
client
->
lock
);
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
request_idr
(
client
,
TRUE
);
}
}
...
...
@@ -261,11 +479,19 @@ static void RTC_API on_track_open(int track, void *ptr)
g_mutex_lock
(
&
client
->
lock
);
if
(
client
->
track
==
track
)
{
client
->
track_open
=
TRUE
;
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
}
if
(
client
->
audio_track
==
track
)
{
client
->
audio_track_open
=
TRUE
;
client
->
audio_pts_valid
=
FALSE
;
}
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_info
(
"libdatachannel: H264 track open, requesting SPS/PPS+IDR"
);
frame_queue_clear
(
client
);
request_idr
(
client
,
TRUE
);
signal_track_ready
(
client
);
my_zlog_info
(
"libdatachannel: track open (video=%d audio=%d), requesting IDR"
,
client
->
track
==
track
?
1
:
0
,
client
->
audio_track
==
track
?
1
:
0
);
}
static
void
RTC_API
on_track_closed
(
int
track
,
void
*
ptr
)
...
...
@@ -276,8 +502,14 @@ static void RTC_API on_track_closed(int track, void *ptr)
g_mutex_lock
(
&
client
->
lock
);
if
(
client
->
track
==
track
)
client
->
track_open
=
FALSE
;
if
(
client
->
audio_track
==
track
)
client
->
audio_track_open
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_warn
(
"libdatachannel: H264 track closed"
);
frame_queue_clear
(
client
);
signal_track_ready
(
client
);
my_zlog_warn
(
"libdatachannel: track closed (video=%d audio=%d)"
,
client
->
track
==
track
?
1
:
0
,
client
->
audio_track
==
track
?
1
:
0
);
}
static
void
RTC_API
on_track_error
(
int
track
,
const
char
*
error
,
void
*
ptr
)
...
...
@@ -293,14 +525,16 @@ static void RTC_API on_pli(int track, void *ptr)
int
dropped
=
0
;
if
(
!
client
)
return
;
if
(
!
request_idr
(
client
,
FALSE
))
my_zlog_debug
(
"libdatachannel: PLI throttled, skip duplicate IDR request"
);
g_mutex_lock
(
&
client
->
lock
);
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
send_lock
);
if
(
client
->
track
==
track
)
dropped
=
rtcClearPacingQueue
(
track
);
g_mutex_unlock
(
&
client
->
send_lock
);
frame_queue_clear
(
client
);
my_zlog_info
(
"libdatachannel: PLI received, dropped %d queued RTP packet(s), next is IDR"
,
dropped
>
0
?
dropped
:
0
);
}
...
...
@@ -366,11 +600,11 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
diff
=
(
cur
>
tgt
)
?
(
cur
-
tgt
)
:
(
tgt
-
cur
);
if
(
cur
<
tgt
)
{
step
=
diff
/
8
;
if
(
step
<
5
0000
)
step
=
5
0000
;
if
(
step
>
15
0000
)
step
=
15
0000
;
step
=
diff
/
4
;
if
(
step
<
10
0000
)
step
=
10
0000
;
if
(
step
>
30
0000
)
step
=
30
0000
;
next
=
cur
+
step
;
if
(
next
>
tgt
)
next
=
tgt
;
...
...
@@ -412,11 +646,16 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
current
=
client
->
target_bitrate
;
client
->
remb_ceiling
=
ceiling
;
if
(
ceiling
<
current
)
{
severe_down
=
((
guint64
)
ceiling
*
100
)
<
((
guint64
)
current
*
75
);
if
(
!
severe_down
&&
(
current
-
ceiling
)
<
WEBRTCPUSH_REMB_DOWN_MIN_STEP
)
{
g_mutex_unlock
(
&
client
->
lock
);
return
;
}
client
->
target_bitrate
=
ceiling
;
next
=
ceiling
;
severe_down
=
((
guint64
)
ceiling
*
100
)
<
((
guint64
)
current
*
75
);
if
(
severe_down
)
{
client
->
need_idr
=
TRUE
;
client
->
last_idr_request_us
=
g_get_monotonic_time
();
client
->
source_pts_valid
=
FALSE
;
}
}
...
...
@@ -425,8 +664,10 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
if
(
next
)
{
g_mutex_lock
(
&
client
->
send_lock
);
if
(
client
->
track
==
track
)
{
if
(
severe_down
)
if
(
severe_down
)
{
dropped
=
rtcClearPacingQueue
(
track
);
frame_queue_clear
(
client
);
}
rtcSetPacingBitrate
(
track
,
pacing_bitrate_for_encoder
(
next
));
}
g_mutex_unlock
(
&
client
->
send_lock
);
...
...
@@ -449,12 +690,19 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
static
void
maybe_ramp_bitrate
(
RtcClient
*
client
,
int
track
,
gint64
now_us
)
{
guint
cur
;
guint
tgt
;
gint64
interval
;
if
(
!
client
||
track
<
0
)
return
;
g_mutex_lock
(
&
client
->
lock
);
if
(
now_us
-
client
->
last_bitrate_ramp_us
<
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_MS
*
1000
)
{
cur
=
client
->
target_bitrate
;
tgt
=
client
->
remb_ceiling
;
interval
=
(
tgt
>
cur
)
?
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_UP_MS
:
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_DOWN_MS
;
if
(
now_us
-
client
->
last_bitrate_ramp_us
<
interval
*
1000
)
{
g_mutex_unlock
(
&
client
->
lock
);
return
;
}
...
...
@@ -472,78 +720,155 @@ static guint32 pts_delta_to_rtp(guint64 delta_ns)
return
(
guint32
)
ticks
;
}
static
gpointer
sender
_thread_main
(
gpointer
data
)
static
gpointer
pull
_thread_main
(
gpointer
data
)
{
RtcClient
*
client
=
data
;
const
gint64
frame_us
=
1000000
/
WEBRTCPUSH_H264_FPS
;
const
guint32
timestamp_step
=
90000
/
WEBRTCPUSH_H264_FPS
;
gint64
next_frame_us
=
0
;
const
gint
pull_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
100
;
guint
pull_fail_streak
=
0
;
guint
idr_discard_streak
=
0
;
gint64
last_pull_warn_us
=
0
;
for
(;;)
{
gboolean
stopping
;
gboolean
open
;
gboolean
track_
open
;
gboolean
force_idr
;
int
track
;
guint32
timestamp
;
const
guint8
*
frame
;
gboolean
need_idr_now
;
const
guint8
*
frame_data
;
size_t
frame_size
;
gboolean
is_idr
;
guint64
source_pts
=
G_MAXUINT64
;
gint64
now
;
guint64
source_pts
;
RtcH264Frame
*
queued
;
g_mutex_lock
(
&
client
->
lock
);
stopping
=
client
->
stopping
;
open
=
client
->
track_open
;
track
=
client
->
track
;
force_idr
=
client
->
need_idr
;
timestamp
=
client
->
rtp_timestamp
;
track_open
=
client
->
track_open
&&
client
->
track
>=
0
;
force_idr
=
track_open
&&
client
->
need_idr
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
break
;
if
(
!
open
||
track
<
0
)
{
next_frame_us
=
0
;
g_usleep
(
10000
);
continue
;
{
gint
pull_timeout
=
force_idr
?
2500
:
pull_ms
;
if
(
!
mpp_h264_source_pull
(
client
->
h264_source
,
force_idr
,
&
frame_data
,
&
frame_size
,
&
is_idr
,
&
source_pts
,
pull_timeout
))
{
gint64
now_us
=
g_get_monotonic_time
();
pull_fail_streak
++
;
if
(
now_us
-
last_pull_warn_us
>=
3000000
||
pull_fail_streak
==
1
||
pull_fail_streak
%
20
==
0
)
{
my_zlog_warn
(
"libdatachannel: live H264 frame pull timeout (streak=%u)"
,
pull_fail_streak
);
last_pull_warn_us
=
now_us
;
}
if
(
pull_fail_streak
>=
20
)
{
if
(
mpp_h264_source_recover
(
client
->
h264_source
))
pull_fail_streak
=
0
;
g_usleep
(
200000
);
}
else
{
g_usleep
(
5000
);
}
continue
;
}
pull_fail_streak
=
0
;
}
now
=
g_get_monotonic_time
();
maybe_ramp_bitrate
(
client
,
track
,
now
);
if
(
next_frame_us
==
0
||
now
-
next_frame_us
>
frame_us
*
4
)
next_frame_us
=
now
;
if
(
now
<
next_frame_us
)
{
g_usleep
((
gulong
)(
next_frame_us
-
now
));
if
(
!
track_open
)
continue
;
}
{
gint
pull_timeout
=
force_idr
?
4000
:
120
;
g_mutex_lock
(
&
client
->
lock
);
need_idr_now
=
client
->
need_idr
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
!
mpp_h264_source_pull
(
client
->
h264_source
,
force_idr
,
&
frame
,
&
frame_size
,
&
is_idr
,
&
source_pts
,
pull_timeout
))
{
my_zlog_warn
(
"libdatachannel: live H264 frame pull timeout"
);
next_frame_us
=
now
+
frame_us
/
2
;
continue
;
if
(
need_idr_now
&&
!
is_idr
)
{
idr_discard_streak
++
;
if
(
idr_discard_streak
>=
WEBRTCPUSH_H264_FPS
)
{
g_mutex_lock
(
&
client
->
lock
);
client
->
need_idr
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
idr_discard_streak
=
0
;
my_zlog_warn
(
"libdatachannel: IDR wait >2s, resume with latest frames"
);
}
continue
;
}
idr_discard_streak
=
0
;
queued
=
rtc_h264_frame_new
(
frame_data
,
frame_size
,
is_idr
,
source_pts
);
frame_queue_push
(
client
,
queued
);
}
return
NULL
;
}
static
gpointer
send_thread_main
(
gpointer
data
)
{
RtcClient
*
client
=
data
;
const
guint32
timestamp_step
=
90000
/
WEBRTCPUSH_H264_FPS
;
const
gint
pop_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
200
;
guint
send_fail_streak
=
0
;
for
(;;)
{
gboolean
stopping
;
int
track
;
guint
target_bps
;
guint
remb_bps
;
guint
queue_dropped
;
guint32
timestamp
;
RtcH264Frame
*
frame
;
gint64
now
;
g_mutex_lock
(
&
client
->
lock
);
stopping
=
client
->
stopping
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
break
;
g_mutex_lock
(
&
client
->
lock
);
while
(
!
client
->
stopping
&&
(
!
client
->
track_open
||
client
->
track
<
0
))
g_cond_wait
(
&
client
->
track_cond
,
&
client
->
lock
);
stopping
=
client
->
stopping
;
track
=
client
->
track
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
break
;
if
(
track
<
0
)
continue
;
now
=
g_get_monotonic_time
();
maybe_ramp_bitrate
(
client
,
track
,
now
);
g_mutex_lock
(
&
client
->
lock
);
if
(
client
->
need_idr
&&
!
is_idr
)
{
if
(
client
->
last_stats_log_us
==
0
||
now
-
client
->
last_stats_log_us
>=
(
gint64
)
WEBRTCPUSH_STATS_LOG_INTERVAL_MS
*
1000
)
{
target_bps
=
client
->
target_bitrate
;
remb_bps
=
client
->
remb_ceiling
;
client
->
last_stats_log_us
=
now
;
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_warn
(
"libdatachannel: discard non-IDR while waiting for resync"
);
next_frame_us
=
g_get_monotonic_time
();
queue_dropped
=
client
->
frame_queue
.
dropped_total
;
my_zlog_info
(
"libdatachannel: stats encoder=%u kbps remb_ceiling=%u kbps queue_dropped=%u"
,
target_bps
/
1000
,
remb_bps
/
1000
,
queue_dropped
);
}
else
{
g_mutex_unlock
(
&
client
->
lock
);
}
frame
=
frame_queue_pop
(
client
,
pop_ms
);
if
(
!
frame
)
{
if
(
client
->
frame_queue
.
stopping
)
break
;
continue
;
}
if
(
source_pts
!=
G_MAXUINT64
)
{
if
(
!
client
->
source_pts_valid
||
source_pts
<
client
->
last_source_pts
)
{
client
->
source_pts_base
=
source_pts
;
client
->
last_source_pts
=
source_pts
;
g_mutex_lock
(
&
client
->
lock
);
if
(
frame
->
pts_ns
!=
G_MAXUINT64
)
{
if
(
!
client
->
source_pts_valid
||
frame
->
pts_ns
<
client
->
last_source_pts
)
{
client
->
source_pts_base
=
frame
->
pts_ns
;
client
->
last_source_pts
=
frame
->
pts_ns
;
client
->
rtp_timestamp_base
=
client
->
rtp_timestamp
;
client
->
source_pts_valid
=
TRUE
;
}
timestamp
=
client
->
rtp_timestamp_base
+
pts_delta_to_rtp
(
source_pt
s
-
client
->
source_pts_base
);
client
->
last_source_pts
=
source_pt
s
;
pts_delta_to_rtp
(
frame
->
pts_n
s
-
client
->
source_pts_base
);
client
->
last_source_pts
=
frame
->
pts_n
s
;
}
else
{
timestamp
=
client
->
rtp_timestamp
;
}
...
...
@@ -552,20 +877,109 @@ static gpointer sender_thread_main(gpointer data)
g_mutex_lock
(
&
client
->
send_lock
);
if
(
!
rtcIsOpen
(
track
)
||
rtcSetTrackRtpTimestamp
(
track
,
timestamp
)
<
0
||
rtcSendMessage
(
track
,
(
const
char
*
)
frame
,
(
int
)
frame_
size
)
<
0
)
{
rtcSendMessage
(
track
,
(
const
char
*
)
frame
->
data
,
(
int
)
frame
->
size
)
<
0
)
{
g_mutex_unlock
(
&
client
->
send_lock
);
my_zlog_warn
(
"libdatachannel: rtcSendMessage failed"
);
g_usleep
(
20000
);
send_fail_streak
++
;
my_zlog_warn
(
"libdatachannel: rtcSendMessage failed (%u)"
,
send_fail_streak
);
if
(
send_fail_streak
>=
WEBRTCPUSH_SEND_FAIL_RESET
)
{
my_zlog_error
(
"libdatachannel: send failed %u times, waiting for track reopen"
,
send_fail_streak
);
g_mutex_lock
(
&
client
->
lock
);
client
->
track_open
=
FALSE
;
client
->
need_idr
=
TRUE
;
g_mutex_unlock
(
&
client
->
lock
);
signal_track_ready
(
client
);
send_fail_streak
=
0
;
}
rtc_h264_frame_free
(
frame
);
g_usleep
(
send_fail_streak
>=
10
?
200000
:
20000U
*
send_fail_streak
);
continue
;
}
g_mutex_unlock
(
&
client
->
send_lock
);
send_fail_streak
=
0
;
g_mutex_lock
(
&
client
->
lock
);
client
->
rtp_timestamp
=
timestamp
+
timestamp_step
;
if
(
is_idr
)
if
(
frame
->
is_idr
)
client
->
need_idr
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
next_frame_us
+=
frame_us
;
rtc_h264_frame_free
(
frame
);
}
return
NULL
;
}
static
gpointer
audio_send_thread_main
(
gpointer
data
)
{
RtcClient
*
client
=
data
;
/* Opus 20ms 帧 → 50fps;RTP 时间戳步进 = 48000/50 = 960 */
const
guint32
timestamp_step
=
WEBRTCPUSH_OPUS_CLOCKRATE
/
50
;
const
gint
pull_ms
=
200
;
guint
send_fail_streak
=
0
;
for
(;;)
{
gboolean
stopping
;
int
track
;
const
uint8_t
*
frame_data
;
size_t
frame_size
;
guint64
pts_ns
;
guint32
timestamp
;
g_mutex_lock
(
&
client
->
lock
);
stopping
=
client
->
stopping
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
break
;
g_mutex_lock
(
&
client
->
lock
);
while
(
!
client
->
stopping
&&
(
!
client
->
audio_track_open
||
client
->
audio_track
<
0
))
g_cond_wait
(
&
client
->
track_cond
,
&
client
->
lock
);
stopping
=
client
->
stopping
;
track
=
client
->
audio_track
;
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
break
;
if
(
track
<
0
)
continue
;
if
(
!
audio_source_pull
(
client
->
audio_source
,
&
frame_data
,
&
frame_size
,
&
pts_ns
,
pull_ms
))
continue
;
g_mutex_lock
(
&
client
->
lock
);
if
(
!
client
->
audio_pts_valid
||
pts_ns
<
client
->
audio_last_pts
)
{
client
->
audio_pts_base
=
pts_ns
;
client
->
audio_last_pts
=
pts_ns
;
client
->
audio_pts_valid
=
TRUE
;
}
timestamp
=
client
->
audio_rtp_timestamp
+
(
guint32
)((
pts_ns
-
client
->
audio_pts_base
)
*
(
guint64
)
WEBRTCPUSH_OPUS_CLOCKRATE
/
G_GUINT64_CONSTANT
(
1000000000
));
client
->
audio_last_pts
=
pts_ns
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
send_lock
);
if
(
!
rtcIsOpen
(
track
)
||
rtcSetTrackRtpTimestamp
(
track
,
timestamp
)
<
0
||
rtcSendMessage
(
track
,
(
const
char
*
)
frame_data
,
(
int
)
frame_size
)
<
0
)
{
g_mutex_unlock
(
&
client
->
send_lock
);
send_fail_streak
++
;
if
(
send_fail_streak
>=
WEBRTCPUSH_SEND_FAIL_RESET
)
{
g_mutex_lock
(
&
client
->
lock
);
client
->
audio_track_open
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
send_fail_streak
=
0
;
}
g_usleep
(
20000
);
continue
;
}
g_mutex_unlock
(
&
client
->
send_lock
);
send_fail_streak
=
0
;
g_mutex_lock
(
&
client
->
lock
);
client
->
audio_rtp_timestamp
=
timestamp
+
timestamp_step
;
g_mutex_unlock
(
&
client
->
lock
);
}
return
NULL
;
}
...
...
@@ -579,44 +993,73 @@ static void offer_video_clear(OfferVideo *video)
video
->
mline_index
=
-
1
;
}
static
gboolean
parse_video_offer
(
const
char
*
sdp
,
OfferVideo
*
video
)
static
void
offer_audio_clear
(
OfferAudio
*
audio
)
{
g_free
(
audio
->
mid
);
memset
(
audio
,
0
,
sizeof
(
*
audio
));
audio
->
payload_type
=
-
1
;
}
static
gboolean
parse_offer
(
const
char
*
sdp
,
OfferVideo
*
video
,
OfferAudio
*
audio
)
{
gchar
**
lines
;
gboolean
in_video
=
FALSE
;
gboolean
in_audio
=
FALSE
;
gint
mline
=
-
1
;
gint
i
;
memset
(
video
,
0
,
sizeof
(
*
video
));
video
->
payload_type
=
-
1
;
video
->
mline_index
=
-
1
;
memset
(
audio
,
0
,
sizeof
(
*
audio
));
audio
->
payload_type
=
-
1
;
audio
->
found
=
FALSE
;
lines
=
g_strsplit
(
sdp
,
"
\n
"
,
-
1
);
for
(
i
=
0
;
lines
[
i
];
i
++
)
{
gchar
*
line
=
g_strchomp
(
lines
[
i
]);
if
(
g_str_has_prefix
(
line
,
"m="
))
{
mline
++
;
in_video
=
g_str_has_prefix
(
line
,
"m=video "
);
in_audio
=
g_str_has_prefix
(
line
,
"m=audio "
);
if
(
in_video
)
video
->
mline_index
=
mline
;
if
(
in_audio
)
audio
->
found
=
TRUE
;
continue
;
}
if
(
!
video
->
ice_ufrag
&&
g_str_has_prefix
(
line
,
"a=ice-ufrag:"
))
video
->
ice_ufrag
=
g_strdup
(
line
+
strlen
(
"a=ice-ufrag:"
));
if
(
!
in_video
)
continue
;
if
(
g_str_has_prefix
(
line
,
"a=mid:"
))
{
g_free
(
video
->
mid
);
video
->
mid
=
g_strdup
(
line
+
strlen
(
"a=mid:"
));
}
else
if
(
g_str_has_prefix
(
line
,
"a=rtpmap:"
))
{
gint
pt
=
-
1
;
gchar
codec
[
32
]
=
{
0
};
if
(
sscanf
(
line
,
"a=rtpmap:%d %31[^/]/90000"
,
&
pt
,
codec
)
==
2
&&
!
g_ascii_strcasecmp
(
codec
,
"H264"
)
&&
video
->
payload_type
<
0
)
video
->
payload_type
=
pt
;
if
(
in_video
)
{
if
(
g_str_has_prefix
(
line
,
"a=mid:"
))
{
g_free
(
video
->
mid
);
video
->
mid
=
g_strdup
(
line
+
strlen
(
"a=mid:"
));
}
else
if
(
g_str_has_prefix
(
line
,
"a=rtpmap:"
))
{
gint
pt
=
-
1
;
gchar
codec
[
32
]
=
{
0
};
if
(
sscanf
(
line
,
"a=rtpmap:%d %31[^/]/90000"
,
&
pt
,
codec
)
==
2
&&
!
g_ascii_strcasecmp
(
codec
,
"H264"
)
&&
video
->
payload_type
<
0
)
video
->
payload_type
=
pt
;
}
}
if
(
in_audio
)
{
if
(
g_str_has_prefix
(
line
,
"a=mid:"
))
{
g_free
(
audio
->
mid
);
audio
->
mid
=
g_strdup
(
line
+
strlen
(
"a=mid:"
));
}
else
if
(
g_str_has_prefix
(
line
,
"a=rtpmap:"
))
{
gint
pt
=
-
1
;
gchar
codec
[
32
]
=
{
0
};
if
(
sscanf
(
line
,
"a=rtpmap:%d %31[^/]/48000"
,
&
pt
,
codec
)
==
2
&&
!
g_ascii_strcasecmp
(
codec
,
"opus"
)
&&
audio
->
payload_type
<
0
)
audio
->
payload_type
=
pt
;
}
}
}
g_strfreev
(
lines
);
if
(
!
video
->
mid
&&
video
->
mline_index
>=
0
)
video
->
mid
=
g_strdup_printf
(
"%d"
,
video
->
mline_index
);
if
(
audio
->
found
&&
!
audio
->
mid
)
audio
->
mid
=
g_strdup_printf
(
"%d"
,
video
->
mline_index
+
1
);
return
video
->
payload_type
>=
0
&&
video
->
mid
&&
video
->
ice_ufrag
;
}
...
...
@@ -631,13 +1074,17 @@ static void destroy_peer(RtcClient *client)
{
int
pc
;
int
track
;
int
audio_track
;
g_mutex_lock
(
&
client
->
lock
);
pc
=
client
->
pc
;
track
=
client
->
track
;
audio_track
=
client
->
audio_track
;
client
->
pc
=
-
1
;
client
->
track
=
-
1
;
client
->
track_open
=
FALSE
;
client
->
audio_track
=
-
1
;
client
->
audio_track_open
=
FALSE
;
client
->
answer_sent
=
FALSE
;
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
...
...
@@ -645,12 +1092,18 @@ static void destroy_peer(RtcClient *client)
g_clear_pointer
(
&
client
->
remote_ice_ufrag
,
g_free
);
g_clear_pointer
(
&
client
->
video_mid
,
g_free
);
g_mutex_unlock
(
&
client
->
lock
);
frame_queue_clear
(
client
);
signal_track_ready
(
client
);
g_mutex_lock
(
&
client
->
send_lock
);
if
(
track
>=
0
)
{
rtcClose
(
track
);
rtcDeleteTrack
(
track
);
}
if
(
audio_track
>=
0
)
{
rtcClose
(
audio_track
);
rtcDeleteTrack
(
audio_track
);
}
if
(
pc
>=
0
)
{
rtcClosePeerConnection
(
pc
);
rtcDeletePeerConnection
(
pc
);
...
...
@@ -677,7 +1130,9 @@ static gboolean setup_track(RtcClient *client, int pc,
track_init
.
name
=
"car-video"
;
track_init
.
msid
=
"car-stream"
;
track_init
.
trackId
=
"car-video"
;
track_init
.
profile
=
"profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1"
;
track_init
.
profile
=
"profile-level-id="
WEBRTCPUSH_H264_PROFILE_LEVEL_ID
";packetization-mode=1;level-asymmetry-allowed=1"
;
track
=
rtcAddTrackEx
(
pc
,
&
track_init
);
if
(
track
<
0
)
return
FALSE
;
...
...
@@ -694,7 +1149,7 @@ static gboolean setup_track(RtcClient *client, int pc,
packetizer
.
clockRate
=
90000
;
packetizer
.
sequenceNumber
=
(
guint16
)
g_random_int
();
packetizer
.
timestamp
=
g_random_int
();
packetizer
.
maxFragmentSize
=
1150
;
packetizer
.
maxFragmentSize
=
WEBRTCPUSH_RTP_MAX_FRAGMENT
;
packetizer
.
nalSeparator
=
RTC_NAL_SEPARATOR_START_SEQUENCE
;
if
(
rtcSetH264Packetizer
(
track
,
&
packetizer
)
<
0
||
...
...
@@ -718,6 +1173,57 @@ static gboolean setup_track(RtcClient *client, int pc,
return
TRUE
;
}
static
gboolean
setup_audio_track
(
RtcClient
*
client
,
int
pc
,
const
OfferAudio
*
audio
,
int
*
track_out
)
{
rtcTrackInit
track_init
;
rtcPacketizerInit
packetizer
;
guint32
ssrc
=
g_random_int
();
int
track
;
if
(
ssrc
==
0
)
ssrc
=
1
;
memset
(
&
track_init
,
0
,
sizeof
(
track_init
));
track_init
.
direction
=
RTC_DIRECTION_SENDONLY
;
track_init
.
codec
=
RTC_CODEC_OPUS
;
track_init
.
payloadType
=
audio
->
payload_type
;
track_init
.
ssrc
=
ssrc
;
track_init
.
mid
=
audio
->
mid
;
track_init
.
name
=
"car-audio"
;
track_init
.
msid
=
"car-stream"
;
track_init
.
trackId
=
"car-audio"
;
track
=
rtcAddTrackEx
(
pc
,
&
track_init
);
if
(
track
<
0
)
return
FALSE
;
rtcSetUserPointer
(
track
,
client
);
rtcSetOpenCallback
(
track
,
on_track_open
);
rtcSetClosedCallback
(
track
,
on_track_closed
);
rtcSetErrorCallback
(
track
,
on_track_error
);
memset
(
&
packetizer
,
0
,
sizeof
(
packetizer
));
packetizer
.
ssrc
=
ssrc
;
packetizer
.
cname
=
"car-audio"
;
packetizer
.
payloadType
=
(
guint8
)
audio
->
payload_type
;
packetizer
.
clockRate
=
WEBRTCPUSH_OPUS_CLOCKRATE
;
packetizer
.
sequenceNumber
=
(
guint16
)
g_random_int
();
packetizer
.
timestamp
=
g_random_int
();
if
(
rtcSetOpusPacketizer
(
track
,
&
packetizer
)
<
0
||
rtcChainRtcpSrReporter
(
track
)
<
0
||
rtcChainRtcpNackResponder
(
track
,
WEBRTCPUSH_NACK_PACKETS
)
<
0
)
{
rtcDeleteTrack
(
track
);
return
FALSE
;
}
g_mutex_lock
(
&
client
->
lock
);
client
->
audio_rtp_timestamp
=
packetizer
.
timestamp
;
client
->
audio_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
*
track_out
=
track
;
return
TRUE
;
}
static
void
flush_remote_ice
(
RtcClient
*
client
)
{
for
(;;)
{
...
...
@@ -757,6 +1263,7 @@ int rtc_client_start(AppState *app)
client
->
pending_remote_ice
=
g_queue_new
();
g_mutex_init
(
&
client
->
lock
);
g_mutex_init
(
&
client
->
send_lock
);
g_cond_init
(
&
client
->
track_cond
);
client
->
h264_source
=
mpp_h264_source_start
(
video_device
,
&
error_message
);
if
(
!
client
->
h264_source
)
{
...
...
@@ -767,17 +1274,34 @@ int rtc_client_start(AppState *app)
g_queue_free
(
client
->
pending_remote_ice
);
g_mutex_clear
(
&
client
->
send_lock
);
g_mutex_clear
(
&
client
->
lock
);
g_cond_clear
(
&
client
->
track_cond
);
g_free
(
client
);
return
-
1
;
}
app
->
rtc_client
=
client
;
client
->
sender_thread
=
g_thread_new
(
"rtc-h264-send"
,
sender_thread_main
,
client
);
frame_queue_init
(
&
client
->
frame_queue
,
WEBRTCPUSH_SEND_QUEUE_DEPTH
);
client
->
pull_thread
=
g_thread_new
(
"rtc-h264-pull"
,
pull_thread_main
,
client
);
client
->
send_thread
=
g_thread_new
(
"rtc-h264-send"
,
send_thread_main
,
client
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
client
->
target_bitrate
);
my_zlog_info
(
"libdatachannel: MPP live source started device=%s mpp=%d fps=%d"
,
/* 音频源(可选;app->enable_audio 由 setup_alsa_device 探测决定) */
if
(
app
->
enable_audio
&&
app
->
audio_alsa_device
&&
app
->
audio_alsa_device
[
0
])
{
char
*
audio_err
=
NULL
;
client
->
audio_source
=
audio_source_start
(
app
->
audio_alsa_device
,
&
audio_err
);
if
(
client
->
audio_source
)
{
client
->
audio_send_thread
=
g_thread_new
(
"rtc-opus-send"
,
audio_send_thread_main
,
client
);
my_zlog_info
(
"libdatachannel: audio source started device=%s"
,
app
->
audio_alsa_device
);
}
else
{
my_zlog_warn
(
"libdatachannel: audio source start failed: %s"
,
audio_err
?
audio_err
:
"?"
);
g_free
(
audio_err
);
}
}
my_zlog_info
(
"libdatachannel: MPP live source started device=%s mpp=%d fps=%d threads=pull+send queue=%u"
,
video_device
,
mpp_h264_source_is_mpp_encoder
(
client
->
h264_source
)
?
1
:
0
,
WEBRTCPUSH_H264_FPS
);
WEBRTCPUSH_H264_FPS
,
WEBRTCPUSH_SEND_QUEUE_DEPTH
);
return
0
;
}
...
...
@@ -797,9 +1321,17 @@ void rtc_client_stop(AppState *app)
destroy_peer
(
client
);
g_mutex_lock
(
&
client
->
lock
);
client
->
stopping
=
TRUE
;
g_cond_broadcast
(
&
client
->
track_cond
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
client
->
sender_thread
)
g_thread_join
(
client
->
sender_thread
);
frame_queue_stop
(
&
client
->
frame_queue
);
if
(
client
->
pull_thread
)
g_thread_join
(
client
->
pull_thread
);
if
(
client
->
send_thread
)
g_thread_join
(
client
->
send_thread
);
if
(
client
->
audio_send_thread
)
g_thread_join
(
client
->
audio_send_thread
);
if
(
client
->
audio_source
)
audio_source_stop
(
client
->
audio_source
);
g_queue_free_full
(
client
->
pending_remote_ice
,
free_pending_remote_ice
);
g_mutex_lock
(
&
client
->
lock
);
...
...
@@ -807,6 +1339,8 @@ void rtc_client_stop(AppState *app)
g_mutex_unlock
(
&
client
->
lock
);
g_queue_free
(
client
->
pending_local_ice
);
mpp_h264_source_stop
(
client
->
h264_source
);
frame_queue_fini
(
&
client
->
frame_queue
);
g_cond_clear
(
&
client
->
track_cond
);
g_mutex_clear
(
&
client
->
send_lock
);
g_mutex_clear
(
&
client
->
lock
);
app
->
rtc_client
=
NULL
;
...
...
@@ -817,16 +1351,19 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
{
RtcClient
*
client
=
client_from_app
(
app
);
OfferVideo
video
;
OfferAudio
audio
;
rtcConfiguration
config
;
int
pc
;
int
track
=
-
1
;
int
audio_track
=
-
1
;
gboolean
duplicate
;
if
(
!
client
||
!
sdp
)
return
-
1
;
if
(
!
parse_
video_offer
(
sdp
,
&
vide
o
))
{
if
(
!
parse_
offer
(
sdp
,
&
video
,
&
audi
o
))
{
my_zlog_error
(
"libdatachannel: phone offer has no usable H264 video"
);
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
return
-
1
;
}
...
...
@@ -836,6 +1373,7 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
g_mutex_unlock
(
&
client
->
lock
);
if
(
duplicate
)
{
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
return
0
;
}
...
...
@@ -844,7 +1382,7 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
config
.
iceServers
=
s_ice_servers
;
config
.
iceServersCount
=
(
int
)
G_N_ELEMENTS
(
s_ice_servers
);
config
.
certificateType
=
RTC_CERTIFICATE_ECDSA
;
config
.
forceMediaTransport
=
TRUE
;
config
.
forceMediaTransport
=
TRUE
;
/* 预分配 SRTP 媒体通道,与 ENABLE_DATACHANNEL=0 一致 */
config
.
mtu
=
1200
;
pc
=
rtcCreatePeerConnection
(
&
config
);
if
(
pc
<
0
)
{
...
...
@@ -859,20 +1397,32 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
if
(
!
setup_track
(
client
,
pc
,
&
video
,
&
track
))
{
rtcDeletePeerConnection
(
pc
);
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
my_zlog_error
(
"libdatachannel: failed to create H264 track/handlers"
);
return
-
1
;
}
/* 若手机 offer 包含 audio m-line 且设备端音频源已启动,建 Opus track */
if
(
audio
.
found
&&
audio
.
payload_type
>=
0
&&
client
->
audio_source
)
{
if
(
setup_audio_track
(
client
,
pc
,
&
audio
,
&
audio_track
))
{
my_zlog_info
(
"libdatachannel: Opus audio track created mid=%s pt=%d"
,
audio
.
mid
,
audio
.
payload_type
);
}
else
{
my_zlog_warn
(
"libdatachannel: failed to create Opus audio track"
);
}
}
g_mutex_lock
(
&
client
->
lock
);
client
->
pc
=
pc
;
client
->
track
=
track
;
client
->
audio_track
=
audio_track
;
client
->
video_mline_index
=
video
.
mline_index
;
client
->
video_mid
=
g_strdup
(
video
.
mid
);
client
->
remote_ice_ufrag
=
g_strdup
(
video
.
ice_ufrag
);
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
last_bitrate_ramp_us
=
g_get_monotonic_time
();
client
->
need_idr
=
TRUE
;
request_idr
(
client
,
TRUE
)
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
...
...
@@ -881,11 +1431,13 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
if
(
rtcSetRemoteDescription
(
pc
,
sdp
,
"offer"
)
<
0
)
{
my_zlog_error
(
"libdatachannel: rtcSetRemoteDescription failed"
);
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
destroy_peer
(
client
);
return
-
1
;
}
flush_remote_ice
(
client
);
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
return
0
;
}
...
...
modules/webrtcpush/webrtcpush_config.h
View file @
7f0fd331
...
...
@@ -14,18 +14,43 @@
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_MIN_FPS 22
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS
* 3)
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS
)
/* 1s 一个 IDR */
/* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */
#define WEBRTCPUSH_INITIAL_BITRATE 1
400000U
/* 起播 1.4 Mbps */
#define WEBRTCPUSH_INITIAL_BITRATE 1
800000U
#define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MAX_BITRATE 3200000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 82U
/* 链路容量扣除音频/RTP 余量 */
#define WEBRTCPUSH_BITRATE_RAMP_MS 1000U
/* 每秒向目标码率逼近一档 */
#define WEBRTCPUSH_REMB_UTIL_PERCENT 85U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 50000U
/* 非急跌时 <50kbps 不调编码器 */
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 500U
/* 升码更快恢复画质 */
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 1000U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 108U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 120U
#define WEBRTCPUSH_PACING_MAX_BITRATE 3750000U
#define WEBRTCPUSH_NACK_PACKETS 1024U
/* RTP 分片与 NACK(MTU=1200,留 SRTP/DTLS/FU 余量) */
#define WEBRTCPUSH_RTP_MAX_FRAGMENT 1050U
#define WEBRTCPUSH_NACK_PACKETS 512U
/* PLI/IDR 节流:避免频繁 force-key-unit 拉高瞬时码率 */
#define WEBRTCPUSH_PLI_IDR_THROTTLE_MS 200U
/* 周期性推流指标日志间隔 */
#define WEBRTCPUSH_STATS_LOG_INTERVAL_MS 8000U
/* 连续发送失败退避与上限 */
#define WEBRTCPUSH_SEND_FAIL_RESET 30U
/* H.264 Baseline@L3.1,兼容性最好 */
#define WEBRTCPUSH_H264_PROFILE_LEVEL_ID "42e01f"
/* 拉帧线程与发送线程之间的有界队列(leaky,满则丢最旧帧) */
#define WEBRTCPUSH_SEND_QUEUE_DEPTH 4U
/* 管道缓冲:运动场景需适度缓存,过低易 pull timeout / 糊屏 */
#define WEBRTCPUSH_MPP_PRE_ENC_BUFFERS 3
/* 编码前 leaky */
#define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 2
/* 编码后保留 AU */
#define WEBRTCPUSH_APPSINK_MAX_BUFFERS 2
/* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
#define WEBRTCPUSH_VIDEO_DEVICE "auto"
...
...
@@ -36,6 +61,12 @@
*/
#define WEBRTCPUSH_ALSA_DEVICE "auto"
/* Opus 音频编码参数(上行麦克风推流) */
#define WEBRTCPUSH_OPUS_BITRATE 64000U
#define WEBRTCPUSH_OPUS_CLOCKRATE 48000U
#define WEBRTCPUSH_OPUS_CHANNELS 1
#define WEBRTCPUSH_AUDIO_QUEUE_DEPTH 8U
/*
* 设备侧 DataChannel(myDataChannel)与手机 createDataChannel('init') 争用 SCTP,
* 易触发 sctpenc association error 并导致管道闪断/进程崩溃。仅推流可不建。
...
...
modules/webrtcpush/webrtcpush_run.c
View file @
7f0fd331
...
...
@@ -172,7 +172,7 @@ int webrtcpush_run_blocking(void)
s_app
.
prefer_mpp
=
TRUE
;
s_app
.
device_offerer
=
FALSE
;
s_app
.
verbose
=
WEBRTCPUSH_DEBUG
?
TRUE
:
FALSE
;
s
_app
.
enable_audio
=
FALSE
;
/* 视频先行;音频后续 ALSA→Opus track
*/
s
etup_alsa_device
(
&
s_app
);
/* enable_audio 由 ALSA 设备探测结果决定
*/
s_app
.
loop
=
g_main_loop_new
(
NULL
,
FALSE
);
...
...
modules/wifi_config/wifi_config_agent.c
0 → 100644
View file @
7f0fd331
#include "wifi_config_agent.h"
#include "wifi_config_config.h"
#include "stream_debug_agent.h"
#include "device_identity.h"
#include "device_fileopen.h"
#include "device_wifi_manager.h"
#include "common.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static
int
s_wifi_config_sock
=
-
1
;
static
pthread_t
s_wifi_config_thread
;
static
volatile
int
s_wifi_config_running
=
0
;
static
pthread_mutex_t
s_wifi_config_sock_mutex
=
PTHREAD_MUTEX_INITIALIZER
;
static
int
wifi_config_bind_socket
(
void
)
{
int
reuse
=
1
;
struct
sockaddr_in
addr
;
if
(
s_wifi_config_sock
>=
0
)
{
close
(
s_wifi_config_sock
);
s_wifi_config_sock
=
-
1
;
}
s_wifi_config_sock
=
socket
(
AF_INET
,
SOCK_DGRAM
,
0
);
if
(
s_wifi_config_sock
<
0
)
{
my_zlog_error
(
"WiFi配置助手: 创建 socket 失败: %s"
,
strerror
(
errno
));
return
-
1
;
}
if
(
setsockopt
(
s_wifi_config_sock
,
SOL_SOCKET
,
SO_REUSEADDR
,
&
reuse
,
sizeof
(
reuse
))
<
0
)
{
my_zlog_warn
(
"WiFi配置助手: SO_REUSEADDR 设置失败"
);
}
memset
(
&
addr
,
0
,
sizeof
(
addr
));
addr
.
sin_family
=
AF_INET
;
addr
.
sin_addr
.
s_addr
=
htonl
(
INADDR_ANY
);
addr
.
sin_port
=
htons
(
WIFI_CONFIG_UDP_PORT
);
if
(
bind
(
s_wifi_config_sock
,
(
struct
sockaddr
*
)
&
addr
,
sizeof
(
addr
))
<
0
)
{
my_zlog_error
(
"WiFi配置助手: 绑定端口 %d 失败: %s"
,
WIFI_CONFIG_UDP_PORT
,
strerror
(
errno
));
close
(
s_wifi_config_sock
);
s_wifi_config_sock
=
-
1
;
return
-
1
;
}
return
0
;
}
void
wifi_config_agent_rebind_socket
(
void
)
{
pthread_mutex_lock
(
&
s_wifi_config_sock_mutex
);
if
(
wifi_config_bind_socket
()
==
0
)
{
my_zlog_info
(
"WiFi配置助手: UDP %d 已重新绑定"
,
WIFI_CONFIG_UDP_PORT
);
}
pthread_mutex_unlock
(
&
s_wifi_config_sock_mutex
);
}
static
int
is_stream_debug_cmd
(
const
char
*
cmd
)
{
return
cmd
&&
(
strcmp
(
cmd
,
"stream_mode_query"
)
==
0
||
strcmp
(
cmd
,
"stream_url_update"
)
==
0
||
strcmp
(
cmd
,
"stream_url_query"
)
==
0
);
}
static
int
is_command_available
(
const
char
*
cmd
)
{
char
check_cmd
[
256
];
snprintf
(
check_cmd
,
sizeof
(
check_cmd
),
"which %s > /dev/null 2>&1"
,
cmd
);
return
system
(
check_cmd
)
==
0
;
}
static
int
ufw_is_active
(
void
)
{
FILE
*
fp
=
popen
(
"ufw status 2>/dev/null"
,
"r"
);
char
line
[
256
];
if
(
!
fp
)
return
0
;
while
(
fgets
(
line
,
sizeof
(
line
),
fp
))
{
if
(
strstr
(
line
,
"Status: active"
)
!=
NULL
)
{
pclose
(
fp
);
return
1
;
}
}
pclose
(
fp
);
return
0
;
}
static
int
ufw_has_udp_port_rule
(
int
port
)
{
char
cmd
[
160
];
snprintf
(
cmd
,
sizeof
(
cmd
),
"ufw status 2>/dev/null | grep -q '%d/udp'"
,
port
);
return
system
(
cmd
)
==
0
;
}
/* ufw 已启用且未放行时自动执行 ufw allow;无 ufw 或未启用则跳过 */
static
void
ensure_firewall_udp_port
(
int
port
)
{
char
allow_cmd
[
128
];
int
ret
;
if
(
!
is_command_available
(
"ufw"
))
{
my_zlog_debug
(
"WiFi配置助手: 未安装 ufw,跳过防火墙检查"
);
return
;
}
if
(
!
ufw_is_active
())
{
my_zlog_debug
(
"WiFi配置助手: ufw 未启用,跳过防火墙放行"
);
return
;
}
if
(
ufw_has_udp_port_rule
(
port
))
{
my_zlog_debug
(
"WiFi配置助手: UDP %d 已在 ufw 中放行"
,
port
);
return
;
}
snprintf
(
allow_cmd
,
sizeof
(
allow_cmd
),
"ufw allow %d/udp >/dev/null 2>&1"
,
port
);
my_zlog_info
(
"WiFi配置助手: UDP %d 未放行,正在执行 ufw allow ..."
,
port
);
ret
=
system
(
allow_cmd
);
if
(
ret
==
0
&&
ufw_has_udp_port_rule
(
port
))
{
my_zlog_info
(
"WiFi配置助手: UDP %d 放行成功"
,
port
);
return
;
}
my_zlog_warn
(
"WiFi配置助手: UDP %d 自动放行失败,App 可能无法发现设备"
,
port
);
}
typedef
struct
{
const
char
*
code
;
const
char
*
name
;
}
wifi_device_type_entry_t
;
static
const
wifi_device_type_entry_t
s_device_type_table
[]
=
{
{
"0101"
,
"大闪电"
},
{
"0102"
,
"小闪电"
},
{
"0103"
,
"挖掘机"
},
{
"0104"
,
"推土机"
},
{
"0105"
,
"人坐超大车"
},
{
"0106"
,
"F1赛车"
},
{
"0107"
,
"大众POLO R"
},
{
"0202"
,
"坦克"
},
{
"0203"
,
"M1A2坦克"
},
{
"0204"
,
"美卡隆坦克"
},
{
"0206"
,
"水坦克"
},
{
"0207"
,
"射水弹坦克"
},
{
"0301"
,
"34号船"
},
{
"0302"
,
"ship0302船"
},
{
"0401"
,
"云台"
},
{
"0403"
,
"定位设备"
},
{
"0404"
,
"云台"
},
{
"0405"
,
"云台0405"
},
{
"0406"
,
"水枪炮台"
},
{
"0501"
,
"机器狗"
},
{
"0502"
,
"机械臂"
},
};
static
const
char
*
device_type_name_from_code
(
const
char
*
type_code
)
{
size_t
i
;
if
(
!
type_code
||
type_code
[
0
]
==
'\0'
)
return
"香橙派"
;
for
(
i
=
0
;
i
<
sizeof
(
s_device_type_table
)
/
sizeof
(
s_device_type_table
[
0
]);
i
++
)
{
if
(
strcmp
(
type_code
,
s_device_type_table
[
i
].
code
)
==
0
)
return
s_device_type_table
[
i
].
name
;
}
return
"香橙派"
;
}
static
void
normalize_device_no
(
char
*
device_no
,
size_t
size
)
{
size_t
len
;
if
(
!
device_no
||
size
==
0
)
return
;
device_no
[
size
-
1
]
=
'\0'
;
len
=
strcspn
(
device_no
,
"
\r\n
"
);
device_no
[
len
]
=
'\0'
;
}
static
const
char
*
get_pure_device_no
(
void
)
{
const
char
*
device_no
=
mqtt_topic_pure_number
();
if
(
device_no
&&
device_no
[
0
]
!=
'\0'
&&
strcmp
(
device_no
,
"00000000000000"
)
!=
0
)
return
device_no
;
device_no
=
device_id_function
();
if
(
device_no
&&
device_no
[
0
]
!=
'\0'
)
return
device_no
;
return
NULL
;
}
static
int
parse_device_no_parts
(
const
char
*
device_no
,
char
*
first2
,
size_t
first2_sz
,
char
*
type_code
,
size_t
type_code_sz
,
char
*
last4
,
size_t
last4_sz
)
{
char
normalized
[
WIFI_CONFIG_DEVICE_NO_LEN
+
1
];
if
(
!
device_no
||
!
first2
||
!
type_code
||
!
last4
)
return
-
1
;
snprintf
(
normalized
,
sizeof
(
normalized
),
"%s"
,
device_no
);
normalize_device_no
(
normalized
,
sizeof
(
normalized
));
if
(
strlen
(
normalized
)
<
WIFI_CONFIG_DEVICE_NO_LEN
)
return
-
1
;
snprintf
(
first2
,
first2_sz
,
"%.2s"
,
normalized
);
snprintf
(
type_code
,
type_code_sz
,
"%.4s"
,
normalized
+
2
);
snprintf
(
last4
,
last4_sz
,
"%.4s"
,
normalized
+
10
);
return
0
;
}
static
void
generate_device_id
(
char
*
buf
,
size_t
size
)
{
const
char
*
device_no
=
get_pure_device_no
();
if
(
!
buf
||
size
==
0
)
return
;
if
(
device_no
)
{
snprintf
(
buf
,
size
,
"%s"
,
device_no
);
normalize_device_no
(
buf
,
size
);
return
;
}
snprintf
(
buf
,
size
,
"00000000000000"
);
}
static
void
generate_device_name
(
char
*
buf
,
size_t
size
)
{
const
char
*
device_no
=
get_pure_device_no
();
char
first2
[
3
];
char
type_code
[
5
];
char
last4
[
5
];
const
char
*
type_name
;
if
(
!
buf
||
size
==
0
)
return
;
if
(
!
device_no
||
parse_device_no_parts
(
device_no
,
first2
,
sizeof
(
first2
),
type_code
,
sizeof
(
type_code
),
last4
,
sizeof
(
last4
))
!=
0
)
{
snprintf
(
buf
,
size
,
"香橙派"
);
return
;
}
type_name
=
device_type_name_from_code
(
type_code
);
snprintf
(
buf
,
size
,
"%s%s-%s"
,
type_name
,
first2
,
last4
);
}
static
int
send_json
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
const
char
*
json
)
{
ssize_t
n
;
if
(
!
json
||
!
addr
||
sock
<
0
)
return
-
1
;
n
=
sendto
(
sock
,
json
,
strlen
(
json
),
0
,
(
const
struct
sockaddr
*
)
addr
,
sizeof
(
*
addr
));
return
n
>=
0
?
0
:
-
1
;
}
static
void
send_config_result
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
int
success
,
const
char
*
message
)
{
cJSON
*
root
=
cJSON_CreateObject
();
if
(
!
root
)
return
;
cJSON_AddStringToObject
(
root
,
"cmd"
,
"config_result"
);
cJSON_AddBoolToObject
(
root
,
"success"
,
success
?
1
:
0
);
if
(
message
&&
message
[
0
])
cJSON_AddStringToObject
(
root
,
"message"
,
message
);
char
*
payload
=
cJSON_PrintUnformatted
(
root
);
if
(
payload
)
{
send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
root
);
}
static
void
handle_discover
(
int
sock
,
const
struct
sockaddr_in
*
addr
)
{
char
device_id
[
WIFI_CONFIG_DEVICE_ID_MAX
];
char
device_name
[
WIFI_CONFIG_DEVICE_NAME_MAX
];
cJSON
*
root
;
generate_device_id
(
device_id
,
sizeof
(
device_id
));
generate_device_name
(
device_name
,
sizeof
(
device_name
));
root
=
cJSON_CreateObject
();
if
(
!
root
)
return
;
cJSON_AddStringToObject
(
root
,
"cmd"
,
"discover_response"
);
cJSON_AddStringToObject
(
root
,
"deviceId"
,
device_id
);
cJSON_AddStringToObject
(
root
,
"name"
,
device_name
);
char
*
payload
=
cJSON_PrintUnformatted
(
root
);
if
(
payload
)
{
send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
root
);
}
static
void
handle_config
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
)
{
cJSON
*
ssid_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"ssid"
);
cJSON
*
password_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"password"
);
cJSON
*
action_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"action"
);
const
char
*
ssid
=
NULL
;
const
char
*
password
=
""
;
const
char
*
action
=
"connect"
;
char
message
[
WIFI_CONFIG_MESSAGE_MAX
];
int
success
;
if
(
!
cJSON_IsString
(
ssid_item
)
||
!
ssid_item
->
valuestring
||
!
ssid_item
->
valuestring
[
0
])
{
send_config_result
(
sock
,
addr
,
0
,
"缺少有效的 ssid 字段"
);
return
;
}
ssid
=
ssid_item
->
valuestring
;
if
(
cJSON_IsString
(
password_item
)
&&
password_item
->
valuestring
)
password
=
password_item
->
valuestring
;
if
(
cJSON_IsString
(
action_item
)
&&
action_item
->
valuestring
&&
action_item
->
valuestring
[
0
])
action
=
action_item
->
valuestring
;
my_zlog_info
(
"WiFi配置助手: 收到配网请求 SSID=%s action=%s"
,
ssid
,
action
);
if
(
strcmp
(
action
,
"save"
)
==
0
)
{
success
=
orange_pi_save_wifi
(
ssid
,
password
)
==
0
;
snprintf
(
message
,
sizeof
(
message
),
"%s"
,
success
?
"WiFi 配置已保存"
:
"WiFi 保存失败"
);
}
else
{
success
=
orange_pi_connect_wifi
(
ssid
,
password
)
==
0
;
snprintf
(
message
,
sizeof
(
message
),
"%s"
,
success
?
"WiFi 配置已保存并连接成功"
:
"WiFi 连接失败"
);
}
my_zlog_info
(
"WiFi配置助手: 配网结果 success=%d, message=%s"
,
success
,
message
);
send_config_result
(
sock
,
addr
,
success
,
message
);
}
static
void
process_message
(
int
sock
,
const
char
*
buf
,
const
struct
sockaddr_in
*
addr
)
{
cJSON
*
root
=
cJSON_Parse
(
buf
);
cJSON
*
cmd_item
;
if
(
!
root
)
{
my_zlog_warn
(
"WiFi配置助手: JSON 解析失败"
);
return
;
}
cmd_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"cmd"
);
if
(
!
cJSON_IsString
(
cmd_item
)
||
!
cmd_item
->
valuestring
)
{
cJSON_Delete
(
root
);
return
;
}
if
(
is_stream_debug_cmd
(
cmd_item
->
valuestring
))
{
wifi_config_agent_rebind_socket
();
}
if
(
strcmp
(
cmd_item
->
valuestring
,
"discover"
)
==
0
)
{
handle_discover
(
sock
,
addr
);
}
else
if
(
strcmp
(
cmd_item
->
valuestring
,
"config"
)
==
0
)
{
handle_config
(
sock
,
addr
,
root
);
}
else
if
(
strcmp
(
cmd_item
->
valuestring
,
"stream_mode_query"
)
==
0
)
{
stream_debug_handle_mode_query
(
sock
,
addr
);
}
else
if
(
strcmp
(
cmd_item
->
valuestring
,
"stream_url_update"
)
==
0
)
{
stream_debug_handle_url_update
(
sock
,
addr
,
root
);
}
else
if
(
strcmp
(
cmd_item
->
valuestring
,
"stream_url_query"
)
==
0
)
{
stream_debug_handle_url_query
(
sock
,
addr
);
}
cJSON_Delete
(
root
);
}
static
void
*
wifi_config_agent_thread
(
void
*
arg
)
{
char
buf
[
WIFI_CONFIG_RECV_BUF_SIZE
];
char
device_id
[
WIFI_CONFIG_DEVICE_ID_MAX
];
char
device_name
[
WIFI_CONFIG_DEVICE_NAME_MAX
];
char
first2
[
3
];
char
type_code
[
5
];
char
last4
[
5
];
const
char
*
type_name
;
(
void
)
arg
;
generate_device_id
(
device_id
,
sizeof
(
device_id
));
generate_device_name
(
device_name
,
sizeof
(
device_name
));
if
(
parse_device_no_parts
(
get_pure_device_no
(),
first2
,
sizeof
(
first2
),
type_code
,
sizeof
(
type_code
),
last4
,
sizeof
(
last4
))
==
0
)
{
type_name
=
device_type_name_from_code
(
type_code
);
my_zlog_info
(
"WiFi配置助手对接服务已启动 (UDP)"
);
my_zlog_info
(
" 设备号: %s"
,
device_id
);
my_zlog_info
(
" 类型: %s (编码 %s)"
,
type_name
,
type_code
);
my_zlog_info
(
" 短码: %s%s"
,
first2
,
last4
);
my_zlog_info
(
" 显示名称: %s"
,
device_name
);
}
else
{
my_zlog_info
(
"WiFi配置助手对接服务已启动 (UDP)"
);
my_zlog_info
(
" 设备号: %s"
,
device_id
);
my_zlog_info
(
" 显示名称: %s"
,
device_name
);
}
my_zlog_info
(
" 监听: 0.0.0.0:%d/udp"
,
WIFI_CONFIG_UDP_PORT
);
my_zlog_info
(
"等待 App 连接..."
);
while
(
s_wifi_config_running
)
{
struct
sockaddr_in
client
;
socklen_t
client_len
=
sizeof
(
client
);
ssize_t
n
=
recvfrom
(
s_wifi_config_sock
,
buf
,
sizeof
(
buf
)
-
1
,
0
,
(
struct
sockaddr
*
)
&
client
,
&
client_len
);
if
(
n
<
0
)
{
if
(
!
s_wifi_config_running
)
break
;
if
(
errno
==
EINTR
)
continue
;
my_zlog_error
(
"WiFi配置助手: recvfrom 失败: %s"
,
strerror
(
errno
));
continue
;
}
buf
[
n
]
=
'\0'
;
process_message
(
s_wifi_config_sock
,
buf
,
&
client
);
}
return
NULL
;
}
int
wifi_config_agent_start
(
void
)
{
if
(
s_wifi_config_running
)
return
0
;
ensure_firewall_udp_port
(
WIFI_CONFIG_UDP_PORT
);
pthread_mutex_lock
(
&
s_wifi_config_sock_mutex
);
if
(
wifi_config_bind_socket
()
!=
0
)
{
pthread_mutex_unlock
(
&
s_wifi_config_sock_mutex
);
return
-
1
;
}
pthread_mutex_unlock
(
&
s_wifi_config_sock_mutex
);
s_wifi_config_running
=
1
;
if
(
pthread_create
(
&
s_wifi_config_thread
,
NULL
,
wifi_config_agent_thread
,
NULL
)
!=
0
)
{
my_zlog_error
(
"WiFi配置助手: 创建线程失败"
);
s_wifi_config_running
=
0
;
pthread_mutex_lock
(
&
s_wifi_config_sock_mutex
);
close
(
s_wifi_config_sock
);
s_wifi_config_sock
=
-
1
;
pthread_mutex_unlock
(
&
s_wifi_config_sock_mutex
);
return
-
1
;
}
return
0
;
}
void
wifi_config_agent_stop
(
void
)
{
if
(
!
s_wifi_config_running
)
return
;
s_wifi_config_running
=
0
;
if
(
s_wifi_config_sock
>=
0
)
{
shutdown
(
s_wifi_config_sock
,
SHUT_RDWR
);
close
(
s_wifi_config_sock
);
s_wifi_config_sock
=
-
1
;
}
pthread_join
(
s_wifi_config_thread
,
NULL
);
}
modules/wifi_config/wifi_config_agent.h
0 → 100644
View file @
7f0fd331
#ifndef WIFI_CONFIG_AGENT_H__
#define WIFI_CONFIG_AGENT_H__
int
wifi_config_agent_start
(
void
);
void
wifi_config_agent_stop
(
void
);
void
wifi_config_agent_rebind_socket
(
void
);
#endif
modules/wifi_config/wifi_config_config.h
0 → 100644
View file @
7f0fd331
#ifndef WIFI_CONFIG_CONFIG_H__
#define WIFI_CONFIG_CONFIG_H__
#define WIFI_CONFIG_UDP_PORT 18888
#define WIFI_CONFIG_RECV_BUF_SIZE 2048
#define WIFI_CONFIG_DEVICE_ID_MAX 32
#define WIFI_CONFIG_DEVICE_NAME_MAX 64
#define WIFI_CONFIG_MESSAGE_MAX 256
#define WIFI_CONFIG_DEVICE_NO_LEN 14
#endif
zlog.conf
View file @
7f0fd331
...
...
@@ -9,4 +9,4 @@ file perms = 600
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
[
rules
]
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-0
1
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-0
2
.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