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);
} }
......
This diff is collapsed.
...@@ -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