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
055aa314
Commit
055aa314
authored
Jul 11, 2026
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
c语言推流基本完成
parent
3585cc8c
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
230 additions
and
603 deletions
+230
-603
main
build/main
+0
-0
car0107_control.c
drivers/devicecontrol/car/car0107_control.c
+97
-0
car0107_control.h
drivers/devicecontrol/car/car0107_control.h
+3
-0
car0107_idle.c
drivers/devicecontrol/car/car0107_idle.c
+5
-26
audioplay.c.bak.20260710_111731
drivers/sensors/audioplay.c.bak.20260710_111731
+0
-550
mpp_h264_source.c
modules/webrtcpush/mpp_h264_source.c
+37
-1
rtc_client.c
modules/webrtcpush/rtc_client.c
+68
-16
webrtcpush_config.h
modules/webrtcpush/webrtcpush_config.h
+19
-9
zlog.conf
zlog.conf
+1
-1
No files found.
build/main
View file @
055aa314
No preview for this file type
drivers/devicecontrol/car/car0107_control.c
View file @
055aa314
...
@@ -28,6 +28,14 @@
...
@@ -28,6 +28,14 @@
#define CAR0107_BACK_US_RUN_MIN 1440
/* val=53 起步即动(须低于死区 1450) */
#define CAR0107_BACK_US_RUN_MIN 1440
/* val=53 起步即动(须低于死区 1450) */
#define CAR0107_BACK_US_RUN_MAX 1300
/* 后退最大速度再降20%:val=200 -> 1300us */
#define CAR0107_BACK_US_RUN_MAX 1300
/* 后退最大速度再降20%:val=200 -> 1300us */
/* idle 唤醒动作:5分钟无控制后 低速后退 -> 低速前进 -> 前进刹车停止 */
#define CAR0107_IDLE_REVERSE_US CAR0107_BACK_US_RUN_MIN
/* 1440us 刚过死区,最低速后退 */
#define CAR0107_IDLE_FORWARD_US CAR0107_FWD_US_RUN_MIN
/* 1560us 最低前进行走脉宽 */
#define CAR0107_IDLE_REVERSE_MS 600
/* 后退保持时长 */
#define CAR0107_IDLE_NEUTRAL_MS 150
/* 后退切前进前中位稳定时长 */
#define CAR0107_IDLE_FORWARD_MS 600
/* 前进保持时长 */
#define CAR0107_IDLE_BRAKE_WAIT_MS 250
/* 等待刹车保持+中位解锁收尾到 NEUTRAL */
typedef
enum
{
typedef
enum
{
CAR0107_ESC_DIR_FORWARD
=
1
,
/* App mode1 前进:高脉宽(与 0101 相反) */
CAR0107_ESC_DIR_FORWARD
=
1
,
/* App mode1 前进:高脉宽(与 0101 相反) */
CAR0107_ESC_DIR_BACK
=
2
,
/* App mode2 后退:低脉宽(与 0101 相反) */
CAR0107_ESC_DIR_BACK
=
2
,
/* App mode2 后退:低脉宽(与 0101 相反) */
...
@@ -1392,3 +1400,92 @@ void car0107_esc_thread_close(void)
...
@@ -1392,3 +1400,92 @@ void car0107_esc_thread_close(void)
pthread_mutex_unlock
(
&
s_esc_start_mutex
);
pthread_mutex_unlock
(
&
s_esc_start_mutex
);
my_zlog_info
(
"car0107 esc thread closed"
);
my_zlog_info
(
"car0107 esc thread closed"
);
}
}
/*
* 0107 idle 唤醒动作:5分钟无控制命令时由 idle 线程调用。
* 顺序:低速后退一点 -> 中位 -> 低速前进一点 -> 前进刹车停止。
* 前进停止遵守电调刹车动作 BRAKE_THEN_NEUTRAL -> NEUTRAL_UNLOCK -> NEUTRAL,
* 由电调专用线程 tick 自动完成刹车保持与中位解锁收尾。
* 动作期间若有真实控制命令介入(control_source 被改为非 NONE)则立即让位。
*/
void
car0107_idle_wakeup_maneuver
(
void
)
{
/* 必须保证电调线程在运行,否则刹车序列无法由 tick 自动收尾到 NEUTRAL */
car0107_esc_thread_start
();
/* 方向盘回正,后退/前进均走直线 */
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
s_control_source
=
CTRL_NONE
;
/* 阻止 speed_smooth_process 串扰速度 */
s_target_angle
=
90
;
s_current_angle
=
90
;
car0107_calculate_L_R
(
90
);
/* 1) 低速后退:从中位直接跳到刚过死区的后退脉宽 */
car0107_esc_clear_pending_stop
();
s_esc
.
cmd_dir
=
CAR0107_ESC_DIR_BACK
;
s_esc
.
last_drive_dir
=
CAR0107_ESC_DIR_BACK
;
s_esc
.
last_nonzero_mode
=
CAR0107_ESC_DIR_BACK
;
s_esc
.
target_us
=
CAR0107_IDLE_REVERSE_US
;
s_esc
.
current_us
=
CAR0107_IDLE_REVERSE_US
;
s_esc
.
state
=
CAR0107_ESC_REVERSE
;
s_esc
.
trans_phase
=
CAR0107_TRANS_RAMP_RUN
;
car0107_esc_output_us
(
s_esc
.
current_us
,
0
);
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
car0107_esc_thread_wake
();
my_zlog_info
(
"car0107 idle maneuver: reverse %dus for %dms"
,
CAR0107_IDLE_REVERSE_US
,
CAR0107_IDLE_REVERSE_MS
);
delay_ms
(
CAR0107_IDLE_REVERSE_MS
);
/* 2) 停止后退:后退松手不刹车,直接回中 */
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
if
(
s_control_source
!=
CTRL_NONE
)
{
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
my_zlog_info
(
"car0107 idle maneuver: yield at neutral (control taken)"
);
return
;
}
car0107_esc_begin_neutral_stop
();
car0107_esc_output_us
(
CAR0107_ESC_US_NEUTRAL
,
0
);
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
car0107_esc_thread_wake
();
delay_ms
(
CAR0107_IDLE_NEUTRAL_MS
);
/* 3) 低速前进:从中位直接跳到最低前进行走脉宽 */
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
if
(
s_control_source
!=
CTRL_NONE
)
{
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
my_zlog_info
(
"car0107 idle maneuver: yield before forward (control taken)"
);
return
;
}
car0107_esc_clear_pending_stop
();
s_esc
.
cmd_dir
=
CAR0107_ESC_DIR_FORWARD
;
s_esc
.
last_drive_dir
=
CAR0107_ESC_DIR_FORWARD
;
s_esc
.
last_nonzero_mode
=
CAR0107_ESC_DIR_FORWARD
;
s_esc
.
target_us
=
CAR0107_IDLE_FORWARD_US
;
s_esc
.
current_us
=
CAR0107_IDLE_FORWARD_US
;
s_esc
.
state
=
CAR0107_ESC_FORWARD
;
s_esc
.
trans_phase
=
CAR0107_TRANS_RAMP_RUN
;
car0107_esc_output_us
(
s_esc
.
current_us
,
0
);
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
car0107_esc_thread_wake
();
my_zlog_info
(
"car0107 idle maneuver: forward %dus for %dms"
,
CAR0107_IDLE_FORWARD_US
,
CAR0107_IDLE_FORWARD_MS
);
delay_ms
(
CAR0107_IDLE_FORWARD_MS
);
/* 4) 停止前进:遵守刹车动作,由电调线程 tick 自动收尾 */
pthread_mutex_lock
(
&
s_car0107_hw_mutex
);
if
(
s_control_source
!=
CTRL_NONE
)
{
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
my_zlog_info
(
"car0107 idle maneuver: yield before brake (control taken)"
);
return
;
}
car0107_esc_begin_forward_brake
();
pthread_mutex_unlock
(
&
s_car0107_hw_mutex
);
car0107_esc_thread_wake
();
my_zlog_info
(
"car0107 idle maneuver: forward brake %dus for %dms"
,
car0107_forward_brake_us
(),
CAR0107_ESC_BRAKE_HOLD_MS
);
delay_ms
(
CAR0107_IDLE_BRAKE_WAIT_MS
);
}
drivers/devicecontrol/car/car0107_control.h
View file @
055aa314
...
@@ -14,4 +14,7 @@ void car0107_esc_thread_start(void);
...
@@ -14,4 +14,7 @@ void car0107_esc_thread_start(void);
void
car0107_esc_thread_wake
(
void
);
void
car0107_esc_thread_wake
(
void
);
void
car0107_esc_thread_close
(
void
);
void
car0107_esc_thread_close
(
void
);
/* idle 唤醒动作:低速后退 -> 低速前进 -> 前进刹车停止 */
void
car0107_idle_wakeup_maneuver
(
void
);
#endif
#endif
drivers/devicecontrol/car/car0107_idle.c
View file @
055aa314
...
@@ -6,16 +6,12 @@
...
@@ -6,16 +6,12 @@
#define CAR0107_IDLE_CHECK_MS 10000
#define CAR0107_IDLE_CHECK_MS 10000
#define CAR0107_IDLE_TIMEOUT_MS (5 * 60 * 1000)
#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
{
typedef
struct
{
pthread_mutex_t
mutex
;
pthread_mutex_t
mutex
;
ThreadPool_t
*
pool
;
ThreadPool_t
*
pool
;
bool
shutdown
;
bool
shutdown
;
bool
pool_ready
;
bool
pool_ready
;
int
next_left
;
}
car0107_idle_ctx_t
;
}
car0107_idle_ctx_t
;
static
car0107_idle_ctx_t
s_idle_ctx
=
{
static
car0107_idle_ctx_t
s_idle_ctx
=
{
...
@@ -23,7 +19,6 @@ static car0107_idle_ctx_t s_idle_ctx = {
...
@@ -23,7 +19,6 @@ static car0107_idle_ctx_t s_idle_ctx = {
.
pool
=
NULL
,
.
pool
=
NULL
,
.
shutdown
=
false
,
.
shutdown
=
false
,
.
pool_ready
=
false
,
.
pool_ready
=
false
,
.
next_left
=
1
,
};
};
static
void
car0107_idle_lock
(
void
)
static
void
car0107_idle_lock
(
void
)
...
@@ -67,16 +62,6 @@ static int car0107_idle_pool_init(void)
...
@@ -67,16 +62,6 @@ static int car0107_idle_pool_init(void)
return
0
;
return
0
;
}
}
static
void
car0107_idle_nudge_steering
(
int
turn_left
)
{
int
angle
=
turn_left
?
CAR0107_IDLE_STEER_LEFT
:
CAR0107_IDLE_STEER_RIGHT
;
/* 速度 PWM 只由持有0107硬件锁的控制路径写,idle线程不直接碰速度。 */
car0107_steering_set_angle_sync
(
angle
);
delay_ms
(
CAR0107_IDLE_STEER_HOLD_MS
);
car0107_steering_set_angle_sync
(
90
);
}
static
void
car0107_idle_monitor_task
(
void
*
arg
)
static
void
car0107_idle_monitor_task
(
void
*
arg
)
{
{
(
void
)
arg
;
(
void
)
arg
;
...
@@ -85,7 +70,6 @@ static void car0107_idle_monitor_task(void *arg)
...
@@ -85,7 +70,6 @@ static void car0107_idle_monitor_task(void *arg)
while
(
!
car0107_idle_shutdown_get
())
{
while
(
!
car0107_idle_shutdown_get
())
{
long
long
now
;
long
long
now
;
long
long
last
;
long
long
last
;
int
turn_left
;
delay_ms
(
CAR0107_IDLE_CHECK_MS
);
delay_ms
(
CAR0107_IDLE_CHECK_MS
);
if
(
car0107_idle_shutdown_get
()
||
g_device_type
!=
DEVICE_CAR0107
)
{
if
(
car0107_idle_shutdown_get
()
||
g_device_type
!=
DEVICE_CAR0107
)
{
...
@@ -101,15 +85,10 @@ static void car0107_idle_monitor_task(void *arg)
...
@@ -101,15 +85,10 @@ static void car0107_idle_monitor_task(void *arg)
continue
;
continue
;
}
}
car0107_idle_lock
();
my_zlog_info
(
"car0107 idle %dmin: wakeup maneuver (reverse->forward->brake)"
,
turn_left
=
s_idle_ctx
.
next_left
;
CAR0107_IDLE_TIMEOUT_MS
/
60000
);
s_idle_ctx
.
next_left
=
!
s_idle_ctx
.
next_left
;
/* 低速后退一点 -> 低速前进一点 -> 前进刹车停止(遵守刹车动作)*/
car0107_idle_unlock
();
car0107_idle_wakeup_maneuver
();
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
();
car0107_notify_control_activity
();
}
}
...
@@ -133,7 +112,7 @@ void car0107_idle_startup(int device_id)
...
@@ -133,7 +112,7 @@ void car0107_idle_startup(int device_id)
car0107_idle_unlock
();
car0107_idle_unlock
();
car0107_notify_control_activity
();
car0107_notify_control_activity
();
my_zlog_info
(
"car0107 idle monitor enabled:
steer nudge
every %dmin when idle"
,
my_zlog_info
(
"car0107 idle monitor enabled:
wakeup maneuver
every %dmin when idle"
,
CAR0107_IDLE_TIMEOUT_MS
/
60000
);
CAR0107_IDLE_TIMEOUT_MS
/
60000
);
}
}
...
...
drivers/sensors/audioplay.c.bak.20260710_111731
deleted
100644 → 0
View file @
3585cc8c
#include "common.h"
#include "audioplay.h"
#include "audio_sink.h" /* USB声卡排队锁 */
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include "audiotts_play.h"
#include "wifi_autoconfig.h"
#include <stdio.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_LOCAL_PLAY_TIMEOUT_SEC 8
static int s_audio_status=7;
static char s_urlbuf[512];
static char s_keybuf[256];
static double s_audio_volume=0.8;
#define AUDIO_DRIVING_LEVEL_MAX 30
static int s_local_play_pending = 0;
static char s_local_filepath[512];
static pthread_mutex_t s_local_play_mutex = PTHREAD_MUTEX_INITIALIZER;
static int local_is_cn_lang(const char *lang) {
return lang != NULL && (strcmp(lang, AUDIO_LANG_ZH) == 0 || strcmp(lang, "cn") == 0);
}
static int local_is_en_lang(const char *lang) {
return lang != NULL &&
(strcmp(lang, AUDIO_LANG_EN) == 0 || strcmp(lang, "en") == 0 || strcmp(lang, "EN") == 0);
}
static void local_resolve_lang(const char *language_override, char *out, size_t size) {
if (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0) {
snprintf(out, size, "%s", audio_get_language());
return;
}
if (local_is_cn_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_ZH);
return;
}
if (local_is_en_lang(language_override)) {
snprintf(out, size, "%s", AUDIO_LANG_EN);
return;
}
snprintf(out, size, "%s", audio_get_language());
my_zlog_warn("2017 未知 language: %s,使用当前语言: %s", language_override, out);
}
static int local_resolve_filepath(const char *filename, const char *language_override,
char *out, size_t size) {
char lang[8];
int use_default_lang = (language_override == NULL || language_override[0] == '\0' ||
strcasecmp(language_override, "default") == 0);
local_resolve_lang(language_override, lang, sizeof(lang));
if (local_is_cn_lang(lang)) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
snprintf(out, size, "%s/%s/%s", AUDIO_LOCAL_BASE, lang, filename);
if (access(out, F_OK) == 0) {
return 1;
}
if (use_default_lang) {
snprintf(out, size, "%s/%s", AUDIO_LOCAL_BASE, filename);
return access(out, F_OK) == 0;
}
return 0;
}
static void local_queue_play(const char *filename, const char *language) {
char resolved[512];
if (!local_resolve_filepath(filename, language, resolved, sizeof(resolved))) {
my_zlog_warn("2017 本地音频不存在: %s", filename);
return;
}
pthread_mutex_lock(&s_local_play_mutex);
snprintf(s_local_filepath, sizeof(s_local_filepath), "%s", resolved);
s_local_play_pending = 1;
pthread_mutex_unlock(&s_local_play_mutex);
my_zlog_info("2017 已排队本地音频: %s", resolved);
}
void audioplay_local_mqtt_receive(cJSON *body) {
if (!cJSON_IsObject(body)) {
my_zlog_warn("2017 body 无效");
return;
}
cJSON *status = cJSON_GetObjectItemCaseSensitive(body, "status");
cJSON *level_item = cJSON_GetObjectItemCaseSensitive(body, "level");
cJSON *language = cJSON_GetObjectItemCaseSensitive(body, "language");
if (!cJSON_IsString(status) || status->valuestring == NULL) {
my_zlog_warn("2017 缺少 status");
return;
}
const char *lang = "default";
if (cJSON_IsString(language) && language->valuestring != NULL) {
lang = language->valuestring;
}
if (strcmp(status->valuestring, "speed") == 0) {
local_queue_play("Speedupforoneminute.mp3", lang);
return;
}
if (strcmp(status->valuestring, "drivinglevel") == 0) {
int level = 0;
if (cJSON_IsString(level_item) && level_item->valuestring != NULL) {
level = atoi(level_item->valuestring);
} else if (cJSON_IsNumber(level_item)) {
level = level_item->valueint;
} else {
my_zlog_warn("2017 drivinglevel 缺少 level");
return;
}
if (level < 1 || level > AUDIO_DRIVING_LEVEL_MAX) {
my_zlog_warn("2017 drivinglevel level 无效: %d (1-%d)",
level, AUDIO_DRIVING_LEVEL_MAX);
return;
}
char filename[48];
snprintf(filename, sizeof(filename), "DrivingLevel%d.mp3", level);
local_queue_play(filename, lang);
return;
}
my_zlog_warn("2017 未知 status: %s", status->valuestring);
}
static double audioplay_volume_clamp(double v) {
if (v < 0.0) {
return 0.0;
}
if (v > 2.0) {
return 2.0;
}
return v;
}
int audio_wheat_init();
static int audio_system_exit_code(int status) {
if (status == -1) {
return -1;
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
}
return -1;
}
static void shell_single_quote(char *out, size_t size, const char *in) {
size_t pos = 0;
if (!out || size == 0) {
return;
}
out[pos++] = '\'';
if (in) {
for (const char *p = in; *p && pos + 5 < size; p++) {
if (*p == '\'') {
const char *esc = "'\\''";
for (const char *e = esc; *e && pos + 1 < size; e++) {
out[pos++] = *e;
}
} else {
out[pos++] = *p;
}
}
}
if (pos + 1 < size) {
out[pos++] = '\'';
}
out[pos] = '\0';
}
static int play_local_audio_file(const char *filepath) {
char quoted_path[1024];
char command[2048];
int ret;
int exit_code;
shell_single_quote(quoted_path, sizeof(quoted_path), filepath);
/* 排队等待USB声卡: 如果audio_sink正在播放手机音频, 等它释放 */
audio_sink_lock_alsa();
snprintf(command, sizeof(command),
"timeout %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! audioresample ! alsasink device=%s sync=false async=false",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path, AUDIO_LOCAL_ALSA_DEVICE);
ret = system(command);
exit_code = audio_system_exit_code(ret);
if (exit_code != 0) {
my_zlog_warn("本地音频 GStreamer 播放失败 exit=%d,尝试 ffplay: %s",
exit_code, filepath);
snprintf(command, sizeof(command),
"timeout %ds ffplay -nodisp -autoexit -loglevel warning %s",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path);
ret = system(command);
exit_code = audio_system_exit_code(ret);
}
audio_sink_unlock_alsa();
return exit_code;
}
//接收音频播放
void audioplay_mqtt_receive(cJSON *json) {
// 解析"audioLink"字段(修正了原始JSON中的拼写错误)
cJSON *audio_link = cJSON_GetObjectItemCaseSensitive(json, "audioLink");
if (cJSON_IsString(audio_link) && (audio_link->valuestring != NULL)) {
my_zlog_debug("音频链接: %s", audio_link->valuestring);
char *url=audio_link->valuestring;
snprintf(s_urlbuf, sizeof(s_urlbuf), "%s", url);
} else {
my_zlog_warn("错误:无法解析音频链接字段");
}
// 解析"key"字段
cJSON *key_char = cJSON_GetObjectItemCaseSensitive(json, "key");
if (cJSON_IsString(key_char) && (key_char->valuestring != NULL)) {
my_zlog_debug("音频链接KEY: %s", key_char->valuestring);
char *key=key_char->valuestring;
snprintf(s_keybuf, sizeof(s_keybuf), "%s", key);
} else {
my_zlog_warn("错误:无法解析音频链接");
s_audio_status=5;
}
// 解析"status"字段
cJSON *s_audio_status_val = cJSON_GetObjectItemCaseSensitive(json, "status");
if (cJSON_IsNumber(s_audio_status_val)) {
my_zlog_debug("标志: %d", s_audio_status_val->valueint);
s_audio_status=s_audio_status_val->valueint;
} else {
my_zlog_warn("错误:无法解析标志字段");
}
// 解析"volume"字段
cJSON *volume = cJSON_GetObjectItemCaseSensitive(json, "volume");
if (cJSON_IsNumber(volume)) {
my_zlog_debug("声量大小: %.3f", volume->valuedouble);
s_audio_volume = audioplay_volume_clamp(volume->valuedouble);
} else {
my_zlog_warn("错误:无法解析声量字段");
}
}
//发送音频播放是否完毕
void audioplay_send_mqtt() {
cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject();
cJSON *head = cJSON_CreateObject();
// 添加各个字段到 JSON 对象
cJSON_AddStringToObject(body, "type", "audio");
cJSON_AddStringToObject(body, "audioLink", s_urlbuf);
cJSON_AddStringToObject(body, "key", s_keybuf);
cJSON_AddNumberToObject(body, "status", s_audio_status);
cJSON_AddNumberToObject(body, "volume", s_audio_volume);
cJSON_AddNumberToObject(head, "message_type",3001);
cJSON_AddItemToObject(root, "body", body);
cJSON_AddItemToObject(root, "head",head);
// 将 JSON 对象转换为字符串
char* json_string = cJSON_PrintUnformatted(root);
my_zlog_debug("%s",json_string);
mqtt_publish_to_all(mqtt_topic_pure_number(), json_string, 0);
free(json_string);
cJSON_Delete(root);
}
//音频播放
void audioplay_cycle(){
char command[2048];
int ret;
while(1){
if(s_audio_status==0){
char *urlmoddle=s_urlbuf;
s_audio_volume = audioplay_volume_clamp(s_audio_volume);
snprintf(command, sizeof(command),
"sudo ffplay -nodisp -autoexit -loglevel quiet -af \"volume=%.3f\" \"%s\"",
s_audio_volume,urlmoddle);
my_zlog_debug("播放地址: %s", s_urlbuf);
my_zlog_debug("执行播放命令: %s", command);
ret = system(command);
if (ret != 0) {
my_zlog_error("播放失败");
s_audio_status=2;
}
if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
my_zlog_debug("播放已成功完成 : %s ", s_urlbuf);
s_audio_status=1;
} else {
my_zlog_warn("播放失败或中断: %s ", s_urlbuf);
s_audio_status=2;
}
audioplay_send_mqtt();
}
char local_filepath[sizeof(s_local_filepath)];
int local_play_pending = 0;
pthread_mutex_lock(&s_local_play_mutex);
if (s_local_play_pending) {
s_local_play_pending = 0;
snprintf(local_filepath, sizeof(local_filepath), "%s", s_local_filepath);
local_play_pending = 1;
}
pthread_mutex_unlock(&s_local_play_mutex);
if (local_play_pending) {
int exit_code;
my_zlog_info("播放本地音频: %s", local_filepath);
exit_code = play_local_audio_file(local_filepath);
if (exit_code == 0) {
my_zlog_debug("本地音频播放完成: %s", local_filepath);
} else {
my_zlog_warn("本地音频播放失败 exit=%d: %s", exit_code, local_filepath);
}
}
video_tts_play();
delay_ms(100);
}
}
static void trim_line(char *s)
{
if (!s)
return;
s[strcspn(s, "\r\n")] = '\0';
}
static int pulse_pactl_exit_code(int status)
{
if (status == -1)
return -1;
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
static int pulse_output_means_already_loaded(const char *output)
{
if (!output || !output[0])
return 0;
return strstr(output, "already loaded") != NULL ||
strstr(output, "Already loaded") != NULL ||
strstr(output, "Module exists") != NULL;
}
static int pulse_module_exists(const char *module_name, const char *device)
{
FILE *fp = popen("pactl list modules short 2>/dev/null", "r");
char line[512];
char needle[96];
if (!fp)
return 0;
snprintf(needle, sizeof(needle), "device=%s", device);
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, module_name) != NULL && strstr(line, needle) != NULL) {
pclose(fp);
return 1;
}
}
pclose(fp);
return 0;
}
static int pulse_load_alsa_module(const char *module_name, const char *label)
{
char cmd[256];
char output[512];
char line[256];
if (pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)",
label, module_name, AUDIO_USB_ALSA_DEVICE);
return 0;
}
snprintf(cmd, sizeof(cmd), "pactl load-module %s device=%s 2>&1",
module_name, AUDIO_USB_ALSA_DEVICE);
FILE *fp = popen(cmd, "r");
if (!fp) {
my_zlog_warn("%s 注册失败: 无法执行 pactl (%s)", label, strerror(errno));
return -1;
}
output[0] = '\0';
while (fgets(line, sizeof(line), fp) != NULL) {
trim_line(line);
if (!line[0])
continue;
if (output[0]) {
strncat(output, "; ", sizeof(output) - strlen(output) - 1);
}
strncat(output, line, sizeof(output) - strlen(output) - 1);
}
int status = pclose(fp);
int exit_code = pulse_pactl_exit_code(status);
if (exit_code == 0) {
my_zlog_info("%s 注册成功 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ", pactl: " : "", output[0] ? output : "");
return 0;
}
if (pulse_output_means_already_loaded(output) ||
pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ": " : "", output[0] ? output : "");
return 0;
}
my_zlog_warn("%s 注册失败 (%s device=%s): exit=%d%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE, exit_code,
output[0] ? ", pactl: " : ", pactl 无输出",
output[0] ? output : "");
return -1;
}
int audio_wheat_init(){
return pulse_load_alsa_module("module-alsa-sink", "喇叭");
}
int audio_speaker_init() {
return pulse_load_alsa_module("module-alsa-source", "麦");
}
int audio_init(){
delay_s(1);
audio_wheat_init();
delay_s(1);
audio_speaker_init();
return 0;
}
#define CONFIG_FILE "/usr/share/pulseaudio/alsa-mixer/profile-sets/default.conf"
static const char *s_yundea_block =
"# 为 Yundea 1076 USB 二合一设备添加配置\n"
"[Mapping yundea-input]\n"
"device-strings = hw:%f,0\n"
"direction = input\n"
"priority = 100\n"
"channel-map = mono\n"
"\n"
"[Mapping yundea-output]\n"
"device-strings = hw:%f,0\n"
"direction = output\n"
"priority = 100\n"
"channel-map = left,right\n"
"\n"
"[Profile yundea-duplex]\n"
"input-mappings = yundea-input\n"
"output-mappings = yundea-output\n"
"priority = 200\n";
/*
*功能:加入usb声卡配置
*
*/
int audio_config_init() {
delay_s(12);
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp) {
perror("无法打开配置文件进行读取");
return 1;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(size + 1);
if (!buffer) {
my_zlog_error("内存分配失败");
fclose(fp);
return 1;
}
size_t result =fread(buffer, 1, size, fp);
if (result != size) {
my_zlog_warn("Error: Failed to read audio data");
}
buffer[size] = '\0';
fclose(fp);
// 检查是否包含完整配置块
if (strstr(buffer, s_yundea_block) != NULL) {
my_zlog_debug("配置文件中已包含指定配置,跳过添加。");
free(buffer);
return 0;
}
free(buffer);
my_zlog_info("未找到指定配置,正在追加到文件末尾...");
// 打开文件追加配置
fp = fopen(CONFIG_FILE, "a");
if (!fp) {
my_zlog_error("无法打开配置文件进行写入");
return 1;
}
fprintf(fp, "\n%s", s_yundea_block);
fclose(fp);
my_zlog_info("配置已成功追加。");
return 0;
}
modules/webrtcpush/mpp_h264_source.c
View file @
055aa314
...
@@ -73,6 +73,8 @@ struct MppH264Source {
...
@@ -73,6 +73,8 @@ struct MppH264Source {
guint8
*
jpeg_tmp_v
;
guint8
*
jpeg_tmp_v
;
size_t
jpeg_tmp_capacity
;
size_t
jpeg_tmp_capacity
;
guint
keyframe_req
;
guint
keyframe_req
;
gint
pending_bitrate_bps
;
guint
applied_bitrate_bps
;
volatile
gboolean
pending_idr
;
volatile
gboolean
pending_idr
;
gboolean
pending_idr_event_sent
;
gboolean
pending_idr_event_sent
;
gint64
last_keyframe_event_us
;
gint64
last_keyframe_event_us
;
...
@@ -104,6 +106,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
...
@@ -104,6 +106,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
size_t
*
size
,
gboolean
*
is_idr
,
size_t
*
size
,
gboolean
*
is_idr
,
guint64
*
pts_ns
);
guint64
*
pts_ns
);
static
gboolean
send_force_key_unit
(
MppH264Source
*
src
);
static
gboolean
send_force_key_unit
(
MppH264Source
*
src
);
static
void
apply_pending_bitrate
(
MppH264Source
*
src
);
static
gpointer
mjpeg_decode_thread
(
gpointer
user_data
);
static
gpointer
mjpeg_decode_thread
(
gpointer
user_data
);
static
void
timing_mark_encoder_in
(
MppH264Source
*
src
,
guint64
pts_ns
,
static
void
timing_mark_encoder_in
(
MppH264Source
*
src
,
guint64
pts_ns
,
gint64
enc_in_us
);
gint64
enc_in_us
);
...
@@ -1648,6 +1651,9 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
...
@@ -1648,6 +1651,9 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
if
(
!
src
||
!
src
->
appsink
||
!
data
||
!
size
)
if
(
!
src
||
!
src
->
appsink
||
!
data
||
!
size
)
return
FALSE
;
return
FALSE
;
/* 编码帧之间应用动态码率,避免从 REMB/RTP 回调线程直接重配编码器。 */
apply_pending_bitrate
(
src
);
/* bus 报 ERROR 后 pipeline 卡死,这里及时恢复避免长期黑屏 */
/* bus 报 ERROR 后 pipeline 卡死,这里及时恢复避免长期黑屏 */
if
(
src
->
pending_recover
&&
mpp_h264_source_recover
(
src
))
{
if
(
src
->
pending_recover
&&
mpp_h264_source_recover
(
src
))
{
src
->
pending_idr
=
TRUE
;
src
->
pending_idr
=
TRUE
;
...
@@ -1676,7 +1682,7 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
...
@@ -1676,7 +1682,7 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
return
pull_sample
(
src
,
FALSE
,
timeout_ms
,
data
,
size
,
is_idr
,
pts_ns
);
return
pull_sample
(
src
,
FALSE
,
timeout_ms
,
data
,
size
,
is_idr
,
pts_ns
);
}
}
void
mpp_h264_source_set_bitrate
(
MppH264Source
*
src
,
guint
bitrate_bps
)
static
void
apply_bitrate_now
(
MppH264Source
*
src
,
guint
bitrate_bps
)
{
{
GstElementFactory
*
f
;
GstElementFactory
*
f
;
const
gchar
*
name
;
const
gchar
*
name
;
...
@@ -1710,6 +1716,36 @@ void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
...
@@ -1710,6 +1716,36 @@ void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
}
else
if
(
name
&&
g_str_has_prefix
(
name
,
"x264enc"
))
{
}
else
if
(
name
&&
g_str_has_prefix
(
name
,
"x264enc"
))
{
g_object_set
(
src
->
enc
,
"bitrate"
,
bitrate_bps
/
1000
,
NULL
);
g_object_set
(
src
->
enc
,
"bitrate"
,
bitrate_bps
/
1000
,
NULL
);
}
}
src
->
applied_bitrate_bps
=
bitrate_bps
;
my_zlog_info
(
"mpp_h264_source: bitrate applied at frame boundary %u kbps"
,
bitrate_bps
/
1000
);
}
static
void
apply_pending_bitrate
(
MppH264Source
*
src
)
{
gint
pending
;
if
(
!
src
)
return
;
pending
=
g_atomic_int_get
(
&
src
->
pending_bitrate_bps
);
if
(
pending
<=
0
||
!
g_atomic_int_compare_and_exchange
(
&
src
->
pending_bitrate_bps
,
pending
,
0
))
return
;
if
((
guint
)
pending
==
src
->
applied_bitrate_bps
)
return
;
apply_bitrate_now
(
src
,
(
guint
)
pending
);
}
void
mpp_h264_source_set_bitrate
(
MppH264Source
*
src
,
guint
bitrate_bps
)
{
if
(
!
src
||
!
bitrate_bps
)
return
;
if
(
bitrate_bps
<
MPP_VIDEO_MIN_BPS
)
bitrate_bps
=
MPP_VIDEO_MIN_BPS
;
if
(
bitrate_bps
>
MPP_VIDEO_MAX_BPS
)
bitrate_bps
=
MPP_VIDEO_MAX_BPS
;
g_atomic_int_set
(
&
src
->
pending_bitrate_bps
,
(
gint
)
bitrate_bps
);
}
}
gboolean
mpp_h264_source_recover
(
MppH264Source
*
src
)
gboolean
mpp_h264_source_recover
(
MppH264Source
*
src
)
...
...
modules/webrtcpush/rtc_client.c
View file @
055aa314
...
@@ -88,6 +88,8 @@ typedef struct {
...
@@ -88,6 +88,8 @@ typedef struct {
guint
last_remb_bitrate
;
guint
last_remb_bitrate
;
guint
remb_filtered
;
guint
remb_filtered
;
guint
remb_down_samples
;
guint
remb_down_samples
;
gboolean
initial_probe_complete
;
gint64
probe_target_reached_us
;
gint64
last_bitrate_ramp_us
;
gint64
last_bitrate_ramp_us
;
gint64
last_mpp_bitrate_apply_us
;
gint64
last_mpp_bitrate_apply_us
;
gint64
last_idr_request_us
;
gint64
last_idr_request_us
;
...
@@ -1021,6 +1023,7 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
...
@@ -1021,6 +1023,7 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
guint
b
;
guint
b
;
guint
old_b
;
guint
old_b
;
guint
old_mpp_b
;
guint
old_mpp_b
;
guint
mpp_apply_b
=
0
;
guint
mpp_delta
;
guint
mpp_delta
;
gint64
now_us
;
gint64
now_us
;
gint64
last_mpp_apply_us
;
gint64
last_mpp_apply_us
;
...
@@ -1039,11 +1042,25 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
...
@@ -1039,11 +1042,25 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
old_mpp_b
=
old_b
?
old_b
:
b
;
old_mpp_b
=
old_b
?
old_b
:
b
;
client
->
target_bitrate
=
b
;
client
->
target_bitrate
=
b
;
mpp_delta
=
bitrate_delta
(
old_mpp_b
,
b
);
mpp_delta
=
bitrate_delta
(
old_mpp_b
,
b
);
if
(
mpp_delta
>=
WEBRTCPUSH_MPP_RECONFIG_MIN_DELTA_BPS
&&
if
((
guint64
)
mpp_delta
*
100U
>=
(
guint64
)
old_mpp_b
*
WEBRTCPUSH_MPP_RECONFIG_MIN_DELTA_PERCENT
&&
now_us
-
last_mpp_apply_us
>=
now_us
-
last_mpp_apply_us
>=
(
gint64
)
WEBRTCPUSH_MPP_RECONFIG_MIN_INTERVAL_MS
*
1000
)
{
(
gint64
)
WEBRTCPUSH_MPP_RECONFIG_MIN_INTERVAL_MS
*
1000
)
{
apply_mpp
=
TRUE
;
apply_mpp
=
TRUE
;
client
->
applied_mpp_bitrate
=
b
;
mpp_apply_b
=
b
;
if
(
b
>
old_mpp_b
)
{
guint
max_up
=
(
guint
)(((
guint64
)
old_mpp_b
*
(
100U
+
WEBRTCPUSH_MPP_RECONFIG_MAX_STEP_PERCENT
))
/
100U
);
if
(
mpp_apply_b
>
max_up
)
mpp_apply_b
=
max_up
;
}
else
{
guint
min_down
=
(
guint
)(((
guint64
)
old_mpp_b
*
(
100U
-
WEBRTCPUSH_MPP_RECONFIG_MAX_STEP_PERCENT
))
/
100U
);
if
(
mpp_apply_b
<
min_down
)
mpp_apply_b
=
min_down
;
}
mpp_apply_b
=
clamp_bitrate
(
mpp_apply_b
);
client
->
applied_mpp_bitrate
=
mpp_apply_b
;
client
->
last_mpp_bitrate_apply_us
=
now_us
;
client
->
last_mpp_bitrate_apply_us
=
now_us
;
}
}
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
...
@@ -1058,12 +1075,13 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
...
@@ -1058,12 +1075,13 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
}
}
if
(
apply_mpp
)
{
if
(
apply_mpp
)
{
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
b
);
/* 这里只投递原子目标;真正的 mpph264enc 属性更新在拉帧边界执行。 */
my_zlog_info
(
"libdatachannel: RTCP bitrate control -> target=%u->%u kbps, MPP=%u->%u kbps pacing=%u kbps"
,
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
mpp_apply_b
);
old_b
/
1000
,
b
/
1000
,
old_mpp_b
/
1000
,
b
/
1000
,
my_zlog_info
(
"libdatachannel: bitrate control -> target=%u->%u kbps, MPP queued=%u->%u kbps pacing=%u kbps"
,
old_b
/
1000
,
b
/
1000
,
old_mpp_b
/
1000
,
mpp_apply_b
/
1000
,
pacing_bitrate_for_encoder
(
b
)
/
1000
);
pacing_bitrate_for_encoder
(
b
)
/
1000
);
}
else
{
}
else
{
my_zlog_info
(
"libdatachannel:
RTCP
bitrate control -> target=%u->%u kbps, MPP hold=%u kbps pacing=%u kbps"
,
my_zlog_info
(
"libdatachannel: bitrate control -> target=%u->%u kbps, MPP hold=%u kbps pacing=%u kbps"
,
old_b
/
1000
,
b
/
1000
,
old_mpp_b
/
1000
,
old_b
/
1000
,
b
/
1000
,
old_mpp_b
/
1000
,
pacing_bitrate_for_encoder
(
b
)
/
1000
);
pacing_bitrate_for_encoder
(
b
)
/
1000
);
}
}
...
@@ -1093,11 +1111,11 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
...
@@ -1093,11 +1111,11 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
return
;
/* confirmed decreases are applied by on_remb() */
return
;
/* confirmed decreases are applied by on_remb() */
diff
=
tgt
-
cur
;
diff
=
tgt
-
cur
;
step
=
(
guint
)(((
guint64
)
cur
*
12U
)
/
100U
);
step
=
(
guint
)(((
guint64
)
cur
*
WEBRTCPUSH_BITRATE_RAMP_UP_PERCENT
)
/
100U
);
if
(
step
<
50000U
)
if
(
step
<
WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MIN_BPS
)
step
=
50000U
;
step
=
WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MIN_BPS
;
if
(
step
>
250000U
)
if
(
step
>
WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MAX_BPS
)
step
=
250000U
;
step
=
WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MAX_BPS
;
if
(
step
>
diff
)
if
(
step
>
diff
)
step
=
diff
;
step
=
diff
;
next
=
cur
+
step
;
next
=
cur
+
step
;
...
@@ -1144,8 +1162,10 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -1144,8 +1162,10 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
gboolean
can_step_down
=
FALSE
;
gboolean
can_step_down
=
FALSE
;
gboolean
have_pacing
;
gboolean
have_pacing
;
gboolean
first_frame_sent
;
gboolean
first_frame_sent
;
gboolean
recent_idr
=
FALSE
;
gboolean
remb_sane_floor
=
FALSE
;
gboolean
remb_sane_floor
=
FALSE
;
gboolean
log_remb_sane_floor
=
FALSE
;
gboolean
log_remb_sane_floor
=
FALSE
;
gboolean
probe_completed_now
=
FALSE
;
guint
pacing_packets
=
0
;
guint
pacing_packets
=
0
;
guint
pacing_bytes
=
0
;
guint
pacing_bytes
=
0
;
guint
pacing_delay_ms
=
0
;
guint
pacing_delay_ms
=
0
;
...
@@ -1161,13 +1181,34 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -1161,13 +1181,34 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
current
=
client
->
target_bitrate
;
current
=
client
->
target_bitrate
;
first_frame_sent
=
client
->
first_frame_sent
;
first_frame_sent
=
client
->
first_frame_sent
;
recent_idr
=
client
->
last_idr_sent_us
>
0
&&
now_us
-
client
->
last_idr_sent_us
<
(
gint64
)
WEBRTCPUSH_REMB_IDR_GRACE_MS
*
1000
;
client
->
last_remb_bitrate
=
bitrate
;
client
->
last_remb_bitrate
=
bitrate
;
can_step_down
=
now_us
-
client
->
last_bitrate_ramp_us
>=
can_step_down
=
now_us
-
client
->
last_bitrate_ramp_us
>=
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_DOWN_MS
*
1000
;
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_DOWN_MS
*
1000
;
/*
* REMB 在低发送量时容易形成“低码率 -> 低估计 -> 永远无法探测”的闭环。
* 初次连接主动爬到健康探测目标并保持一段时间,让接收端用真实流量重新估计。
* 探测完成后仍保留“发送端受限”保护:pacing 健康时,低 REMB 只能阻止
* 继续上升,不能把发送量越压越低。只有持续积压且不在 IDR 窗口才允许下降。
*/
if
(
!
client
->
initial_probe_complete
)
{
if
(
current
+
WEBRTCPUSH_REMB_DOWN_MIN_STEP
>=
WEBRTCPUSH_HEALTHY_PROBE_TARGET_BPS
)
{
if
(
client
->
probe_target_reached_us
==
0
)
client
->
probe_target_reached_us
=
now_us
;
else
if
(
now_us
-
client
->
probe_target_reached_us
>=
(
gint64
)
WEBRTCPUSH_INITIAL_PROBE_HOLD_MS
*
1000
)
{
client
->
initial_probe_complete
=
TRUE
;
probe_completed_now
=
TRUE
;
}
}
}
estimate_bitrate
=
bitrate
;
estimate_bitrate
=
bitrate
;
if
(
first_frame_sent
&&
have_pacing
&&
if
(
first_frame_sent
&&
have_pacing
&&
bitrate
<
WEBRTCPUSH_REMB_SANE_RAW_MAX_BPS
&&
bitrate
<
WEBRTCPUSH_REMB_SANE_RAW_MAX_BPS
&&
pacing_delay_ms
<=
WEBRTCPUSH_REMB_SANE_PACING_MAX_MS
)
{
(
pacing_delay_ms
<=
WEBRTCPUSH_REMB_SANE_PACING_MAX_MS
||
recent_idr
)
)
{
estimate_bitrate
=
WEBRTCPUSH_REMB_SANE_FLOOR_BPS
;
estimate_bitrate
=
WEBRTCPUSH_REMB_SANE_FLOOR_BPS
;
remb_sane_floor
=
TRUE
;
remb_sane_floor
=
TRUE
;
if
(
client
->
last_remb_sane_log_us
==
0
||
if
(
client
->
last_remb_sane_log_us
==
0
||
...
@@ -1234,10 +1275,16 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -1234,10 +1275,16 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
}
}
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
probe_completed_now
)
{
my_zlog_info
(
"libdatachannel: initial bandwidth probe complete at %u kbps; steady sender-limited REMB guard enabled"
,
current
/
1000
);
}
if
(
log_remb_sane_floor
)
{
if
(
log_remb_sane_floor
)
{
my_zlog_info
(
"libdatachannel: RTCP REMB s
uspicious low raw=%u kbps, use=%u kbps (pacing=%u pkt/%u bytes/%ums
)"
,
my_zlog_info
(
"libdatachannel: RTCP REMB s
ender-limited raw=%u kbps, use=%u kbps (pacing=%u pkt/%u bytes/%ums idr_grace=%d
)"
,
bitrate
/
1000
,
estimate_bitrate
/
1000
,
bitrate
/
1000
,
estimate_bitrate
/
1000
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
);
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
recent_idr
?
1
:
0
);
}
}
if
(
next
)
{
if
(
next
)
{
...
@@ -1492,13 +1539,14 @@ static gpointer send_thread_main(gpointer data)
...
@@ -1492,13 +1539,14 @@ static gpointer send_thread_main(gpointer data)
actual_fps_x10
=
(
guint
)(((
guint64
)
sent_window_frames
*
10U
*
1000000U
)
/
actual_fps_x10
=
(
guint
)(((
guint64
)
sent_window_frames
*
10U
*
1000000U
)
/
elapsed_us
);
elapsed_us
);
}
}
my_zlog_info
(
"libdatachannel: stats encoder=%u actual_h264=%u kbps sent_fps=%u.%u raw_remb=%u filtered_remb=%u ceiling=%u kbps pacing=%u pkt/%u bytes/%ums queue_dropped=%u"
,
my_zlog_info
(
"libdatachannel: stats encoder=%u actual_h264=%u kbps sent_fps=%u.%u raw_remb=%u filtered_remb=%u ceiling=%u kbps pacing=%u pkt/%u bytes/%ums queue_dropped=%u
probe=%s
"
,
target_bps
/
1000
,
actual_kbps
,
target_bps
/
1000
,
actual_kbps
,
actual_fps_x10
/
10
,
actual_fps_x10
%
10
,
actual_fps_x10
/
10
,
actual_fps_x10
%
10
,
raw_remb_bps
/
1000
,
filtered_remb_bps
/
1000
,
raw_remb_bps
/
1000
,
filtered_remb_bps
/
1000
,
remb_bps
/
1000
,
remb_bps
/
1000
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
queue_dropped
);
queue_dropped
,
client
->
initial_probe_complete
?
"done"
:
"active"
);
send_datachannel_mbps
(
client
,
actual_kbps
);
send_datachannel_mbps
(
client
,
actual_kbps
);
/* 修复: MPP CBR失控时actual>>target, pacing必须跟actual否则永远堆积 */
/* 修复: MPP CBR失控时actual>>target, pacing必须跟actual否则永远堆积 */
if
(
actual_kbps
>
0
)
{
if
(
actual_kbps
>
0
)
{
...
@@ -1975,6 +2023,8 @@ int rtc_client_start(AppState *app)
...
@@ -1975,6 +2023,8 @@ int rtc_client_start(AppState *app)
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
applied_mpp_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
applied_mpp_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
initial_probe_complete
=
FALSE
;
client
->
probe_target_reached_us
=
0
;
client
->
last_remb_bitrate
=
0
;
client
->
last_remb_bitrate
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_down_samples
=
0
;
client
->
remb_down_samples
=
0
;
...
@@ -2201,6 +2251,8 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
...
@@ -2201,6 +2251,8 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
applied_mpp_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
applied_mpp_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
initial_probe_complete
=
FALSE
;
client
->
probe_target_reached_us
=
0
;
client
->
last_remb_bitrate
=
0
;
client
->
last_remb_bitrate
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_down_samples
=
0
;
client
->
remb_down_samples
=
0
;
...
...
modules/webrtcpush/webrtcpush_config.h
View file @
055aa314
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
*
*
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
*/
*/
#define WEBRTCPUSH_USE_MPP
0
#define WEBRTCPUSH_USE_MPP
1
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_FPS 24
...
@@ -22,24 +22,33 @@
...
@@ -22,24 +22,33 @@
*/
*/
#define WEBRTCPUSH_INITIAL_BITRATE 900000U
#define WEBRTCPUSH_INITIAL_BITRATE 900000U
#define WEBRTCPUSH_MIN_BITRATE 800000U
#define WEBRTCPUSH_MIN_BITRATE 800000U
#define WEBRTCPUSH_MAX_BITRATE
26
00000U
#define WEBRTCPUSH_MAX_BITRATE
30
00000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS 8U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS 8U
#define WEBRTCPUSH_REMB_SEVERE_CONFIRMATIONS 2U
#define WEBRTCPUSH_REMB_SEVERE_CONFIRMATIONS 2U
#define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 1
8
00U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 1
2
00U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 2000U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 2000U
#define WEBRTCPUSH_BITRATE_RAMP_UP_PERCENT 12U
#define WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MIN_BPS 70000U
#define WEBRTCPUSH_BITRATE_RAMP_UP_STEP_MAX_BPS 250000U
#define WEBRTCPUSH_REMB_DOWN_STEP_PERCENT 10U
#define WEBRTCPUSH_REMB_DOWN_STEP_PERCENT 10U
#define WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS 60000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS 60000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS 120000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS 120000U
/* REMB 低估保护:pacing 不堵时,过低浏览器估计不直接压糊 720p。 */
/* REMB 低估保护:pacing 不堵时,过低浏览器估计不直接压糊 720p。 */
#define WEBRTCPUSH_REMB_SANE_FLOOR_BPS 2200000U
#define WEBRTCPUSH_HEALTHY_PROBE_TARGET_BPS 2700000U
#define WEBRTCPUSH_REMB_SANE_RAW_MAX_BPS 1200000U
/* 3.375Mbps * 80% = 2.7Mbps. 这是 REMB 原始估计的探测地板,不是编码码率。 */
#define WEBRTCPUSH_REMB_SANE_FLOOR_BPS 3375000U
#define WEBRTCPUSH_REMB_SANE_RAW_MAX_BPS WEBRTCPUSH_REMB_SANE_FLOOR_BPS
#define WEBRTCPUSH_REMB_SANE_PACING_MAX_MS 100U
#define WEBRTCPUSH_REMB_SANE_PACING_MAX_MS 100U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 180U
/* IDR 会产生短时大包,不能在这个窗口内把瞬时 pacing 峰值误判成网络拥塞。 */
#define WEBRTCPUSH_REMB_IDR_GRACE_MS 1000U
/* 达到 2.3Mbps 后继续发送一段时间,让浏览器有真实流量可重新估计带宽。 */
#define WEBRTCPUSH_INITIAL_PROBE_HOLD_MS 6000U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 135U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_MAX_BITRATE 4
0
00000U
#define WEBRTCPUSH_PACING_MAX_BITRATE 4
2
00000U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 260U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 260U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 1000U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 1000U
/* 首个/刚恢复的 IDR 允许短暂排队,避免首屏关键帧刚发出就被清队列 */
/* 首个/刚恢复的 IDR 允许短暂排队,避免首屏关键帧刚发出就被清队列 */
...
@@ -48,8 +57,9 @@
...
@@ -48,8 +57,9 @@
#define WEBRTCPUSH_PACING_RESYNC_IDR_MS 5000U
#define WEBRTCPUSH_PACING_RESYNC_IDR_MS 5000U
/* RK MPP 运行中小幅改 bps 容易顿一下;小变化只调 pacing,少重配硬编。 */
/* RK MPP 运行中小幅改 bps 容易顿一下;小变化只调 pacing,少重配硬编。 */
#define WEBRTCPUSH_MPP_RECONFIG_MIN_DELTA_BPS 200000U
#define WEBRTCPUSH_MPP_RECONFIG_MIN_DELTA_PERCENT 20U
#define WEBRTCPUSH_MPP_RECONFIG_MIN_INTERVAL_MS 5000U
#define WEBRTCPUSH_MPP_RECONFIG_MAX_STEP_PERCENT 25U
#define WEBRTCPUSH_MPP_RECONFIG_MIN_INTERVAL_MS 1000U
/* RTP 分片与 NACK(MTU=1200,留 SRTP/DTLS/FU 余量) */
/* RTP 分片与 NACK(MTU=1200,留 SRTP/DTLS/FU 余量) */
#define WEBRTCPUSH_RTP_MAX_FRAGMENT 1050U
#define WEBRTCPUSH_RTP_MAX_FRAGMENT 1050U
...
...
zlog.conf
View file @
055aa314
...
@@ -9,4 +9,4 @@ file perms = 600
...
@@ -9,4 +9,4 @@ file perms = 600
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
millisecond
=
"%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
[
rules
]
[
rules
]
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-1
0
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-1
1
.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