Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
car-controlserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wenzhongjian
car-controlserver
Commits
d92f8afb
Commit
d92f8afb
authored
Jul 06, 2026
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
加入了跟app的通信,网络配置那些优化
parent
6edd4759
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
1341 additions
and
87 deletions
+1341
-87
device_wifi_manager.c
app/device_change/device_wifi_manager.c
+144
-0
device_wifi_manager.h
app/device_change/device_wifi_manager.h
+16
-0
main
build/main
+0
-0
modules_common.h
include/modules_common.h
+2
-1
CMakeLists.txt
modules/CMakeLists.txt
+2
-2
device_debug_agent.c
modules/device_debug/device_debug_agent.c
+2
-2
device_debug_agent.h
modules/device_debug/device_debug_agent.h
+2
-2
device_debug_config.h
modules/device_debug/device_debug_config.h
+0
-0
device_diag.c
modules/device_debug/device_diag.c
+607
-0
device_diag.h
modules/device_debug/device_diag.h
+21
-0
runtime_deps.c
modules/runtime_deps/runtime_deps.c
+21
-1
audio_source.c
modules/webrtcpush/audio_source.c
+1
-1
mpp_h264_source.c
modules/webrtcpush/mpp_h264_source.c
+30
-8
rtc_client.c
modules/webrtcpush/rtc_client.c
+251
-47
webrtcpush_config.h
modules/webrtcpush/webrtcpush_config.h
+16
-13
wifi_config_agent.c
modules/wifi_config/wifi_config_agent.c
+172
-5
pacinghandler.hpp
third_party/libdatachannel/include/rtc/pacinghandler.hpp
+2
-0
rtc.h
third_party/libdatachannel/include/rtc/rtc.h
+4
-0
capi.cpp
third_party/libdatachannel/src/capi.cpp
+25
-0
pacinghandler.cpp
third_party/libdatachannel/src/pacinghandler.cpp
+22
-4
zlog.conf
zlog.conf
+1
-1
No files found.
app/device_change/device_wifi_manager.c
View file @
d92f8afb
...
@@ -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
;
}
app/device_change/device_wifi_manager.h
View file @
d92f8afb
...
@@ -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
build/main
View file @
d92f8afb
No preview for this file type
include/modules_common.h
View file @
d92f8afb
...
@@ -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
modules/CMakeLists.txt
View file @
d92f8afb
...
@@ -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
}
...
...
modules/
stream_debug/stream
_debug_agent.c
→
modules/
device_debug/device
_debug_agent.c
View file @
d92f8afb
#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"
...
...
modules/
stream_debug/stream
_debug_agent.h
→
modules/
device_debug/device
_debug_agent.h
View file @
d92f8afb
#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>
...
...
modules/
stream_debug/stream
_debug_config.h
→
modules/
device_debug/device
_debug_config.h
View file @
d92f8afb
File moved
modules/device_debug/device_diag.c
0 → 100644
View file @
d92f8afb
#include "device_diag.h"
#include "device_debug_config.h"
#include "browser_open.h"
#include "mylog.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>
#define DIAG_LOG_PASSWORD "fcrs666"
#define DIAG_LOG_DIR "/home/orangepi/car/master/log"
#define DIAG_LOG_TCP_PORT 18889
#define DIAG_LOG_MAX_FILES 2
#define DIAG_LOG_FILENAME_MAX 128
#define DIAG_LOG_PATH_MAX 512
#define DIAG_BROWSER_URL_MAX 512
#define DIAG_MESSAGE_MAX 256
static
void
diag_send_json
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
const
char
*
json
)
{
if
(
!
json
)
return
;
sendto
(
sock
,
json
,
strlen
(
json
),
0
,
(
const
struct
sockaddr
*
)
addr
,
sizeof
(
*
addr
));
}
/* ===== 简易自检 ===== */
static
int
diag_check_camera
(
char
*
name
,
size_t
name_sz
,
char
*
msg
,
size_t
msg_sz
)
{
DIR
*
dir
;
struct
dirent
*
ent
;
int
found
=
0
;
char
path
[
64
];
dir
=
opendir
(
"/dev"
);
if
(
!
dir
)
{
snprintf
(
name
,
name_sz
,
"未知"
);
snprintf
(
msg
,
msg_sz
,
"无法打开 /dev"
);
return
0
;
}
while
((
ent
=
readdir
(
dir
))
!=
NULL
)
{
if
(
strncmp
(
ent
->
d_name
,
"video"
,
5
)
!=
0
)
continue
;
if
(
strstr
(
ent
->
d_name
,
"dec0"
)
||
strstr
(
ent
->
d_name
,
"enc0"
))
continue
;
size_t
len
=
strlen
(
ent
->
d_name
);
if
(
len
<
6
)
continue
;
int
is_num
=
1
;
for
(
size_t
i
=
5
;
i
<
len
;
i
++
)
{
if
(
ent
->
d_name
[
i
]
<
'0'
||
ent
->
d_name
[
i
]
>
'9'
)
{
is_num
=
0
;
break
;
}
}
if
(
!
is_num
)
continue
;
snprintf
(
path
,
sizeof
(
path
),
"/dev/%s"
,
ent
->
d_name
);
int
fd
=
open
(
path
,
O_RDWR
);
if
(
fd
>=
0
)
{
close
(
fd
);
snprintf
(
name
,
name_sz
,
"USB Camera"
);
snprintf
(
msg
,
msg_sz
,
"/dev/%s"
,
ent
->
d_name
);
found
=
1
;
break
;
}
}
closedir
(
dir
);
if
(
!
found
)
{
snprintf
(
name
,
name_sz
,
"未检测到"
);
snprintf
(
msg
,
msg_sz
,
"无可用 /dev/video* 设备"
);
}
return
found
;
}
static
int
diag_check_mic
(
char
*
name
,
size_t
name_sz
,
char
*
msg
,
size_t
msg_sz
)
{
FILE
*
fp
;
char
line
[
512
];
int
found
=
0
;
fp
=
popen
(
"arecord -l 2>/dev/null"
,
"r"
);
if
(
!
fp
)
{
snprintf
(
name
,
name_sz
,
"未知"
);
snprintf
(
msg
,
msg_sz
,
"arecord 不可用"
);
return
0
;
}
while
(
fgets
(
line
,
sizeof
(
line
),
fp
))
{
if
(
strstr
(
line
,
"card"
)
&&
strstr
(
line
,
"USB Audio"
))
{
char
*
p
=
strstr
(
line
,
"card"
);
snprintf
(
name
,
name_sz
,
"USB Audio"
);
snprintf
(
msg
,
msg_sz
,
"%s"
,
p
?
p
:
"USB Audio"
);
msg
[
strcspn
(
msg
,
"
\n
"
)]
=
'\0'
;
found
=
1
;
break
;
}
}
pclose
(
fp
);
if
(
!
found
)
{
snprintf
(
name
,
name_sz
,
"未检测到"
);
snprintf
(
msg
,
msg_sz
,
"无 USB Audio 设备"
);
}
return
found
;
}
static
int
diag_check_speaker
(
char
*
name
,
size_t
name_sz
,
char
*
msg
,
size_t
msg_sz
)
{
FILE
*
fp
;
char
line
[
512
];
int
found
=
0
;
fp
=
popen
(
"aplay -l 2>/dev/null"
,
"r"
);
if
(
!
fp
)
{
snprintf
(
name
,
name_sz
,
"未知"
);
snprintf
(
msg
,
msg_sz
,
"aplay 不可用"
);
return
0
;
}
while
(
fgets
(
line
,
sizeof
(
line
),
fp
))
{
if
(
strstr
(
line
,
"card"
)
&&
(
strstr
(
line
,
"rk809"
)
||
strstr
(
line
,
"analog"
)
||
strstr
(
line
,
"hdmi"
)
||
strstr
(
line
,
"USB Audio"
)))
{
char
*
p
=
strstr
(
line
,
"card"
);
snprintf
(
name
,
name_sz
,
"Audio Device"
);
snprintf
(
msg
,
msg_sz
,
"%s"
,
p
?
p
:
"Audio Device"
);
msg
[
strcspn
(
msg
,
"
\n
"
)]
=
'\0'
;
found
=
1
;
break
;
}
}
pclose
(
fp
);
if
(
!
found
)
{
snprintf
(
name
,
name_sz
,
"未检测到"
);
snprintf
(
msg
,
msg_sz
,
"无音频输出设备"
);
}
return
found
;
}
static
int
diag_check_browser
(
char
*
name
,
size_t
name_sz
,
char
*
msg
,
size_t
msg_sz
,
char
*
url
,
size_t
url_sz
)
{
bool
running
=
is_browser_running
();
if
(
running
)
{
snprintf
(
name
,
name_sz
,
"Chromium"
);
snprintf
(
msg
,
name_sz
,
"running"
);
char
tmp_url
[
DIAG_BROWSER_URL_MAX
]
=
{
0
};
bool
tmp_running
=
false
;
browser_get_stream_url_info
(
tmp_url
,
sizeof
(
tmp_url
),
&
tmp_running
);
snprintf
(
url
,
url_sz
,
"%s"
,
tmp_url
);
}
else
{
snprintf
(
name
,
name_sz
,
"未运行"
);
snprintf
(
msg
,
msg_sz
,
"chromium 进程不存在"
);
url
[
0
]
=
'\0'
;
}
return
running
?
1
:
0
;
}
void
device_diag_handle_check
(
int
sock
,
const
struct
sockaddr_in
*
addr
)
{
cJSON
*
resp
;
cJSON
*
item
;
char
*
payload
;
char
cam_name
[
64
],
cam_msg
[
128
];
char
mic_name
[
64
],
mic_msg
[
128
];
char
spk_name
[
64
],
spk_msg
[
128
];
char
brw_name
[
64
],
brw_msg
[
128
];
char
brw_url
[
DIAG_BROWSER_URL_MAX
];
int
cam_ok
=
diag_check_camera
(
cam_name
,
sizeof
(
cam_name
),
cam_msg
,
sizeof
(
cam_msg
));
int
mic_ok
=
diag_check_mic
(
mic_name
,
sizeof
(
mic_name
),
mic_msg
,
sizeof
(
mic_msg
));
int
spk_ok
=
diag_check_speaker
(
spk_name
,
sizeof
(
spk_name
),
spk_msg
,
sizeof
(
spk_msg
));
int
brw_ok
=
diag_check_browser
(
brw_name
,
sizeof
(
brw_name
),
brw_msg
,
sizeof
(
brw_msg
),
brw_url
,
sizeof
(
brw_url
));
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"device_check_response"
);
item
=
cJSON_CreateObject
();
cJSON_AddBoolToObject
(
item
,
"detected"
,
cam_ok
?
1
:
0
);
cJSON_AddStringToObject
(
item
,
"name"
,
cam_name
);
cJSON_AddStringToObject
(
item
,
"message"
,
cam_msg
);
cJSON_AddItemToObject
(
resp
,
"camera"
,
item
);
item
=
cJSON_CreateObject
();
cJSON_AddBoolToObject
(
item
,
"detected"
,
mic_ok
?
1
:
0
);
cJSON_AddStringToObject
(
item
,
"name"
,
mic_name
);
cJSON_AddStringToObject
(
item
,
"message"
,
mic_msg
);
cJSON_AddItemToObject
(
resp
,
"mic"
,
item
);
item
=
cJSON_CreateObject
();
cJSON_AddBoolToObject
(
item
,
"detected"
,
spk_ok
?
1
:
0
);
cJSON_AddStringToObject
(
item
,
"name"
,
spk_name
);
cJSON_AddStringToObject
(
item
,
"message"
,
spk_msg
);
cJSON_AddItemToObject
(
resp
,
"speaker"
,
item
);
item
=
cJSON_CreateObject
();
cJSON_AddBoolToObject
(
item
,
"detected"
,
brw_ok
?
1
:
0
);
cJSON_AddStringToObject
(
item
,
"name"
,
brw_name
);
cJSON_AddStringToObject
(
item
,
"message"
,
brw_msg
);
cJSON_AddItemToObject
(
resp
,
"browser"
,
item
);
if
(
brw_url
[
0
])
cJSON_AddStringToObject
(
resp
,
"browserUrl"
,
brw_url
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
my_zlog_info
(
"设备调试: 自检完成 cam=%d mic=%d spk=%d brw=%d"
,
cam_ok
,
mic_ok
,
spk_ok
,
brw_ok
);
}
/* ===== 浏览器控制 ===== */
void
device_diag_handle_browser_ctrl
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
const
char
*
op
)
{
cJSON
*
resp
;
char
*
payload
;
int
success
=
0
;
const
char
*
msg
=
""
;
const
char
*
cmd_name
;
resp
=
cJSON_CreateObject
();
if
(
strcmp
(
op
,
"refresh"
)
==
0
)
{
cmd_name
=
"browser_refresh_result"
;
if
(
!
is_browser_running
())
{
success
=
0
;
msg
=
"刷新失败,浏览器未运行"
;
}
else
{
refresh_cam
();
success
=
1
;
msg
=
"浏览器已刷新"
;
}
}
else
if
(
strcmp
(
op
,
"reopen"
)
==
0
)
{
cmd_name
=
"browser_reopen_result"
;
char
url
[
DIAG_BROWSER_URL_MAX
]
=
{
0
};
char
effective
[
DIAG_BROWSER_URL_MAX
]
=
{
0
};
bool
running
=
false
;
browser_get_stream_url_info
(
url
,
sizeof
(
url
),
&
running
);
if
(
!
running
||
!
url
[
0
])
{
success
=
0
;
msg
=
"重开失败,浏览器未运行或无 URL"
;
}
else
{
browser_begin_external_switch
();
{
int
_r
=
system
(
"pkill -f chromium-browser 2>/dev/null"
);
(
void
)
_r
;
}
usleep
(
800000
);
if
(
browser_switch_stream_url
(
url
,
effective
,
sizeof
(
effective
))
==
0
)
{
success
=
1
;
msg
=
"浏览器已重新打开"
;
}
else
{
success
=
0
;
msg
=
"重开失败,URL 无效"
;
}
browser_end_external_switch
();
}
}
else
{
cmd_name
=
"browser_refresh_result"
;
success
=
0
;
msg
=
"未知操作"
;
}
cJSON_AddStringToObject
(
resp
,
"cmd"
,
cmd_name
);
cJSON_AddBoolToObject
(
resp
,
"success"
,
success
?
1
:
0
);
cJSON_AddStringToObject
(
resp
,
"message"
,
msg
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
my_zlog_info
(
"设备调试: 浏览器控制 op=%s success=%d"
,
op
,
success
);
}
/* ===== 日志获取 ===== */
typedef
struct
{
char
filename
[
DIAG_LOG_FILENAME_MAX
];
long
size
;
time_t
mtime
;
}
log_entry_t
;
static
int
diag_log_compare_mtime
(
const
void
*
a
,
const
void
*
b
)
{
const
log_entry_t
*
ea
=
(
const
log_entry_t
*
)
a
;
const
log_entry_t
*
eb
=
(
const
log_entry_t
*
)
b
;
if
(
ea
->
mtime
>
eb
->
mtime
)
return
-
1
;
if
(
ea
->
mtime
<
eb
->
mtime
)
return
1
;
return
0
;
}
static
int
diag_find_recent_logs
(
log_entry_t
*
entries
,
int
max_count
)
{
DIR
*
dir
;
struct
dirent
*
ent
;
struct
stat
st
;
char
filepath
[
DIAG_LOG_PATH_MAX
];
log_entry_t
all
[
32
];
int
count
=
0
;
dir
=
opendir
(
DIAG_LOG_DIR
);
if
(
!
dir
)
{
my_zlog_error
(
"设备调试: 无法打开日志目录 %s"
,
DIAG_LOG_DIR
);
return
0
;
}
while
((
ent
=
readdir
(
dir
))
!=
NULL
&&
count
<
32
)
{
if
(
strncmp
(
ent
->
d_name
,
"log_"
,
4
)
!=
0
)
continue
;
if
(
!
strstr
(
ent
->
d_name
,
".log"
))
continue
;
snprintf
(
filepath
,
sizeof
(
filepath
),
"%s/%s"
,
DIAG_LOG_DIR
,
ent
->
d_name
);
if
(
stat
(
filepath
,
&
st
)
!=
0
)
continue
;
strncpy
(
all
[
count
].
filename
,
ent
->
d_name
,
DIAG_LOG_FILENAME_MAX
-
1
);
all
[
count
].
filename
[
DIAG_LOG_FILENAME_MAX
-
1
]
=
'\0'
;
all
[
count
].
size
=
(
long
)
st
.
st_size
;
all
[
count
].
mtime
=
st
.
st_mtime
;
count
++
;
}
closedir
(
dir
);
if
(
count
==
0
)
return
0
;
qsort
(
all
,
count
,
sizeof
(
log_entry_t
),
diag_log_compare_mtime
);
if
(
count
>
max_count
)
count
=
max_count
;
for
(
int
i
=
0
;
i
<
count
;
i
++
)
entries
[
i
]
=
all
[
i
];
return
count
;
}
static
void
*
diag_log_tcp_thread
(
void
*
arg
)
{
log_entry_t
*
entries
=
(
log_entry_t
*
)
arg
;
int
server_fd
,
client_fd
;
struct
sockaddr_in
addr
;
int
opt
=
1
;
char
filename
[
DIAG_LOG_FILENAME_MAX
+
2
];
server_fd
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
if
(
server_fd
<
0
)
{
my_zlog_error
(
"设备调试: TCP socket 创建失败"
);
free
(
entries
);
return
NULL
;
}
setsockopt
(
server_fd
,
SOL_SOCKET
,
SO_REUSEADDR
,
&
opt
,
sizeof
(
opt
));
memset
(
&
addr
,
0
,
sizeof
(
addr
));
addr
.
sin_family
=
AF_INET
;
addr
.
sin_port
=
htons
(
DIAG_LOG_TCP_PORT
);
addr
.
sin_addr
.
s_addr
=
INADDR_ANY
;
if
(
bind
(
server_fd
,
(
struct
sockaddr
*
)
&
addr
,
sizeof
(
addr
))
<
0
)
{
my_zlog_error
(
"设备调试: TCP bind %d 失败"
,
DIAG_LOG_TCP_PORT
);
close
(
server_fd
);
free
(
entries
);
return
NULL
;
}
if
(
listen
(
server_fd
,
1
)
<
0
)
{
my_zlog_error
(
"设备调试: TCP listen 失败"
);
close
(
server_fd
);
free
(
entries
);
return
NULL
;
}
my_zlog_info
(
"设备调试: TCP 日志服务已启动,等待连接 port=%d"
,
DIAG_LOG_TCP_PORT
);
struct
timeval
tv
;
tv
.
tv_sec
=
30
;
tv
.
tv_usec
=
0
;
setsockopt
(
server_fd
,
SOL_SOCKET
,
SO_RCVTIMEO
,
&
tv
,
sizeof
(
tv
));
client_fd
=
accept
(
server_fd
,
NULL
,
NULL
);
if
(
client_fd
<
0
)
{
my_zlog_error
(
"设备调试: TCP accept 超时或失败"
);
close
(
server_fd
);
free
(
entries
);
return
NULL
;
}
while
(
1
)
{
ssize_t
n
;
size_t
flen
=
0
;
int
c
;
char
ch
;
char
filepath
[
DIAG_LOG_PATH_MAX
];
FILE
*
f
;
long
fsize
;
uint8_t
sizebuf
[
8
];
char
buf
[
4096
];
size_t
r
;
while
(
flen
<
sizeof
(
filename
)
-
1
)
{
n
=
recv
(
client_fd
,
&
ch
,
1
,
0
);
if
(
n
<=
0
)
{
close
(
client_fd
);
close
(
server_fd
);
free
(
entries
);
my_zlog_info
(
"设备调试: TCP 日志传输完成"
);
return
NULL
;
}
if
(
ch
==
'\n'
)
break
;
filename
[
flen
++
]
=
ch
;
}
filename
[
flen
]
=
'\0'
;
int
valid
=
1
;
for
(
size_t
i
=
0
;
i
<
flen
;
i
++
)
{
c
=
(
unsigned
char
)
filename
[
i
];
if
(
!
((
c
>=
'a'
&&
c
<=
'z'
)
||
(
c
>=
'A'
&&
c
<=
'Z'
)
||
(
c
>=
'0'
&&
c
<=
'9'
)
||
c
==
'_'
||
c
==
'.'
))
{
valid
=
0
;
break
;
}
}
if
(
!
valid
||
flen
==
0
)
{
my_zlog_error
(
"设备调试: 非法文件名请求 '%s'"
,
filename
);
memset
(
sizebuf
,
0
,
8
);
send
(
client_fd
,
sizebuf
,
8
,
0
);
continue
;
}
snprintf
(
filepath
,
sizeof
(
filepath
),
"%s/%s"
,
DIAG_LOG_DIR
,
filename
);
f
=
fopen
(
filepath
,
"rb"
);
if
(
!
f
)
{
my_zlog_error
(
"设备调试: 日志文件不存在 %s"
,
filepath
);
memset
(
sizebuf
,
0
,
8
);
send
(
client_fd
,
sizebuf
,
8
,
0
);
continue
;
}
fseek
(
f
,
0
,
SEEK_END
);
fsize
=
ftell
(
f
);
rewind
(
f
);
uint64_t
sz
=
(
uint64_t
)
fsize
;
for
(
int
i
=
7
;
i
>=
0
;
i
--
)
{
sizebuf
[
i
]
=
(
uint8_t
)(
sz
&
0xFF
);
sz
>>=
8
;
}
send
(
client_fd
,
sizebuf
,
8
,
0
);
while
((
r
=
fread
(
buf
,
1
,
sizeof
(
buf
),
f
))
>
0
)
{
size_t
sent
=
0
;
while
(
sent
<
r
)
{
ssize_t
s
=
send
(
client_fd
,
buf
+
sent
,
r
-
sent
,
0
);
if
(
s
<=
0
)
break
;
sent
+=
s
;
}
if
(
sent
<
r
)
break
;
}
fclose
(
f
);
my_zlog_info
(
"设备调试: 已发送日志 %s (%ld bytes)"
,
filename
,
fsize
);
}
close
(
client_fd
);
close
(
server_fd
);
free
(
entries
);
return
NULL
;
}
void
device_diag_handle_log_fetch
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
)
{
cJSON
*
pwd_item
;
const
char
*
password
=
NULL
;
cJSON
*
resp
;
cJSON
*
arr
;
char
*
payload
;
log_entry_t
entries
[
DIAG_LOG_MAX_FILES
];
log_entry_t
*
thread_entries
;
int
n
;
pthread_t
tid
;
pthread_attr_t
attr
;
pwd_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"password"
);
if
(
!
cJSON_IsString
(
pwd_item
)
||
!
pwd_item
->
valuestring
)
{
password
=
""
;
}
else
{
password
=
pwd_item
->
valuestring
;
}
if
(
strcmp
(
password
,
DIAG_LOG_PASSWORD
)
!=
0
)
{
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
0
);
cJSON_AddItemToObject
(
resp
,
"files"
,
cJSON_CreateArray
());
cJSON_AddStringToObject
(
resp
,
"message"
,
"密码错误"
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
my_zlog_warn
(
"设备调试: log_fetch 密码错误"
);
return
;
}
n
=
diag_find_recent_logs
(
entries
,
DIAG_LOG_MAX_FILES
);
if
(
n
<=
0
)
{
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
0
);
cJSON_AddItemToObject
(
resp
,
"files"
,
cJSON_CreateArray
());
cJSON_AddStringToObject
(
resp
,
"message"
,
"设备无日志文件"
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
my_zlog_warn
(
"设备调试: 无日志文件可发送"
);
return
;
}
thread_entries
=
(
log_entry_t
*
)
malloc
(
sizeof
(
log_entry_t
)
*
n
);
if
(
!
thread_entries
)
{
my_zlog_error
(
"设备调试: malloc 失败"
);
return
;
}
for
(
int
i
=
0
;
i
<
n
;
i
++
)
thread_entries
[
i
]
=
entries
[
i
];
pthread_attr_init
(
&
attr
);
pthread_attr_setdetachstate
(
&
attr
,
PTHREAD_CREATE_DETACHED
);
if
(
pthread_create
(
&
tid
,
&
attr
,
diag_log_tcp_thread
,
thread_entries
)
!=
0
)
{
my_zlog_error
(
"设备调试: TCP 线程创建失败"
);
free
(
thread_entries
);
pthread_attr_destroy
(
&
attr
);
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
0
);
cJSON_AddItemToObject
(
resp
,
"files"
,
cJSON_CreateArray
());
cJSON_AddStringToObject
(
resp
,
"message"
,
"设备 TCP 服务启动失败"
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
return
;
}
pthread_attr_destroy
(
&
attr
);
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
DIAG_LOG_TCP_PORT
);
arr
=
cJSON_CreateArray
();
for
(
int
i
=
0
;
i
<
n
;
i
++
)
{
cJSON
*
item
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
item
,
"name"
,
entries
[
i
].
filename
);
cJSON_AddNumberToObject
(
item
,
"size"
,
entries
[
i
].
size
);
cJSON_AddItemToArray
(
arr
,
item
);
}
cJSON_AddItemToObject
(
resp
,
"files"
,
arr
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
my_zlog_info
(
"设备调试: log_fetch 已启动 TCP %d,共 %d 个日志"
,
DIAG_LOG_TCP_PORT
,
n
);
}
int
device_diag_is_cmd
(
const
char
*
cmd
)
{
if
(
!
cmd
)
return
0
;
if
(
strcmp
(
cmd
,
"device_check"
)
==
0
||
strcmp
(
cmd
,
"browser_refresh"
)
==
0
||
strcmp
(
cmd
,
"browser_reopen"
)
==
0
||
strcmp
(
cmd
,
"log_fetch"
)
==
0
)
return
1
;
return
0
;
}
modules/device_debug/device_diag.h
0 → 100644
View file @
d92f8afb
#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
modules/runtime_deps/runtime_deps.c
View file @
d92f8afb
...
@@ -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"
);
...
...
modules/webrtcpush/audio_source.c
View file @
d92f8afb
...
@@ -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
);
...
...
modules/webrtcpush/mpp_h264_source.c
View file @
d92f8afb
...
@@ -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
)
{
...
...
modules/webrtcpush/rtc_client.c
View file @
d92f8afb
...
@@ -57,6 +57,7 @@ typedef struct {
...
@@ -57,6 +57,7 @@ typedef struct {
gboolean
stopping
;
gboolean
stopping
;
gboolean
track_open
;
gboolean
track_open
;
gboolean
need_idr
;
gboolean
need_idr
;
gboolean
first_frame_sent
;
/* 首帧 IDR 不受 pacing flush 影响 */
gboolean
answer_sent
;
gboolean
answer_sent
;
int
pc
;
int
pc
;
int
track
;
int
track
;
...
@@ -67,6 +68,9 @@ typedef struct {
...
@@ -67,6 +68,9 @@ typedef struct {
gboolean
source_pts_valid
;
gboolean
source_pts_valid
;
guint
target_bitrate
;
guint
target_bitrate
;
guint
remb_ceiling
;
guint
remb_ceiling
;
guint
last_remb_bitrate
;
guint
remb_filtered
;
guint
remb_down_samples
;
gint64
last_bitrate_ramp_us
;
gint64
last_bitrate_ramp_us
;
gint64
last_idr_request_us
;
gint64
last_idr_request_us
;
gint64
last_stats_log_us
;
gint64
last_stats_log_us
;
...
@@ -166,6 +170,8 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
...
@@ -166,6 +170,8 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
{
{
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcH264Frame
*
old
;
RtcH264Frame
*
old
;
gboolean
overflowed
=
FALSE
;
gboolean
request_resync
=
FALSE
;
g_mutex_lock
(
&
q
->
lock
);
g_mutex_lock
(
&
q
->
lock
);
if
(
q
->
stopping
)
{
if
(
q
->
stopping
)
{
...
@@ -173,14 +179,32 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
...
@@ -173,14 +179,32 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
rtc_h264_frame_free
(
frame
);
rtc_h264_frame_free
(
frame
);
return
;
return
;
}
}
while
(
g_queue_get_length
(
&
q
->
queue
)
>=
q
->
max_depth
)
{
if
(
g_queue_get_length
(
&
q
->
queue
)
>=
q
->
max_depth
)
{
old
=
g_queue_pop_head
(
&
q
->
queue
);
overflowed
=
TRUE
;
while
((
old
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
{
q
->
dropped_total
++
;
q
->
dropped_total
++
;
rtc_h264_frame_free
(
old
);
rtc_h264_frame_free
(
old
);
}
}
}
if
(
overflowed
&&
!
frame
->
is_idr
)
{
q
->
dropped_total
++
;
rtc_h264_frame_free
(
frame
);
request_resync
=
TRUE
;
}
else
{
g_queue_push_tail
(
&
q
->
queue
,
frame
);
g_queue_push_tail
(
&
q
->
queue
,
frame
);
g_cond_signal
(
&
q
->
not_empty
);
g_cond_signal
(
&
q
->
not_empty
);
}
g_mutex_unlock
(
&
q
->
lock
);
g_mutex_unlock
(
&
q
->
lock
);
/* Never continue a predictive chain after dropping an encoded P frame. */
if
(
request_resync
)
{
g_mutex_lock
(
&
client
->
lock
);
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
client
->
last_idr_request_us
=
g_get_monotonic_time
();
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_warn
(
"libdatachannel: encoded-frame queue overflow; request clean IDR"
);
}
}
}
static
RtcH264Frame
*
frame_queue_pop
(
RtcClient
*
client
,
gint
timeout_ms
)
static
RtcH264Frame
*
frame_queue_pop
(
RtcClient
*
client
,
gint
timeout_ms
)
...
@@ -493,14 +517,42 @@ static gboolean request_idr(RtcClient *client, gboolean bypass_throttle)
...
@@ -493,14 +517,42 @@ static gboolean request_idr(RtcClient *client, gboolean bypass_throttle)
return
TRUE
;
return
TRUE
;
}
}
static
void
log_selected_ice_path
(
int
pc
)
{
char
local_candidate
[
512
]
=
{
0
};
char
remote_candidate
[
512
]
=
{
0
};
char
local_address
[
128
]
=
{
0
};
char
remote_address
[
128
]
=
{
0
};
int
pair_ret
;
int
local_ret
;
int
remote_ret
;
if
(
pc
<
0
)
return
;
pair_ret
=
rtcGetSelectedCandidatePair
(
pc
,
local_candidate
,
(
int
)
sizeof
(
local_candidate
),
remote_candidate
,
(
int
)
sizeof
(
remote_candidate
));
local_ret
=
rtcGetLocalAddress
(
pc
,
local_address
,
(
int
)
sizeof
(
local_address
));
remote_ret
=
rtcGetRemoteAddress
(
pc
,
remote_address
,
(
int
)
sizeof
(
remote_address
));
if
(
pair_ret
>=
0
)
{
my_zlog_info
(
"libdatachannel: ICE selected local=[%s] remote=[%s] socket=%s -> %s"
,
local_candidate
,
remote_candidate
,
local_ret
>=
0
?
local_address
:
"?"
,
remote_ret
>=
0
?
remote_address
:
"?"
);
}
else
{
my_zlog_warn
(
"libdatachannel: connected but selected ICE pair unavailable (%d)"
,
pair_ret
);
}
}
static
void
RTC_API
on_state_change
(
int
pc
,
rtcState
state
,
void
*
ptr
)
static
void
RTC_API
on_state_change
(
int
pc
,
rtcState
state
,
void
*
ptr
)
{
{
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
(
void
)
pc
;
my_zlog_info
(
"libdatachannel: peer state=%s"
,
rtc_state_name
(
state
));
my_zlog_info
(
"libdatachannel: peer state=%s"
,
rtc_state_name
(
state
));
if
(
!
client
)
if
(
!
client
)
return
;
return
;
if
(
state
==
RTC_CONNECTED
)
{
if
(
state
==
RTC_CONNECTED
)
{
log_selected_ice_path
(
pc
);
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
...
@@ -520,6 +572,7 @@ static void RTC_API on_track_open(int track, void *ptr)
...
@@ -520,6 +572,7 @@ static void RTC_API on_track_open(int track, void *ptr)
if
(
client
->
track
==
track
)
{
if
(
client
->
track
==
track
)
{
client
->
track_open
=
TRUE
;
client
->
track_open
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
video_open
=
TRUE
;
video_open
=
TRUE
;
}
}
if
(
client
->
audio_track
==
track
)
{
if
(
client
->
audio_track
==
track
)
{
...
@@ -550,6 +603,7 @@ static void RTC_API on_track_closed(int track, void *ptr)
...
@@ -550,6 +603,7 @@ static void RTC_API on_track_closed(int track, void *ptr)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
if
(
client
->
track
==
track
)
{
if
(
client
->
track
==
track
)
{
client
->
track_open
=
FALSE
;
client
->
track_open
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
video_closed
=
TRUE
;
video_closed
=
TRUE
;
}
}
if
(
client
->
audio_track
==
track
)
{
if
(
client
->
audio_track
==
track
)
{
...
@@ -574,11 +628,15 @@ static void RTC_API on_track_error(int track, const char *error, void *ptr)
...
@@ -574,11 +628,15 @@ static void RTC_API on_track_error(int track, const char *error, void *ptr)
static
void
RTC_API
on_pli
(
int
track
,
void
*
ptr
)
static
void
RTC_API
on_pli
(
int
track
,
void
*
ptr
)
{
{
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
gboolean
requested
;
int
dropped
=
0
;
int
dropped
=
0
;
if
(
!
client
||
client
->
track
!=
track
)
if
(
!
client
||
client
->
track
!=
track
)
return
;
return
;
if
(
!
request_idr
(
client
,
FALSE
))
requested
=
request_idr
(
client
,
FALSE
);
if
(
!
requested
)
{
my_zlog_debug
(
"libdatachannel: PLI throttled, skip duplicate IDR request"
);
my_zlog_debug
(
"libdatachannel: PLI throttled, skip duplicate IDR request"
);
return
;
}
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
...
@@ -608,6 +666,58 @@ static guint pacing_bitrate_for_encoder(guint encoder_bitrate)
...
@@ -608,6 +666,58 @@ static guint pacing_bitrate_for_encoder(guint encoder_bitrate)
return
(
guint
)
pacing
;
return
(
guint
)
pacing
;
}
}
static
gboolean
pacing_queue_snapshot
(
RtcClient
*
client
,
int
track
,
guint
*
packets_out
,
guint
*
bytes_out
,
guint
*
delay_ms_out
)
{
guint
packets
=
0
;
guint
bytes
=
0
;
guint
target
;
guint
pacing_bps
;
int
ret
=
-
1
;
if
(
!
client
||
track
<
0
)
return
FALSE
;
g_mutex_lock
(
&
client
->
send_lock
);
if
(
client
->
track
==
track
)
ret
=
rtcGetPacingQueueStats
(
track
,
&
packets
,
&
bytes
);
g_mutex_unlock
(
&
client
->
send_lock
);
if
(
ret
<
0
)
return
FALSE
;
g_mutex_lock
(
&
client
->
lock
);
target
=
client
->
target_bitrate
;
g_mutex_unlock
(
&
client
->
lock
);
pacing_bps
=
pacing_bitrate_for_encoder
(
target
);
if
(
packets_out
)
*
packets_out
=
packets
;
if
(
bytes_out
)
*
bytes_out
=
bytes
;
if
(
delay_ms_out
)
{
*
delay_ms_out
=
pacing_bps
>
0
?
(
guint
)(((
guint64
)
bytes
*
8U
*
1000U
+
pacing_bps
-
1U
)
/
pacing_bps
)
:
0
;
}
return
TRUE
;
}
static
int
clear_stale_pacing
(
RtcClient
*
client
,
int
track
)
{
int
dropped
=
0
;
request_idr
(
client
,
TRUE
);
g_mutex_lock
(
&
client
->
lock
);
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
send_lock
);
if
(
client
->
track
==
track
)
dropped
=
rtcClearPacingQueue
(
track
);
g_mutex_unlock
(
&
client
->
send_lock
);
frame_queue_clear
(
client
);
return
dropped
;
}
static
void
apply_bitrate
(
RtcClient
*
client
,
int
track
,
guint
bitrate
)
static
void
apply_bitrate
(
RtcClient
*
client
,
int
track
,
guint
bitrate
)
{
{
guint
b
;
guint
b
;
...
@@ -629,7 +739,7 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
...
@@ -629,7 +739,7 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
b
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
b
);
}
}
/*
与 gst_webrtc_pipeline::ramp_video_bitrate 同档升降步长
*/
/*
Public Internet: climb slowly so a transient REMB spike cannot build latency.
*/
static
void
ramp_bitrate_toward_ceiling
(
RtcClient
*
client
,
int
track
)
static
void
ramp_bitrate_toward_ceiling
(
RtcClient
*
client
,
int
track
)
{
{
guint
cur
;
guint
cur
;
...
@@ -649,26 +759,18 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
...
@@ -649,26 +759,18 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
if
(
!
cur
||
!
tgt
||
cur
==
tgt
)
if
(
!
cur
||
!
tgt
||
cur
==
tgt
)
return
;
return
;
diff
=
(
cur
>
tgt
)
?
(
cur
-
tgt
)
:
(
tgt
-
cur
);
if
(
cur
>
tgt
)
if
(
cur
<
tgt
)
{
return
;
/* confirmed decreases are applied by on_remb() */
step
=
diff
/
4
;
if
(
step
<
100000
)
diff
=
tgt
-
cur
;
step
=
100000
;
step
=
(
guint
)(((
guint64
)
cur
*
12U
)
/
100U
);
if
(
step
>
300000
)
if
(
step
<
50000U
)
step
=
300000
;
step
=
50000U
;
if
(
step
>
250000U
)
step
=
250000U
;
if
(
step
>
diff
)
step
=
diff
;
next
=
cur
+
step
;
next
=
cur
+
step
;
if
(
next
>
tgt
)
next
=
tgt
;
}
else
{
step
=
diff
/
3
;
if
(
step
<
100000
)
step
=
100000
;
if
(
step
>
600000
)
step
=
600000
;
next
=
(
cur
>
step
)
?
(
cur
-
step
)
:
0
;
if
(
next
<
tgt
)
next
=
tgt
;
}
if
(
next
==
cur
)
if
(
next
==
cur
)
return
;
return
;
...
@@ -682,33 +784,68 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -682,33 +784,68 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
{
{
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
guint
current
;
guint
current
;
guint
usable
;
guint
filtered
;
guint
ceiling
;
guint
ceiling
;
guint
next
=
0
;
guint
next
=
0
;
guint
down_samples
=
0
;
gboolean
severe_down
=
FALSE
;
gboolean
severe_down
=
FALSE
;
gint64
now_us
;
int
dropped
=
0
;
int
dropped
=
0
;
if
(
!
client
||
bitrate
==
0
)
if
(
!
client
||
bitrate
==
0
)
return
;
return
;
now_us
=
g_get_monotonic_time
();
ceiling
=
clamp_bitrate
(
usable
=
clamp_bitrate
(
(
guint
)(((
guint64
)
bitrate
*
WEBRTCPUSH_REMB_UTIL_PERCENT
)
/
100
));
(
guint
)(((
guint64
)
bitrate
*
WEBRTCPUSH_REMB_UTIL_PERCENT
)
/
100
U
));
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
current
=
client
->
target_bitrate
;
current
=
client
->
target_bitrate
;
client
->
remb_ceiling
=
ceiling
;
client
->
last_remb_bitrate
=
bitrate
;
if
(
ceiling
<
current
)
{
severe_down
=
((
guint64
)
ceiling
*
100
)
<
((
guint64
)
current
*
75
);
if
(
client
->
remb_filtered
==
0
)
{
if
(
!
severe_down
&&
(
current
-
ceiling
)
<
WEBRTCPUSH_REMB_DOWN_MIN_STEP
)
{
filtered
=
bitrate
;
g_mutex_unlock
(
&
client
->
lock
);
}
else
if
(
bitrate
<
client
->
remb_filtered
)
{
return
;
/* React to a real fall, but do not mirror every 200ms estimator sample. */
filtered
=
(
guint
)(((
guint64
)
client
->
remb_filtered
*
2U
+
bitrate
)
/
3U
);
}
else
{
/* Rising bandwidth needs sustained evidence. */
filtered
=
(
guint
)(((
guint64
)
client
->
remb_filtered
*
7U
+
bitrate
)
/
8U
);
}
}
severe_down
=
((
guint64
)
usable
*
100U
)
<
((
guint64
)
current
*
WEBRTCPUSH_REMB_SEVERE_PERCENT
);
if
(
severe_down
)
{
/* Reset the filter too, otherwise its old high value immediately rebounds. */
filtered
=
bitrate
;
ceiling
=
usable
;
client
->
remb_down_samples
=
0
;
}
else
{
ceiling
=
clamp_bitrate
(
(
guint
)(((
guint64
)
filtered
*
WEBRTCPUSH_REMB_UTIL_PERCENT
)
/
100U
));
}
client
->
remb_filtered
=
filtered
;
client
->
remb_ceiling
=
ceiling
;
if
(
ceiling
+
WEBRTCPUSH_REMB_DOWN_MIN_STEP
<=
current
)
{
if
(
!
severe_down
)
client
->
remb_down_samples
++
;
down_samples
=
client
->
remb_down_samples
;
if
(
severe_down
||
client
->
remb_down_samples
>=
WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
)
{
client
->
remb_down_samples
=
0
;
client
->
target_bitrate
=
ceiling
;
client
->
target_bitrate
=
ceiling
;
client
->
last_bitrate_ramp_us
=
now_us
;
next
=
ceiling
;
next
=
ceiling
;
}
if
(
severe_down
)
{
if
(
severe_down
)
{
client
->
need_idr
=
TRUE
;
client
->
need_idr
=
TRUE
;
client
->
last_idr_request_us
=
g_get_monotonic_time
()
;
client
->
last_idr_request_us
=
now_us
;
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
}
}
}
else
if
(
ceiling
>=
current
)
{
client
->
remb_down_samples
=
0
;
}
}
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
...
@@ -724,18 +861,22 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -724,18 +861,22 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
next
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
next
);
if
(
severe_down
)
{
if
(
severe_down
)
{
my_zlog_info
(
"libdatachannel: REMB
=%u kbps, encoder=%u kbps
pacing=%u kbps, resync dropped=%d RTP packets"
,
my_zlog_info
(
"libdatachannel: REMB
raw=%u filtered=%u kbps, encoder=%u
pacing=%u kbps, resync dropped=%d RTP packets"
,
bitrate
/
1000
,
next
/
1000
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
pacing_bitrate_for_encoder
(
next
)
/
1000
,
pacing_bitrate_for_encoder
(
next
)
/
1000
,
dropped
>
0
?
dropped
:
0
);
dropped
>
0
?
dropped
:
0
);
}
else
{
}
else
{
my_zlog_info
(
"libdatachannel: REMB
=%u kbps, encoder=%u kbps pacing=%u kbp
s"
,
my_zlog_info
(
"libdatachannel: REMB
raw=%u filtered=%u kbps, encoder=%u pacing=%u kbps after %u sample
s"
,
bitrate
/
1000
,
next
/
1000
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
pacing_bitrate_for_encoder
(
next
)
/
1000
);
pacing_bitrate_for_encoder
(
next
)
/
1000
,
down_samples
);
}
}
}
else
if
(
ceiling
>
current
)
{
}
else
if
(
ceiling
>
current
)
{
my_zlog_debug
(
"libdatachannel: REMB=%u kbps, ceiling %u kbps (ramping up)"
,
my_zlog_debug
(
"libdatachannel: REMB raw=%u filtered=%u ceiling=%u kbps (slow ramp)"
,
bitrate
/
1000
,
ceiling
/
1000
);
bitrate
/
1000
,
filtered
/
1000
,
ceiling
/
1000
);
}
else
if
(
down_samples
>
0
)
{
my_zlog_debug
(
"libdatachannel: REMB down candidate %u/%u raw=%u filtered=%u kbps"
,
down_samples
,
WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
,
bitrate
/
1000
,
filtered
/
1000
);
}
}
}
}
...
@@ -743,7 +884,6 @@ static void maybe_ramp_bitrate(RtcClient *client, int track, gint64 now_us)
...
@@ -743,7 +884,6 @@ static void maybe_ramp_bitrate(RtcClient *client, int track, gint64 now_us)
{
{
guint
cur
;
guint
cur
;
guint
tgt
;
guint
tgt
;
gint64
interval
;
if
(
!
client
||
track
<
0
)
if
(
!
client
||
track
<
0
)
return
;
return
;
...
@@ -751,9 +891,9 @@ static void maybe_ramp_bitrate(RtcClient *client, int track, gint64 now_us)
...
@@ -751,9 +891,9 @@ static void maybe_ramp_bitrate(RtcClient *client, int track, gint64 now_us)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
cur
=
client
->
target_bitrate
;
cur
=
client
->
target_bitrate
;
tgt
=
client
->
remb_ceiling
;
tgt
=
client
->
remb_ceiling
;
i
nterval
=
(
tgt
>
cur
)
?
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_UP_MS
i
f
(
tgt
<=
cur
||
:
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_DOWN_MS
;
now_us
-
client
->
last_bitrate_ramp_us
<
if
(
now_us
-
client
->
last_bitrate_ramp_us
<
interval
*
1000
)
{
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_UP_MS
*
1000
)
{
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
return
;
return
;
}
}
...
@@ -856,13 +996,25 @@ static gpointer send_thread_main(gpointer data)
...
@@ -856,13 +996,25 @@ static gpointer send_thread_main(gpointer data)
const
guint32
timestamp_step
=
90000
/
WEBRTCPUSH_H264_FPS
;
const
guint32
timestamp_step
=
90000
/
WEBRTCPUSH_H264_FPS
;
const
gint
pop_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
200
;
const
gint
pop_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
200
;
guint
send_fail_streak
=
0
;
guint
send_fail_streak
=
0
;
guint64
sent_window_bytes
=
0
;
guint
sent_window_frames
=
0
;
gint64
sent_window_start_us
=
g_get_monotonic_time
();
gint64
last_pacing_check_us
=
0
;
gint64
last_pacing_flush_us
=
0
;
for
(;;)
{
for
(;;)
{
gboolean
stopping
;
gboolean
stopping
;
int
track
;
int
track
;
guint
target_bps
;
guint
target_bps
;
guint
raw_remb_bps
;
guint
filtered_remb_bps
;
guint
remb_bps
;
guint
remb_bps
;
guint
queue_dropped
;
guint
queue_dropped
;
guint
pacing_packets
=
0
;
guint
pacing_bytes
=
0
;
guint
pacing_delay_ms
=
0
;
guint
actual_kbps
=
0
;
guint
actual_fps_x10
=
0
;
guint32
timestamp
;
guint32
timestamp
;
RtcH264Frame
*
frame
;
RtcH264Frame
*
frame
;
gint64
now
;
gint64
now
;
...
@@ -887,17 +1039,51 @@ static gpointer send_thread_main(gpointer data)
...
@@ -887,17 +1039,51 @@ static gpointer send_thread_main(gpointer data)
now
=
g_get_monotonic_time
();
now
=
g_get_monotonic_time
();
maybe_ramp_bitrate
(
client
,
track
,
now
);
maybe_ramp_bitrate
(
client
,
track
,
now
);
if
(
now
-
last_pacing_check_us
>=
100000
)
{
last_pacing_check_us
=
now
;
if
(
pacing_queue_snapshot
(
client
,
track
,
&
pacing_packets
,
&
pacing_bytes
,
&
pacing_delay_ms
)
&&
pacing_delay_ms
>
WEBRTCPUSH_PACING_MAX_QUEUE_MS
&&
client
->
first_frame_sent
&&
now
-
last_pacing_flush_us
>=
(
gint64
)
WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS
*
1000
)
{
int
dropped
=
clear_stale_pacing
(
client
,
track
);
last_pacing_flush_us
=
now
;
my_zlog_warn
(
"libdatachannel: pacing backlog %u packets/%u bytes ~= %ums; dropped=%d and request IDR"
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
dropped
>
0
?
dropped
:
0
);
}
}
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
if
(
client
->
last_stats_log_us
==
0
||
if
(
client
->
last_stats_log_us
==
0
||
now
-
client
->
last_stats_log_us
>=
now
-
client
->
last_stats_log_us
>=
(
gint64
)
WEBRTCPUSH_STATS_LOG_INTERVAL_MS
*
1000
)
{
(
gint64
)
WEBRTCPUSH_STATS_LOG_INTERVAL_MS
*
1000
)
{
target_bps
=
client
->
target_bitrate
;
target_bps
=
client
->
target_bitrate
;
raw_remb_bps
=
client
->
last_remb_bitrate
;
filtered_remb_bps
=
client
->
remb_filtered
;
remb_bps
=
client
->
remb_ceiling
;
remb_bps
=
client
->
remb_ceiling
;
client
->
last_stats_log_us
=
now
;
client
->
last_stats_log_us
=
now
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
queue_dropped
=
client
->
frame_queue
.
dropped_total
;
queue_dropped
=
client
->
frame_queue
.
dropped_total
;
my_zlog_info
(
"libdatachannel: stats encoder=%u kbps remb_ceiling=%u kbps queue_dropped=%u"
,
pacing_queue_snapshot
(
client
,
track
,
&
pacing_packets
,
target_bps
/
1000
,
remb_bps
/
1000
,
queue_dropped
);
&
pacing_bytes
,
&
pacing_delay_ms
);
if
(
now
>
sent_window_start_us
)
{
guint64
elapsed_us
=
(
guint64
)(
now
-
sent_window_start_us
);
actual_kbps
=
(
guint
)((
sent_window_bytes
*
8U
*
1000U
)
/
elapsed_us
);
actual_fps_x10
=
(
guint
)(((
guint64
)
sent_window_frames
*
10U
*
1000000U
)
/
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"
,
target_bps
/
1000
,
actual_kbps
,
actual_fps_x10
/
10
,
actual_fps_x10
%
10
,
raw_remb_bps
/
1000
,
filtered_remb_bps
/
1000
,
remb_bps
/
1000
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
queue_dropped
);
sent_window_bytes
=
0
;
sent_window_frames
=
0
;
sent_window_start_us
=
now
;
}
else
{
}
else
{
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
}
}
...
@@ -938,6 +1124,7 @@ static gpointer send_thread_main(gpointer data)
...
@@ -938,6 +1124,7 @@ static gpointer send_thread_main(gpointer data)
send_fail_streak
);
send_fail_streak
);
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
track_open
=
FALSE
;
client
->
track_open
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
need_idr
=
TRUE
;
client
->
need_idr
=
TRUE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
signal_track_ready
(
client
);
signal_track_ready
(
client
);
...
@@ -949,6 +1136,12 @@ static gpointer send_thread_main(gpointer data)
...
@@ -949,6 +1136,12 @@ static gpointer send_thread_main(gpointer data)
}
}
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
send_fail_streak
=
0
;
send_fail_streak
=
0
;
if
(
!
client
->
first_frame_sent
)
{
client
->
first_frame_sent
=
TRUE
;
my_zlog_info
(
"libdatachannel: first frame sent (%zu bytes), pacing flush armed"
,
frame
->
size
);
}
sent_window_bytes
+=
frame
->
size
;
sent_window_frames
++
;
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
rtp_timestamp
=
timestamp
+
timestamp_step
;
client
->
rtp_timestamp
=
timestamp
+
timestamp_step
;
...
@@ -1134,6 +1327,7 @@ static void destroy_peer(RtcClient *client)
...
@@ -1134,6 +1327,7 @@ static void destroy_peer(RtcClient *client)
client
->
pc
=
-
1
;
client
->
pc
=
-
1
;
client
->
track
=
-
1
;
client
->
track
=
-
1
;
client
->
track_open
=
FALSE
;
client
->
track_open
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
audio_track
=
-
1
;
client
->
audio_track
=
-
1
;
client
->
audio_track_open
=
FALSE
;
client
->
audio_track_open
=
FALSE
;
client
->
answer_sent
=
FALSE
;
client
->
answer_sent
=
FALSE
;
...
@@ -1311,6 +1505,9 @@ int rtc_client_start(AppState *app)
...
@@ -1311,6 +1505,9 @@ int rtc_client_start(AppState *app)
client
->
track
=
-
1
;
client
->
track
=
-
1
;
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
last_remb_bitrate
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_down_samples
=
0
;
client
->
last_bitrate_ramp_us
=
g_get_monotonic_time
();
client
->
last_bitrate_ramp_us
=
g_get_monotonic_time
();
client
->
pending_local_ice
=
g_queue_new
();
client
->
pending_local_ice
=
g_queue_new
();
client
->
pending_remote_ice
=
g_queue_new
();
client
->
pending_remote_ice
=
g_queue_new
();
...
@@ -1415,6 +1612,7 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
...
@@ -1415,6 +1612,7 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
return
-
1
;
return
-
1
;
if
(
!
parse_offer
(
sdp
,
&
video
,
&
audio
))
{
if
(
!
parse_offer
(
sdp
,
&
video
,
&
audio
))
{
my_zlog_error
(
"libdatachannel: phone offer has no usable H264 video"
);
my_zlog_error
(
"libdatachannel: phone offer has no usable H264 video"
);
my_zlog_error
(
"libdatachannel: SDP dump BEGIN:
\n
%s"
,
sdp
);
offer_video_clear
(
&
video
);
offer_video_clear
(
&
video
);
offer_audio_clear
(
&
audio
);
offer_audio_clear
(
&
audio
);
return
-
1
;
return
-
1
;
...
@@ -1474,10 +1672,16 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
...
@@ -1474,10 +1672,16 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
client
->
remote_ice_ufrag
=
g_strdup
(
video
.
ice_ufrag
);
client
->
remote_ice_ufrag
=
g_strdup
(
video
.
ice_ufrag
);
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
target_bitrate
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
remb_ceiling
=
WEBRTCPUSH_INITIAL_BITRATE
;
client
->
last_remb_bitrate
=
0
;
client
->
remb_filtered
=
0
;
client
->
remb_down_samples
=
0
;
client
->
last_bitrate_ramp_us
=
g_get_monotonic_time
();
client
->
last_bitrate_ramp_us
=
g_get_monotonic_time
();
request_idr
(
client
,
TRUE
);
client
->
need_idr
=
TRUE
;
/* 内联,避免 request_idr 二次加锁死锁 */
client
->
last_idr_request_us
=
g_get_monotonic_time
();
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
WEBRTCPUSH_INITIAL_BITRATE
);
my_zlog_info
(
"libdatachannel: applying offer mid=%s pt=%d ufrag=%s"
,
my_zlog_info
(
"libdatachannel: applying offer mid=%s pt=%d ufrag=%s"
,
video
.
mid
,
video
.
payload_type
,
video
.
ice_ufrag
);
video
.
mid
,
video
.
payload_type
,
video
.
ice_ufrag
);
...
...
modules/webrtcpush/webrtcpush_config.h
View file @
d92f8afb
...
@@ -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 1
8
00000U
#define WEBRTCPUSH_INITIAL_BITRATE 1
4
00000U
#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
4
U
#define WEBRTCPUSH_SEND_QUEUE_DEPTH
2
U
/* 管道缓冲:运动场景需适度缓存,过低易 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"
...
...
modules/wifi_config/wifi_config_agent.c
View file @
d92f8afb
#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
);
...
...
third_party/libdatachannel/include/rtc/pacinghandler.hpp
View file @
d92f8afb
...
@@ -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
);
};
};
...
...
third_party/libdatachannel/include/rtc/rtc.h
View file @
d92f8afb
...
@@ -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
);
...
...
third_party/libdatachannel/src/capi.cpp
View file @
d92f8afb
...
@@ -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
);
...
...
third_party/libdatachannel/src/pacinghandler.cpp
View file @
d92f8afb
...
@@ -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
();
...
...
zlog.conf
View file @
d92f8afb
...
@@ -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-0
2
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-0
6
.log"
;
millisecond
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment