Commit d92f8afb authored by 957dd's avatar 957dd

加入了跟app的通信,网络配置那些优化

parent 6edd4759
...@@ -885,3 +885,147 @@ void device_send_saved_wifi() { ...@@ -885,3 +885,147 @@ void device_send_saved_wifi() {
} }
cJSON_Delete(root); cJSON_Delete(root);
} }
/* ==================== App WiFi 管理 (UDP 18888) 实现 ==================== */
/* 从 .nmconnection 文件读取 psk= 字段。文件权限 root:root 600,
* 设备进程以 root 运行可直接读取。返回 0=成功, -1=失败/无密码 */
static int read_wifi_psk_from_profile(const char *ssid, char *psk_out, size_t psk_sz) {
char filepath[512];
FILE *fp;
char line[256];
char *p;
int found = 0;
if (!ssid || !psk_out || psk_sz == 0)
return -1;
psk_out[0] = '\0';
/* 主路径:/etc/NetworkManager/system-connections/<ssid>.nmconnection */
snprintf(filepath, sizeof(filepath), "%s%s.nmconnection", NM_CONN_DIR, ssid);
fp = fopen(filepath, "r");
if (!fp)
return -1;
while (fgets(line, sizeof(line), fp)) {
trim_line(line);
/* 匹配 psk= 开头(前面可能有空格) */
p = strchr(line, 'p');
while (p) {
if (strncmp(p, "psk=", 4) == 0) {
const char *val = p + 4;
strncpy(psk_out, val, psk_sz - 1);
psk_out[psk_sz - 1] = '\0';
found = 1;
break;
}
p = strchr(p + 1, 'p');
}
if (found)
break;
}
fclose(fp);
return found ? 0 : -1;
}
/* 查询已保存 WiFi 列表,返回 cJSON 数组(调用方负责 cJSON_Delete) */
cJSON *app_wifi_list_query(void) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count;
cJSON *list;
int active_idx = -1;
/* 刷新当前连接 SSID,确保 active 判断准确 */
get_current_wifi();
count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
if (count < 0)
count = 0;
list = cJSON_CreateArray();
if (!list)
return NULL;
for (int i = 0; i < count; i++) {
cJSON *item = cJSON_CreateObject();
if (!item)
continue;
cJSON_AddStringToObject(item, "ssid", entries[i].ssid);
/* password:从 .nmconnection 读取;默认 WiFi 用内置密码兜底 */
char psk[WIFI_PSK_MAX_LENGTH + 1] = {0};
if (read_wifi_psk_from_profile(entries[i].ssid, psk, sizeof(psk)) == 0)
cJSON_AddStringToObject(item, "password", psk);
else if (is_default_wifi_ssid(entries[i].ssid))
cJSON_AddStringToObject(item, "password", default_password);
else
cJSON_AddStringToObject(item, "password", "");
/* active:wifi_profile_is_in_use 内部已综合判断 */
int in_use = wifi_profile_is_in_use(entries[i].ssid);
cJSON_AddBoolToObject(item, "active", in_use ? 1 : 0);
if (in_use && active_idx < 0)
active_idx = i;
/* protected:默认 WiFi (jking) 受保护 */
cJSON_AddBoolToObject(item, "protected", is_default_wifi_ssid(entries[i].ssid) ? 1 : 0);
cJSON_AddItemToArray(list, item);
}
(void)active_idx;
return list;
}
/* 切换当前连接到已保存的指定 SSID */
int app_wifi_switch(const char *ssid) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count;
int found = 0;
char psk[WIFI_PSK_MAX_LENGTH + 1] = {0};
if (!ssid || !ssid[0])
return -1;
count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
if (count < 0)
return -1;
for (int i = 0; i < count; i++) {
if (strcmp(entries[i].ssid, ssid) == 0) {
found = 1;
break;
}
}
if (!found)
return -1;
/* 读取已保存密码,无密码则空串连接(开放网络) */
if (read_wifi_psk_from_profile(ssid, psk, sizeof(psk)) != 0 &&
is_default_wifi_ssid(ssid))
snprintf(psk, sizeof(psk), "%s", default_password);
/* 复用现有连接逻辑:已存在则直接 connect,无密码走无密码分支。
* 区分返回码:未找到返回 -1,连接失败返回 -2,便于 agent 给准确提示。 */
if (orange_pi_connect_wifi(ssid, psk[0] ? psk : NULL) == 0)
return 0;
my_zlog_error("app_wifi_switch: WiFi 已保存但连接失败 SSID=%s", ssid);
return -2;
}
/* 删除已保存的指定 SSID(受保护的 jking 不可删) */
int app_wifi_delete(const char *ssid) {
if (!ssid || !ssid[0])
return -1;
/* 受保护检查(与 device_wifi_delete_saved_profile 内部一致,提前返回便于 agent 区分原因) */
if (is_default_wifi_ssid(ssid))
return -2;
/* device_wifi_delete_saved_profile 内部还会检查是否在用,
* 在用的返回 -3。这里统一映射为 -1 失败,由 agent 组织 message。 */
int rc = device_wifi_delete_saved_profile(ssid);
if (rc == 0)
return 0;
return -1;
}
...@@ -18,4 +18,19 @@ int orange_pi_save_wifi(const char *ssid, const char *password); ...@@ -18,4 +18,19 @@ int orange_pi_save_wifi(const char *ssid, const char *password);
/* 保存并立即连接 WiFi(含满 10 个自动删最旧逻辑) */ /* 保存并立即连接 WiFi(含满 10 个自动删最旧逻辑) */
int orange_pi_connect_wifi(const char *ssid, const char *password); int orange_pi_connect_wifi(const char *ssid, const char *password);
/* ===== App WiFi 管理 (UDP 18888) 接口 ===== */
/* 查询已保存 WiFi 列表,返回 cJSON 数组(调用方负责 cJSON_Delete):
* 每项含 ssid/password/active/protected 四字段。
* active/protected 由内部判断,password 从 .nmconnection 读取。 */
cJSON *app_wifi_list_query(void);
/* 切换当前连接到已保存的指定 SSID。
* 返回 0=已发起切换(异步), -1=未找到/失败 */
int app_wifi_switch(const char *ssid);
/* 删除已保存的指定 SSID(受保护的 jking 不可删)。
* 返回 0=成功, -1=未找到/失败, -2=受保护不可删 */
int app_wifi_delete(const char *ssid);
#endif #endif
\ No newline at end of file
No preview for this file type
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "tailscale_deploy.h" #include "tailscale_deploy.h"
#include "runtime_deps.h" #include "runtime_deps.h"
#include "wifi_config_agent.h" #include "wifi_config_agent.h"
#include "stream_debug_agent.h" #include "device_debug_agent.h"
#include "webrtcpush_run.h" #include "webrtcpush_run.h"
#endif #endif
\ No newline at end of file
...@@ -22,7 +22,7 @@ file(GLOB_RECURSE MODULES_SOURCES ...@@ -22,7 +22,7 @@ file(GLOB_RECURSE MODULES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/webrtcpush/*.c ${CMAKE_CURRENT_SOURCE_DIR}/webrtcpush/*.c
${CMAKE_CURRENT_SOURCE_DIR}/runtime_deps/*.c ${CMAKE_CURRENT_SOURCE_DIR}/runtime_deps/*.c
${CMAKE_CURRENT_SOURCE_DIR}/wifi_config/*.c ${CMAKE_CURRENT_SOURCE_DIR}/wifi_config/*.c
${CMAKE_CURRENT_SOURCE_DIR}/stream_debug/*.c ${CMAKE_CURRENT_SOURCE_DIR}/device_debug/*.c
) )
set(MODULES_SOURCES set(MODULES_SOURCES
...@@ -43,7 +43,7 @@ set(MODULES_INCLUDE_DIRS ...@@ -43,7 +43,7 @@ set(MODULES_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/webrtcpush ${CMAKE_CURRENT_SOURCE_DIR}/webrtcpush
${CMAKE_CURRENT_SOURCE_DIR}/runtime_deps ${CMAKE_CURRENT_SOURCE_DIR}/runtime_deps
${CMAKE_CURRENT_SOURCE_DIR}/wifi_config ${CMAKE_CURRENT_SOURCE_DIR}/wifi_config
${CMAKE_CURRENT_SOURCE_DIR}/stream_debug ${CMAKE_CURRENT_SOURCE_DIR}/device_debug
${WEBRTCPUSH_GST_INCLUDE_DIRS} ${WEBRTCPUSH_GST_INCLUDE_DIRS}
${WEBRTCPUSH_JSON_INCLUDE_DIRS} ${WEBRTCPUSH_JSON_INCLUDE_DIRS}
${WEBRTCPUSH_SOUP_INCLUDE_DIRS} ${WEBRTCPUSH_SOUP_INCLUDE_DIRS}
......
#include "stream_debug_agent.h" #include "device_debug_agent.h"
#include "stream_debug_config.h" #include "device_debug_config.h"
#include "browser_open.h" #include "browser_open.h"
#include "webrtcpush_run.h" #include "webrtcpush_run.h"
#include "wifi_config_agent.h" #include "wifi_config_agent.h"
......
#ifndef STREAM_DEBUG_AGENT_H__ #ifndef DEVICE_DEBUG_AGENT_H__
#define STREAM_DEBUG_AGENT_H__ #define DEVICE_DEBUG_AGENT_H__
#include <netinet/in.h> #include <netinet/in.h>
#include <cJSON.h> #include <cJSON.h>
......
This diff is collapsed.
#ifndef DEVICE_DIAG_H__
#define DEVICE_DIAG_H__
#include <netinet/in.h>
#include <cJSON.h>
/* ===== 设备调试协议 v4(UDP 18888) ===== */
/* 简易自检:检测 USB 摄像头/麦克风/喇叭/浏览器状态,回 device_check_response */
void device_diag_handle_check(int sock, const struct sockaddr_in *addr);
/* 浏览器控制:op="refresh" 刷新,op="reopen" 重开 */
void device_diag_handle_browser_ctrl(int sock, const struct sockaddr_in *addr, const char *op);
/* 日志获取:校验密码 fcrs666,启动 TCP 18889 传输线程,回 log_fetch_ready */
void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSON *root);
/* 判断是否为设备诊断 cmd(device_check / browser_refresh / browser_reopen / log_fetch) */
int device_diag_is_cmd(const char *cmd);
#endif
...@@ -45,6 +45,23 @@ static const GstNeed s_webrtc_gst_required[] = { ...@@ -45,6 +45,23 @@ static const GstNeed s_webrtc_gst_required[] = {
static gboolean s_webrtc_push_ok = FALSE; static gboolean s_webrtc_push_ok = FALSE;
static gboolean s_webrtc_push_disabled = FALSE; static gboolean s_webrtc_push_disabled = FALSE;
/* GLib/GObject/GStreamer 日志重定向到 zlog
* 平台底层库(libsoup/libnice/GStreamer)的 INFO/MESSAGE/DEBUG 级别
* 通常是 WebSocket 心跳帧/消息分发等 3s 一次的常规事件,
* 下沉到 debug 后 release 发布版(LOG_PRODUCTION)自动屏蔽,
* 避免日志污染。ERROR/WARNING 保留。 */
static void glib_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data)
{
(void)user_data;
if (log_level & G_LOG_LEVEL_ERROR)
my_zlog_error("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
else if (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
my_zlog_warn("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
else
my_zlog_debug("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
}
static const char *s_chromium_bins[] = { static const char *s_chromium_bins[] = {
"/usr/bin/chromium-browser", "/usr/bin/chromium-browser",
"/usr/bin/chromium", "/usr/bin/chromium",
...@@ -283,8 +300,11 @@ static gboolean gst_check_required_plugins(void) ...@@ -283,8 +300,11 @@ static gboolean gst_check_required_plugins(void)
static int ensure_gstreamer_plugins(void) static int ensure_gstreamer_plugins(void)
{ {
if (!gst_is_initialized()) if (!gst_is_initialized()) {
gst_init(NULL, NULL); gst_init(NULL, NULL);
/* 把 GLib/GObject/GStreamer 警告重定向到 zlog,避免污染终端 stderr */
g_log_set_default_handler(glib_log_handler, NULL);
}
if (gst_check_required_plugins()) { if (gst_check_required_plugins()) {
my_zlog_info("runtime_deps: GStreamer plugins OK"); my_zlog_info("runtime_deps: GStreamer plugins OK");
......
...@@ -62,7 +62,7 @@ AudioSource *audio_source_start(const char *alsa_device, char **error_message) ...@@ -62,7 +62,7 @@ AudioSource *audio_source_start(const char *alsa_device, char **error_message)
g_object_set(enc, g_object_set(enc,
"bitrate", WEBRTCPUSH_OPUS_BITRATE, "bitrate", WEBRTCPUSH_OPUS_BITRATE,
"cbr", TRUE, "bitrate-type", 0, /* 0=cbr */
"framesize", 20, /* 20ms 帧 */ "framesize", 20, /* 20ms 帧 */
"inband-fec", TRUE, "inband-fec", TRUE,
NULL); NULL);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#define MPP_VIDEO_START_BPS WEBRTCPUSH_INITIAL_BITRATE #define MPP_VIDEO_START_BPS WEBRTCPUSH_INITIAL_BITRATE
#define MPP_VIDEO_MIN_BPS WEBRTCPUSH_MIN_BITRATE #define MPP_VIDEO_MIN_BPS WEBRTCPUSH_MIN_BITRATE
#define MPP_VIDEO_MAX_BPS WEBRTCPUSH_MAX_BITRATE #define MPP_VIDEO_MAX_BPS WEBRTCPUSH_MAX_BITRATE
#define MPP_H264_PROFILE 66 /* Baseline:板端 mpph264enc 最稳 */ #define MPP_H264_PROFILE 66 /* Baseline matches negotiated 42e01f */
#define WRTC_V4L2_CAP_VIDEO_CAPTURE (1u << 0) #define WRTC_V4L2_CAP_VIDEO_CAPTURE (1u << 0)
#define WRTC_V4L2_CAP_VIDEO_CAPTURE_MPLANE (1u << 12) #define WRTC_V4L2_CAP_VIDEO_CAPTURE_MPLANE (1u << 12)
...@@ -35,6 +35,7 @@ struct MppH264Source { ...@@ -35,6 +35,7 @@ struct MppH264Source {
GstElement *parse; GstElement *parse;
GstElement *eq; GstElement *eq;
GstElement *appsink; GstElement *appsink;
GstElement *vsrc; /* v4l2src 指针,用于 recover 时更新设备 */
gboolean use_mpp; gboolean use_mpp;
guint8 *frame_buf; guint8 *frame_buf;
size_t frame_size; size_t frame_size;
...@@ -282,6 +283,8 @@ static void configure_v4l2_src(GstElement *src, gboolean compressed_mjpeg) ...@@ -282,6 +283,8 @@ static void configure_v4l2_src(GstElement *src, gboolean compressed_mjpeg)
* mmap 兼容性最好,性能损失可接受(720p@24fps)。 * mmap 兼容性最好,性能损失可接受(720p@24fps)。
*/ */
g_object_set(src, "io-mode", 2, NULL); g_object_set(src, "io-mode", 2, NULL);
/* 减少内核 DQBUF 缓冲,降低采集延迟 */
g_object_set(src, "buffer-size", 1024, NULL);
} }
static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test, static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
...@@ -399,18 +402,21 @@ static void request_keyframe(MppH264Source *src) ...@@ -399,18 +402,21 @@ static void request_keyframe(MppH264Source *src)
src->keyframe_req++; src->keyframe_req++;
src->pending_idr = TRUE; src->pending_idr = TRUE;
/* 兜底:直接向编码器发 force-key-unit,不依赖 enc_sink_probe 触发时机 */ /* 直接向编码器 sink pad 发 force-key-unit(upstream event 标准发送方式) */
if (src->enc) { if (src->enc) {
GstEvent *ev = gst_video_event_new_upstream_force_key_unit(
GST_CLOCK_TIME_NONE, TRUE, src->keyframe_req);
if (!gst_element_send_event(src->enc, ev)) {
GstPad *sink_pad = gst_element_get_static_pad(src->enc, "sink"); GstPad *sink_pad = gst_element_get_static_pad(src->enc, "sink");
if (sink_pad) { if (sink_pad) {
ev = gst_video_event_new_upstream_force_key_unit( GstEvent *ev = gst_video_event_new_upstream_force_key_unit(
GST_CLOCK_TIME_NONE, TRUE, src->keyframe_req); GST_CLOCK_TIME_NONE, TRUE, src->keyframe_req);
gst_pad_send_event(sink_pad, ev); gboolean ok = gst_pad_send_event(sink_pad, ev);
gst_object_unref(sink_pad); gst_object_unref(sink_pad);
} if (!ok)
my_zlog_warn("mpp_h264_source: force-key-unit send failed on sink pad");
} else {
/* fallback: element_send_event */
GstEvent *ev = gst_video_event_new_upstream_force_key_unit(
GST_CLOCK_TIME_NONE, TRUE, src->keyframe_req);
gst_element_send_event(src->enc, ev);
} }
} }
my_zlog_info("mpp_h264_source: IDR queued (%u)", src->keyframe_req); my_zlog_info("mpp_h264_source: IDR queued (%u)", src->keyframe_req);
...@@ -690,6 +696,7 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess ...@@ -690,6 +696,7 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
} }
} }
src->vsrc = vsrc;
src->pipeline = pipe; src->pipeline = pipe;
src->enc = enc; src->enc = enc;
src->parse = parse; src->parse = parse;
...@@ -881,6 +888,21 @@ gboolean mpp_h264_source_recover(MppH264Source *src) ...@@ -881,6 +888,21 @@ gboolean mpp_h264_source_recover(MppH264Source *src)
src->pending_idr = FALSE; src->pending_idr = FALSE;
src->pending_recover = FALSE; src->pending_recover = FALSE;
gst_element_set_state(src->pipeline, GST_STATE_NULL); gst_element_set_state(src->pipeline, GST_STATE_NULL);
/* 重新探测摄像头设备(USB 重枚举后可能从 video0 变成 video1/2) */
if (src->vsrc) {
gchar *new_dev = pick_v4l2_capture_device();
if (new_dev) {
gchar *old_dev = NULL;
g_object_get(src->vsrc, "device", &old_dev, NULL);
if (!old_dev || g_strcmp0(old_dev, new_dev) != 0) {
g_object_set(src->vsrc, "device", new_dev, NULL);
my_zlog_warn("mpp_h264_source: recover device changed %s -> %s",
old_dev ? old_dev : "(null)", new_dev);
}
g_free(old_dev);
g_free(new_dev);
}
}
g_usleep(300000); g_usleep(300000);
ret = gst_element_set_state(src->pipeline, GST_STATE_PLAYING); ret = gst_element_set_state(src->pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) { if (ret == GST_STATE_CHANGE_FAILURE) {
......
This diff is collapsed.
...@@ -7,26 +7,29 @@ ...@@ -7,26 +7,29 @@
* 1:走 libdatachannel 原生推流(摄像头 → MPP/x264 → libdatachannel) * 1:走 libdatachannel 原生推流(摄像头 → MPP/x264 → libdatachannel)
* *
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。 * 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* 音频尚未接入,视频跑通后再接 ALSA→Opus track。
*/ */
#define WEBRTCPUSH_USE_MPP 1 #define WEBRTCPUSH_USE_MPP 1
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */ /* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FPS 24 #define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_MIN_FPS 22 #define WEBRTCPUSH_H264_MIN_FPS 22
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS) /* 1s 一个 IDR */ #define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS * 3) /* 3s; PLI still requests IDR */
/* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */ /* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */
#define WEBRTCPUSH_INITIAL_BITRATE 1800000U #define WEBRTCPUSH_INITIAL_BITRATE 1400000U
#define WEBRTCPUSH_MIN_BITRATE 500000U #define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MAX_BITRATE 3200000U #define WEBRTCPUSH_MAX_BITRATE 3200000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 85U #define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 50000U /* 非急跌时 <50kbps 不调编码器 */ #define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 500U /* 升码更快恢复画质 */ #define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS 3U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 1000U #define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 108U #define WEBRTCPUSH_BITRATE_RAMP_UP_MS 2500U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 500U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 125U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U #define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_MAX_BITRATE 3750000U #define WEBRTCPUSH_PACING_MAX_BITRATE 4000000U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 300U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_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
...@@ -45,12 +48,12 @@ ...@@ -45,12 +48,12 @@
#define WEBRTCPUSH_H264_PROFILE_LEVEL_ID "42e01f" #define WEBRTCPUSH_H264_PROFILE_LEVEL_ID "42e01f"
/* 拉帧线程与发送线程之间的有界队列(leaky,满则丢最旧帧) */ /* 拉帧线程与发送线程之间的有界队列(leaky,满则丢最旧帧) */
#define WEBRTCPUSH_SEND_QUEUE_DEPTH 4U #define WEBRTCPUSH_SEND_QUEUE_DEPTH 2U
/* 管道缓冲:运动场景需适度缓存,过低易 pull timeout / 糊屏 */ /* 管道缓冲:运动场景需适度缓存,过低易 pull timeout / 糊屏 */
#define WEBRTCPUSH_MPP_PRE_ENC_BUFFERS 3 /* 编码前 leaky */ #define WEBRTCPUSH_MPP_PRE_ENC_BUFFERS 1 /* 编码前 leaky,低延迟 */
#define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 2 /* 编码后保留 AU */ #define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 1 /* 编码后保留 AU */
#define WEBRTCPUSH_APPSINK_MAX_BUFFERS 2 #define WEBRTCPUSH_APPSINK_MAX_BUFFERS 1
/* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */ /* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
#define WEBRTCPUSH_VIDEO_DEVICE "auto" #define WEBRTCPUSH_VIDEO_DEVICE "auto"
......
#include "wifi_config_agent.h" #include "wifi_config_agent.h"
#include "wifi_config_config.h" #include "wifi_config_config.h"
#include "stream_debug_agent.h" #include "device_debug_agent.h"
#include "device_diag.h"
#include "device_identity.h" #include "device_identity.h"
#include "device_fileopen.h" #include "device_fileopen.h"
#include "device_wifi_manager.h" #include "device_wifi_manager.h"
...@@ -60,6 +61,12 @@ void wifi_config_agent_rebind_socket(void) ...@@ -60,6 +61,12 @@ void wifi_config_agent_rebind_socket(void)
pthread_mutex_unlock(&s_wifi_config_sock_mutex); pthread_mutex_unlock(&s_wifi_config_sock_mutex);
} }
/* 设备诊断 cmd 转发判断(device_check / browser_refresh / browser_reopen / log_fetch) */
static int is_device_diag_cmd(const char *cmd)
{
return device_diag_is_cmd(cmd);
}
static int is_stream_debug_cmd(const char *cmd) static int is_stream_debug_cmd(const char *cmd)
{ {
return cmd && (strcmp(cmd, "stream_mode_query") == 0 || return cmd && (strcmp(cmd, "stream_mode_query") == 0 ||
...@@ -292,6 +299,31 @@ static void send_config_result(int sock, const struct sockaddr_in *addr, int suc ...@@ -292,6 +299,31 @@ static void send_config_result(int sock, const struct sockaddr_in *addr, int suc
cJSON_Delete(root); cJSON_Delete(root);
} }
/* config_ack:设备收到 config 后立即回复,表示已收到即将执行 */
static void send_config_ack(int sock, const struct sockaddr_in *addr, int accepted,
const char *action, const char *message)
{
cJSON *root = cJSON_CreateObject();
char *payload;
if (!root)
return;
cJSON_AddStringToObject(root, "cmd", "config_ack");
cJSON_AddBoolToObject(root, "accepted", accepted ? 1 : 0);
if (action && action[0])
cJSON_AddStringToObject(root, "action", action);
if (message && message[0])
cJSON_AddStringToObject(root, "message", message);
payload = cJSON_PrintUnformatted(root);
if (payload) {
send_json(sock, addr, payload);
free(payload);
}
cJSON_Delete(root);
}
static void handle_discover(int sock, const struct sockaddr_in *addr) static void handle_discover(int sock, const struct sockaddr_in *addr)
{ {
char device_id[WIFI_CONFIG_DEVICE_ID_MAX]; char device_id[WIFI_CONFIG_DEVICE_ID_MAX];
...@@ -327,6 +359,7 @@ static void handle_config(int sock, const struct sockaddr_in *addr, cJSON *root) ...@@ -327,6 +359,7 @@ static void handle_config(int sock, const struct sockaddr_in *addr, cJSON *root)
char message[WIFI_CONFIG_MESSAGE_MAX]; char message[WIFI_CONFIG_MESSAGE_MAX];
int success; int success;
/* 参数校验:ssid 非法时仍用旧协议回 config_result,保持兼容 */
if (!cJSON_IsString(ssid_item) || !ssid_item->valuestring || !ssid_item->valuestring[0]) { if (!cJSON_IsString(ssid_item) || !ssid_item->valuestring || !ssid_item->valuestring[0]) {
send_config_result(sock, addr, 0, "缺少有效的 ssid 字段"); send_config_result(sock, addr, 0, "缺少有效的 ssid 字段");
return; return;
...@@ -340,18 +373,138 @@ static void handle_config(int sock, const struct sockaddr_in *addr, cJSON *root) ...@@ -340,18 +373,138 @@ static void handle_config(int sock, const struct sockaddr_in *addr, cJSON *root)
my_zlog_info("WiFi配置助手: 收到配网请求 SSID=%s action=%s", ssid, action); my_zlog_info("WiFi配置助手: 收到配网请求 SSID=%s action=%s", ssid, action);
/* 新协议(v3):先立即回复 config_ack,表示已收到即将执行。
* 这样 action=connect 切换 WiFi 后,即使手机收不到最终结果,
* 也已通过 ack 知道设备收到了配置。 */
snprintf(message, sizeof(message), "%s",
strcmp(action, "connect") == 0
? "已收到配置,即将切换 WiFi"
: "已收到配置,即将保存");
send_config_ack(sock, addr, 1, action, message);
if (strcmp(action, "save") == 0) { if (strcmp(action, "save") == 0) {
/* save 模式:保存完成后回复 config_result(最终结果) */
success = orange_pi_save_wifi(ssid, password) == 0; success = orange_pi_save_wifi(ssid, password) == 0;
snprintf(message, sizeof(message), "%s", snprintf(message, sizeof(message), "%s",
success ? "WiFi 配置已保存" : "WiFi 保存失败"); success ? "WiFi 配置已保存" : "WiFi 保存失败");
my_zlog_info("WiFi配置助手: 配网结果 success=%d, message=%s", success, message);
send_config_result(sock, addr, success, message);
} else { } else {
/* connect 模式:只回 config_ack 即可,切 WiFi 后手机收不到最终结果,
* 不再回复 config_result,避免无意义发送。 */
success = orange_pi_connect_wifi(ssid, password) == 0; success = orange_pi_connect_wifi(ssid, password) == 0;
snprintf(message, sizeof(message), "%s", my_zlog_info("WiFi配置助手: connect 已执行 success=%d (不回 config_result)", success);
success ? "WiFi 配置已保存并连接成功" : "WiFi 连接失败");
} }
}
my_zlog_info("WiFi配置助手: 配网结果 success=%d, message=%s", success, message); static void handle_wifi_list_query(int sock, const struct sockaddr_in *addr)
send_config_result(sock, addr, success, message); {
cJSON *root = cJSON_CreateObject();
cJSON *list;
char *payload;
if (!root)
return;
cJSON_AddStringToObject(root, "cmd", "wifi_list_response");
list = app_wifi_list_query();
if (!list)
list = cJSON_CreateArray();
cJSON_AddItemToObject(root, "list", list);
payload = cJSON_PrintUnformatted(root);
if (payload) {
send_json(sock, addr, payload);
free(payload);
}
cJSON_Delete(root);
my_zlog_info("WiFi配置助手: 应答 wifi_list_query");
}
static void handle_wifi_switch(int sock, const struct sockaddr_in *addr, cJSON *root)
{
cJSON *ssid_item = cJSON_GetObjectItemCaseSensitive(root, "ssid");
cJSON *resp;
const char *ssid = NULL;
char message[WIFI_CONFIG_MESSAGE_MAX];
int success;
int rc;
char *payload;
if (!cJSON_IsString(ssid_item) || !ssid_item->valuestring || !ssid_item->valuestring[0]) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "cmd", "wifi_switch_result");
cJSON_AddBoolToObject(resp, "success", 0);
cJSON_AddStringToObject(resp, "message", "缺少有效的 ssid 字段");
payload = cJSON_PrintUnformatted(resp);
if (payload) { send_json(sock, addr, payload); free(payload); }
cJSON_Delete(resp);
return;
}
ssid = ssid_item->valuestring;
my_zlog_info("WiFi配置助手: 收到 wifi_switch SSID=%s", ssid);
rc = app_wifi_switch(ssid);
success = (rc == 0);
if (rc == 0)
snprintf(message, sizeof(message), "WiFi 切换成功,正在连接 %s", ssid);
else if (rc == -1)
snprintf(message, sizeof(message), "未找到已保存的 WiFi:%s", ssid);
else /* rc == -2:已保存但连接失败 */
snprintf(message, sizeof(message), "WiFi 已保存但连接失败:%s(信号弱或不可达)", ssid);
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "cmd", "wifi_switch_result");
cJSON_AddBoolToObject(resp, "success", success ? 1 : 0);
cJSON_AddStringToObject(resp, "message", message);
payload = cJSON_PrintUnformatted(resp);
if (payload) { send_json(sock, addr, payload); free(payload); }
cJSON_Delete(resp);
}
static void handle_wifi_delete(int sock, const struct sockaddr_in *addr, cJSON *root)
{
cJSON *ssid_item = cJSON_GetObjectItemCaseSensitive(root, "ssid");
cJSON *resp;
const char *ssid = NULL;
char message[WIFI_CONFIG_MESSAGE_MAX];
int success;
int rc;
char *payload;
if (!cJSON_IsString(ssid_item) || !ssid_item->valuestring || !ssid_item->valuestring[0]) {
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "cmd", "wifi_delete_result");
cJSON_AddBoolToObject(resp, "success", 0);
cJSON_AddStringToObject(resp, "message", "缺少有效的 ssid 字段");
payload = cJSON_PrintUnformatted(resp);
if (payload) { send_json(sock, addr, payload); free(payload); }
cJSON_Delete(resp);
return;
}
ssid = ssid_item->valuestring;
my_zlog_info("WiFi配置助手: 收到 wifi_delete SSID=%s", ssid);
rc = app_wifi_delete(ssid);
success = (rc == 0);
if (rc == 0)
snprintf(message, sizeof(message), "已删除 WiFi:%s", ssid);
else if (rc == -2)
snprintf(message, sizeof(message), "受保护的 WiFi 不可删除:%s", ssid);
else
snprintf(message, sizeof(message), "未找到或正在使用的 WiFi:%s", ssid);
resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "cmd", "wifi_delete_result");
cJSON_AddBoolToObject(resp, "success", success ? 1 : 0);
cJSON_AddStringToObject(resp, "message", message);
payload = cJSON_PrintUnformatted(resp);
if (payload) { send_json(sock, addr, payload); free(payload); }
cJSON_Delete(resp);
} }
static void process_message(int sock, const char *buf, const struct sockaddr_in *addr) static void process_message(int sock, const char *buf, const struct sockaddr_in *addr)
...@@ -384,6 +537,20 @@ static void process_message(int sock, const char *buf, const struct sockaddr_in ...@@ -384,6 +537,20 @@ static void process_message(int sock, const char *buf, const struct sockaddr_in
stream_debug_handle_url_update(sock, addr, root); stream_debug_handle_url_update(sock, addr, root);
} else if (strcmp(cmd_item->valuestring, "stream_url_query") == 0) { } else if (strcmp(cmd_item->valuestring, "stream_url_query") == 0) {
stream_debug_handle_url_query(sock, addr); stream_debug_handle_url_query(sock, addr);
} else if (strcmp(cmd_item->valuestring, "wifi_list_query") == 0) {
handle_wifi_list_query(sock, addr);
} else if (strcmp(cmd_item->valuestring, "wifi_switch") == 0) {
handle_wifi_switch(sock, addr, root);
} else if (strcmp(cmd_item->valuestring, "wifi_delete") == 0) {
handle_wifi_delete(sock, addr, root);
} else if (strcmp(cmd_item->valuestring, "device_check") == 0) {
device_diag_handle_check(sock, addr);
} else if (strcmp(cmd_item->valuestring, "browser_refresh") == 0) {
device_diag_handle_browser_ctrl(sock, addr, "refresh");
} else if (strcmp(cmd_item->valuestring, "browser_reopen") == 0) {
device_diag_handle_browser_ctrl(sock, addr, "reopen");
} else if (strcmp(cmd_item->valuestring, "log_fetch") == 0) {
device_diag_handle_log_fetch(sock, addr, root);
} }
cJSON_Delete(root); cJSON_Delete(root);
......
...@@ -26,6 +26,7 @@ public: ...@@ -26,6 +26,7 @@ public:
PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval); PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval);
void setBitrate(double bitsPerSecond); void setBitrate(double bitsPerSecond);
size_t clear(); size_t clear();
void getQueueStats(size_t &packetCount, size_t &byteCount);
void outgoing(message_vector &messages, const message_callback &send) override; void outgoing(message_vector &messages, const message_callback &send) override;
...@@ -40,6 +41,7 @@ private: ...@@ -40,6 +41,7 @@ private:
std::mutex mMutex; std::mutex mMutex;
std::queue<message_ptr> mRtpBuffer; std::queue<message_ptr> mRtpBuffer;
size_t mQueuedBytes = 0;
void schedule(const message_callback &send); void schedule(const message_callback &send);
}; };
......
...@@ -434,6 +434,10 @@ RTC_C_EXPORT int rtcSetPacingBitrate(int tr, unsigned int bitrate); ...@@ -434,6 +434,10 @@ RTC_C_EXPORT int rtcSetPacingBitrate(int tr, unsigned int bitrate);
// Returns the number of dropped packets, or a negative RTC_ERR_* value. // Returns the number of dropped packets, or a negative RTC_ERR_* value.
RTC_C_EXPORT int rtcClearPacingQueue(int tr); RTC_C_EXPORT int rtcClearPacingQueue(int tr);
// Read the current pacing queue without modifying it.
RTC_C_EXPORT int rtcGetPacingQueueStats(int tr, unsigned int *packetCount,
unsigned int *byteCount);
// Transform seconds to timestamp using track's clock rate, result is written to timestamp // Transform seconds to timestamp using track's clock rate, result is written to timestamp
RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp); RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
......
...@@ -1482,6 +1482,31 @@ int rtcClearPacingQueue(int tr) { ...@@ -1482,6 +1482,31 @@ int rtcClearPacingQueue(int tr) {
}); });
} }
int rtcGetPacingQueueStats(int tr, unsigned int *packetCount, unsigned int *byteCount) {
return wrap([&] {
shared_ptr<PacingHandler> handler;
{
std::lock_guard lock(mutex);
auto it = pacingHandlerMap.find(tr);
if (it == pacingHandlerMap.end())
throw std::invalid_argument("Pacing handler does not exist");
handler = it->second;
}
size_t packets = 0;
size_t bytes = 0;
handler->getQueueStats(packets, bytes);
if (packetCount)
*packetCount = packets > std::numeric_limits<unsigned int>::max()
? std::numeric_limits<unsigned int>::max()
: static_cast<unsigned int>(packets);
if (byteCount)
*byteCount = bytes > std::numeric_limits<unsigned int>::max()
? std::numeric_limits<unsigned int>::max()
: static_cast<unsigned int>(bytes);
return RTC_ERR_SUCCESS;
});
}
int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) { int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
return wrap([&] { return wrap([&] {
auto config = getRtpConfig(id); auto config = getRtpConfig(id);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#if RTC_ENABLE_MEDIA #if RTC_ENABLE_MEDIA
#include <algorithm>
#include <memory> #include <memory>
#include "pacinghandler.hpp" #include "pacinghandler.hpp"
...@@ -29,10 +30,17 @@ size_t PacingHandler::clear() { ...@@ -29,10 +30,17 @@ size_t PacingHandler::clear() {
const size_t count = mRtpBuffer.size(); const size_t count = mRtpBuffer.size();
while (!mRtpBuffer.empty()) while (!mRtpBuffer.empty())
mRtpBuffer.pop(); mRtpBuffer.pop();
mQueuedBytes = 0;
mBudget = 0.; mBudget = 0.;
return count; return count;
} }
void PacingHandler::getQueueStats(size_t &packetCount, size_t &byteCount) {
std::lock_guard<std::mutex> lock(mMutex);
packetCount = mRtpBuffer.size();
byteCount = mQueuedBytes;
}
void PacingHandler::schedule(const message_callback &send) { void PacingHandler::schedule(const message_callback &send) {
if (mHaveScheduled.exchange(true)) { if (mHaveScheduled.exchange(true)) {
return; return;
...@@ -44,21 +52,30 @@ void PacingHandler::schedule(const message_callback &send) { ...@@ -44,21 +52,30 @@ void PacingHandler::schedule(const message_callback &send) {
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
mHaveScheduled.store(false); mHaveScheduled.store(false);
// Update the budget and cap it // Keep credit for short scheduler stalls. Capping at one interval
// permanently under-runs the configured bitrate whenever the worker
// wakes up late, which turns scheduler jitter into growing media delay.
const double bytesPerSecond = mBytesPerSecond.load(); const double bytesPerSecond = mBytesPerSecond.load();
const auto now = std::chrono::high_resolution_clock::now();
auto newBudget = auto newBudget =
std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - mLastRun) std::chrono::duration<double>(now - mLastRun)
.count() * .count() *
bytesPerSecond; bytesPerSecond;
auto maxBudget = std::chrono::duration<double>(mSendInterval).count() * bytesPerSecond; const auto catchUpWindow = std::max(mSendInterval * 5,
std::chrono::milliseconds(25));
auto maxBudget =
std::chrono::duration<double>(catchUpWindow).count() * bytesPerSecond;
mBudget = std::min(mBudget + newBudget, maxBudget); mBudget = std::min(mBudget + newBudget, maxBudget);
mLastRun = std::chrono::high_resolution_clock::now(); mLastRun = now;
// Send packets while there is budget, allow a single partial packet over budget // Send packets while there is budget, allow a single partial packet over budget
while (!mRtpBuffer.empty() && mBudget > 0) { while (!mRtpBuffer.empty() && mBudget > 0) {
auto size = int(mRtpBuffer.front()->size()); auto size = int(mRtpBuffer.front()->size());
send(std::move(mRtpBuffer.front())); send(std::move(mRtpBuffer.front()));
mRtpBuffer.pop(); mRtpBuffer.pop();
mQueuedBytes = mQueuedBytes >= static_cast<size_t>(size)
? mQueuedBytes - static_cast<size_t>(size)
: 0;
mBudget -= size; mBudget -= size;
} }
...@@ -74,6 +91,7 @@ void PacingHandler::outgoing(message_vector &messages, const message_callback &s ...@@ -74,6 +91,7 @@ void PacingHandler::outgoing(message_vector &messages, const message_callback &s
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
for (auto &m : messages) { for (auto &m : messages) {
mQueuedBytes += m->size();
mRtpBuffer.push(std::move(m)); mRtpBuffer.push(std::move(m));
} }
messages.clear(); messages.clear();
......
...@@ -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-02.log"; millisecond my_log.* "/home/orangepi/car/master/log/log_2026-07-06.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