Commit 055aa314 authored by 957dd's avatar 957dd

c语言推流基本完成

parent 3585cc8c
No preview for this file type
...@@ -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);
}
...@@ -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
...@@ -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);
} }
......
#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;
}
...@@ -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)
......
...@@ -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 suspicious low raw=%u kbps, use=%u kbps (pacing=%u pkt/%u bytes/%ums)", my_zlog_info("libdatachannel: RTCP REMB sender-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;
......
...@@ -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 2600000U #define WEBRTCPUSH_MAX_BITRATE 3000000U
#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 1800U #define WEBRTCPUSH_BITRATE_RAMP_UP_MS 1200U
#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 4000000U #define WEBRTCPUSH_PACING_MAX_BITRATE 4200000U
#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
......
...@@ -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-10.log"; millisecond my_log.* "/home/orangepi/car/master/log/log_2026-07-11.log"; millisecond
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment