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
4687e6c8
Commit
4687e6c8
authored
Jul 08, 2026
by
957dd
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
加入了手机去配网
parent
d92f8afb
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
1166 additions
and
193 deletions
+1166
-193
device_wifi_manager.c
app/device_change/device_wifi_manager.c
+24
-0
device_wifi_manager.h
app/device_change/device_wifi_manager.h
+4
-0
main
build/main
+0
-0
device_diag.c
modules/device_debug/device_diag.c
+79
-63
runtime_deps.c
modules/runtime_deps/runtime_deps.c
+40
-1
audio_sink.c
modules/webrtcpush/audio_sink.c
+277
-0
audio_sink.h
modules/webrtcpush/audio_sink.h
+20
-0
audio_source.c
modules/webrtcpush/audio_source.c
+6
-1
mpp_h264_source.c
modules/webrtcpush/mpp_h264_source.c
+149
-45
rtc_client.c
modules/webrtcpush/rtc_client.c
+221
-61
volume_control.c
modules/webrtcpush/volume_control.c
+264
-0
volume_control.h
modules/webrtcpush/volume_control.h
+12
-0
webrtcpush_config.h
modules/webrtcpush/webrtcpush_config.h
+45
-12
wifi_config_agent.c
modules/wifi_config/wifi_config_agent.c
+24
-9
zlog.conf
zlog.conf
+1
-1
No files found.
app/device_change/device_wifi_manager.c
View file @
4687e6c8
...
@@ -978,6 +978,30 @@ cJSON *app_wifi_list_query(void) {
...
@@ -978,6 +978,30 @@ cJSON *app_wifi_list_query(void) {
}
}
/* 切换当前连接到已保存的指定 SSID */
/* 切换当前连接到已保存的指定 SSID */
/* 检查指定 SSID 是否已保存(不连接)。
* 供 agent 在切换前做预校验,便于"先回复 app 再实际切换"。 */
int
app_wifi_check_saved
(
const
char
*
ssid
)
{
saved_wifi_entry_t
entries
[
MAX_WIFI_ENTRIES
];
int
count
;
int
found
=
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
;
}
}
return
found
?
0
:
-
1
;
}
int
app_wifi_switch
(
const
char
*
ssid
)
{
int
app_wifi_switch
(
const
char
*
ssid
)
{
saved_wifi_entry_t
entries
[
MAX_WIFI_ENTRIES
];
saved_wifi_entry_t
entries
[
MAX_WIFI_ENTRIES
];
int
count
;
int
count
;
...
...
app/device_change/device_wifi_manager.h
View file @
4687e6c8
...
@@ -27,6 +27,10 @@ cJSON *app_wifi_list_query(void);
...
@@ -27,6 +27,10 @@ cJSON *app_wifi_list_query(void);
/* 切换当前连接到已保存的指定 SSID。
/* 切换当前连接到已保存的指定 SSID。
* 返回 0=已发起切换(异步), -1=未找到/失败 */
* 返回 0=已发起切换(异步), -1=未找到/失败 */
/* 检查指定 SSID 是否已保存(不连接)。
* 返回 0=已保存, -1=未找到 */
int
app_wifi_check_saved
(
const
char
*
ssid
);
int
app_wifi_switch
(
const
char
*
ssid
);
int
app_wifi_switch
(
const
char
*
ssid
);
/* 删除已保存的指定 SSID(受保护的 jking 不可删)。
/* 删除已保存的指定 SSID(受保护的 jking 不可删)。
...
...
build/main
View file @
4687e6c8
No preview for this file type
modules/device_debug/device_diag.c
View file @
4687e6c8
...
@@ -23,7 +23,6 @@
...
@@ -23,7 +23,6 @@
#define DIAG_LOG_FILENAME_MAX 128
#define DIAG_LOG_FILENAME_MAX 128
#define DIAG_LOG_PATH_MAX 512
#define DIAG_LOG_PATH_MAX 512
#define DIAG_BROWSER_URL_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
)
static
void
diag_send_json
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
const
char
*
json
)
{
{
...
@@ -300,6 +299,13 @@ typedef struct {
...
@@ -300,6 +299,13 @@ typedef struct {
time_t
mtime
;
time_t
mtime
;
}
log_entry_t
;
}
log_entry_t
;
/* TCP 传输上下文:主线程已完成 socket+bind+listen,线程只做 accept+传输 */
typedef
struct
{
int
server_fd
;
log_entry_t
entries
[
DIAG_LOG_MAX_FILES
];
int
count
;
}
log_tcp_ctx_t
;
static
int
diag_log_compare_mtime
(
const
void
*
a
,
const
void
*
b
)
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
*
ea
=
(
const
log_entry_t
*
)
a
;
...
@@ -356,57 +362,33 @@ static int diag_find_recent_logs(log_entry_t *entries, int max_count)
...
@@ -356,57 +362,33 @@ static int diag_find_recent_logs(log_entry_t *entries, int max_count)
return
count
;
return
count
;
}
}
/* TCP 线程:接收已 listen 的 server_fd,做 accept + 文件传输 */
static
void
*
diag_log_tcp_thread
(
void
*
arg
)
static
void
*
diag_log_tcp_thread
(
void
*
arg
)
{
{
log_
entry_t
*
entries
=
(
log_entry
_t
*
)
arg
;
log_
tcp_ctx_t
*
ctx
=
(
log_tcp_ctx
_t
*
)
arg
;
int
server_fd
,
client
_fd
;
int
server_fd
=
ctx
->
server
_fd
;
struct
sockaddr_in
addr
;
int
client_fd
;
int
opt
=
1
;
struct
timeval
tv
;
char
filename
[
DIAG_LOG_FILENAME_MAX
+
2
];
char
filename
[
DIAG_LOG_FILENAME_MAX
+
2
];
server_fd
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
/* accept 超时 30 秒 */
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_sec
=
30
;
tv
.
tv_usec
=
0
;
tv
.
tv_usec
=
0
;
setsockopt
(
server_fd
,
SOL_SOCKET
,
SO_RCVTIMEO
,
&
tv
,
sizeof
(
tv
));
setsockopt
(
server_fd
,
SOL_SOCKET
,
SO_RCVTIMEO
,
&
tv
,
sizeof
(
tv
));
my_zlog_info
(
"设备调试: TCP 等待连接 port=%d"
,
DIAG_LOG_TCP_PORT
);
client_fd
=
accept
(
server_fd
,
NULL
,
NULL
);
client_fd
=
accept
(
server_fd
,
NULL
,
NULL
);
if
(
client_fd
<
0
)
{
if
(
client_fd
<
0
)
{
my_zlog_error
(
"设备调试: TCP accept 超时或失败"
);
my_zlog_error
(
"设备调试: TCP accept 超时或失败"
);
close
(
server_fd
);
close
(
server_fd
);
free
(
entries
);
free
(
ctx
);
return
NULL
;
return
NULL
;
}
}
my_zlog_info
(
"设备调试: TCP 客户端已连接,开始传输"
);
/* 循环接收文件名请求 */
while
(
1
)
{
while
(
1
)
{
ssize_t
n
;
ssize_t
n
;
size_t
flen
=
0
;
size_t
flen
=
0
;
...
@@ -419,12 +401,13 @@ static void *diag_log_tcp_thread(void *arg)
...
@@ -419,12 +401,13 @@ static void *diag_log_tcp_thread(void *arg)
char
buf
[
4096
];
char
buf
[
4096
];
size_t
r
;
size_t
r
;
/* 逐字符读文件名直到 \n */
while
(
flen
<
sizeof
(
filename
)
-
1
)
{
while
(
flen
<
sizeof
(
filename
)
-
1
)
{
n
=
recv
(
client_fd
,
&
ch
,
1
,
0
);
n
=
recv
(
client_fd
,
&
ch
,
1
,
0
);
if
(
n
<=
0
)
{
if
(
n
<=
0
)
{
close
(
client_fd
);
close
(
client_fd
);
close
(
server_fd
);
close
(
server_fd
);
free
(
entries
);
free
(
ctx
);
my_zlog_info
(
"设备调试: TCP 日志传输完成"
);
my_zlog_info
(
"设备调试: TCP 日志传输完成"
);
return
NULL
;
return
NULL
;
}
}
...
@@ -434,11 +417,12 @@ static void *diag_log_tcp_thread(void *arg)
...
@@ -434,11 +417,12 @@ static void *diag_log_tcp_thread(void *arg)
}
}
filename
[
flen
]
=
'\0'
;
filename
[
flen
]
=
'\0'
;
/* 安全校验:文件名只能包含字母数字下划线和点 */
int
valid
=
1
;
int
valid
=
1
;
for
(
size_t
i
=
0
;
i
<
flen
;
i
++
)
{
for
(
size_t
i
=
0
;
i
<
flen
;
i
++
)
{
c
=
(
unsigned
char
)
filename
[
i
];
c
=
(
unsigned
char
)
filename
[
i
];
if
(
!
((
c
>=
'a'
&&
c
<=
'z'
)
||
(
c
>=
'A'
&&
c
<=
'Z'
)
||
if
(
!
((
c
>=
'a'
&&
c
<=
'z'
)
||
(
c
>=
'A'
&&
c
<=
'Z'
)
||
(
c
>=
'0'
&&
c
<=
'9'
)
||
c
==
'_'
||
c
==
'.'
))
{
(
c
>=
'0'
&&
c
<=
'9'
)
||
c
==
'_'
||
c
==
'
-'
||
c
==
'
.'
))
{
valid
=
0
;
valid
=
0
;
break
;
break
;
}
}
...
@@ -463,6 +447,7 @@ static void *diag_log_tcp_thread(void *arg)
...
@@ -463,6 +447,7 @@ static void *diag_log_tcp_thread(void *arg)
fsize
=
ftell
(
f
);
fsize
=
ftell
(
f
);
rewind
(
f
);
rewind
(
f
);
/* 8 字节大端序发送文件大小 */
uint64_t
sz
=
(
uint64_t
)
fsize
;
uint64_t
sz
=
(
uint64_t
)
fsize
;
for
(
int
i
=
7
;
i
>=
0
;
i
--
)
{
for
(
int
i
=
7
;
i
>=
0
;
i
--
)
{
sizebuf
[
i
]
=
(
uint8_t
)(
sz
&
0xFF
);
sizebuf
[
i
]
=
(
uint8_t
)(
sz
&
0xFF
);
...
@@ -470,6 +455,7 @@ static void *diag_log_tcp_thread(void *arg)
...
@@ -470,6 +455,7 @@ static void *diag_log_tcp_thread(void *arg)
}
}
send
(
client_fd
,
sizebuf
,
8
,
0
);
send
(
client_fd
,
sizebuf
,
8
,
0
);
/* 发送文件内容 */
while
((
r
=
fread
(
buf
,
1
,
sizeof
(
buf
),
f
))
>
0
)
{
while
((
r
=
fread
(
buf
,
1
,
sizeof
(
buf
),
f
))
>
0
)
{
size_t
sent
=
0
;
size_t
sent
=
0
;
while
(
sent
<
r
)
{
while
(
sent
<
r
)
{
...
@@ -487,7 +473,7 @@ static void *diag_log_tcp_thread(void *arg)
...
@@ -487,7 +473,7 @@ static void *diag_log_tcp_thread(void *arg)
close
(
client_fd
);
close
(
client_fd
);
close
(
server_fd
);
close
(
server_fd
);
free
(
entries
);
free
(
ctx
);
return
NULL
;
return
NULL
;
}
}
...
@@ -499,10 +485,13 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
...
@@ -499,10 +485,13 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
cJSON
*
arr
;
cJSON
*
arr
;
char
*
payload
;
char
*
payload
;
log_entry_t
entries
[
DIAG_LOG_MAX_FILES
];
log_entry_t
entries
[
DIAG_LOG_MAX_FILES
];
log_entry_t
*
thread_entries
;
int
n
;
int
n
;
int
server_fd
;
struct
sockaddr_in
tcp_addr
;
int
opt
=
1
;
pthread_t
tid
;
pthread_t
tid
;
pthread_attr_t
attr
;
pthread_attr_t
attr
;
log_tcp_ctx_t
*
ctx
;
pwd_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"password"
);
pwd_item
=
cJSON_GetObjectItemCaseSensitive
(
root
,
"password"
);
if
(
!
cJSON_IsString
(
pwd_item
)
||
!
pwd_item
->
valuestring
)
{
if
(
!
cJSON_IsString
(
pwd_item
)
||
!
pwd_item
->
valuestring
)
{
...
@@ -511,6 +500,7 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
...
@@ -511,6 +500,7 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
password
=
pwd_item
->
valuestring
;
password
=
pwd_item
->
valuestring
;
}
}
/* 密码校验 */
if
(
strcmp
(
password
,
DIAG_LOG_PASSWORD
)
!=
0
)
{
if
(
strcmp
(
password
,
DIAG_LOG_PASSWORD
)
!=
0
)
{
resp
=
cJSON_CreateObject
();
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
...
@@ -527,6 +517,7 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
...
@@ -527,6 +517,7 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
return
;
return
;
}
}
/* 查找最近 2 个日志 */
n
=
diag_find_recent_logs
(
entries
,
DIAG_LOG_MAX_FILES
);
n
=
diag_find_recent_logs
(
entries
,
DIAG_LOG_MAX_FILES
);
if
(
n
<=
0
)
{
if
(
n
<=
0
)
{
resp
=
cJSON_CreateObject
();
resp
=
cJSON_CreateObject
();
...
@@ -544,35 +535,36 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
...
@@ -544,35 +535,36 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
return
;
return
;
}
}
thread_entries
=
(
log_entry_t
*
)
malloc
(
sizeof
(
log_entry_t
)
*
n
);
/* 关键修复:先创建 TCP server 并 listen 成功,再回 log_fetch_ready。
if
(
!
thread_entries
)
{
* 避免"手机收到 ready 后立即连接,但设备还没 listen,连接被拒绝"的时序问题。 */
my_zlog_error
(
"设备调试: malloc 失败"
);
server_fd
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
if
(
server_fd
<
0
)
{
my_zlog_error
(
"设备调试: TCP socket 创建失败"
);
return
;
return
;
}
}
for
(
int
i
=
0
;
i
<
n
;
i
++
)
thread_entries
[
i
]
=
entries
[
i
];
pthread_attr_init
(
&
attr
);
setsockopt
(
server_fd
,
SOL_SOCKET
,
SO_REUSEADDR
,
&
opt
,
sizeof
(
opt
));
pthread_attr_setdetachstate
(
&
attr
,
PTHREAD_CREATE_DETACHED
);
if
(
pthread_create
(
&
tid
,
&
attr
,
diag_log_tcp_thread
,
thread_entries
)
!=
0
)
{
memset
(
&
tcp_addr
,
0
,
sizeof
(
tcp_addr
));
my_zlog_error
(
"设备调试: TCP 线程创建失败"
);
tcp_addr
.
sin_family
=
AF_INET
;
free
(
thread_entries
);
tcp_addr
.
sin_port
=
htons
(
DIAG_LOG_TCP_PORT
);
pthread_attr_destroy
(
&
attr
);
tcp_addr
.
sin_addr
.
s_addr
=
INADDR_ANY
;
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
if
(
bind
(
server_fd
,
(
struct
sockaddr
*
)
&
tcp_addr
,
sizeof
(
tcp_addr
))
<
0
)
{
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
0
);
my_zlog_error
(
"设备调试: TCP bind %d 失败"
,
DIAG_LOG_TCP_PORT
);
cJSON_AddItemToObject
(
resp
,
"files"
,
cJSON_CreateArray
());
close
(
server_fd
);
cJSON_AddStringToObject
(
resp
,
"message"
,
"设备 TCP 服务启动失败"
);
return
;
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
diag_send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
}
cJSON_Delete
(
resp
);
if
(
listen
(
server_fd
,
1
)
<
0
)
{
my_zlog_error
(
"设备调试: TCP listen 失败"
);
close
(
server_fd
);
return
;
return
;
}
}
pthread_attr_destroy
(
&
attr
);
my_zlog_info
(
"设备调试: TCP 已 listen port=%d,准备回 log_fetch_ready"
,
DIAG_LOG_TCP_PORT
);
/* 回 log_fetch_ready(此时 listen 已就绪,手机可立即连接) */
resp
=
cJSON_CreateObject
();
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"log_fetch_ready"
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
DIAG_LOG_TCP_PORT
);
cJSON_AddNumberToObject
(
resp
,
"tcpPort"
,
DIAG_LOG_TCP_PORT
);
...
@@ -591,6 +583,30 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
...
@@ -591,6 +583,30 @@ void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSO
free
(
payload
);
free
(
payload
);
}
}
cJSON_Delete
(
resp
);
cJSON_Delete
(
resp
);
/* 启动 detached 线程做 accept + 传输 */
ctx
=
(
log_tcp_ctx_t
*
)
malloc
(
sizeof
(
log_tcp_ctx_t
));
if
(
!
ctx
)
{
my_zlog_error
(
"设备调试: malloc 失败"
);
close
(
server_fd
);
return
;
}
ctx
->
server_fd
=
server_fd
;
ctx
->
count
=
n
;
for
(
int
i
=
0
;
i
<
n
;
i
++
)
ctx
->
entries
[
i
]
=
entries
[
i
];
pthread_attr_init
(
&
attr
);
pthread_attr_setdetachstate
(
&
attr
,
PTHREAD_CREATE_DETACHED
);
if
(
pthread_create
(
&
tid
,
&
attr
,
diag_log_tcp_thread
,
ctx
)
!=
0
)
{
my_zlog_error
(
"设备调试: TCP 线程创建失败"
);
free
(
ctx
);
close
(
server_fd
);
pthread_attr_destroy
(
&
attr
);
return
;
}
pthread_attr_destroy
(
&
attr
);
my_zlog_info
(
"设备调试: log_fetch 已启动 TCP %d,共 %d 个日志"
,
DIAG_LOG_TCP_PORT
,
n
);
my_zlog_info
(
"设备调试: log_fetch 已启动 TCP %d,共 %d 个日志"
,
DIAG_LOG_TCP_PORT
,
n
);
}
}
...
...
modules/runtime_deps/runtime_deps.c
View file @
4687e6c8
...
@@ -288,11 +288,50 @@ static gboolean gst_check_required_plugins(void)
...
@@ -288,11 +288,50 @@ static gboolean gst_check_required_plugins(void)
my_zlog_warn
(
"runtime_deps: missing GStreamer element %s"
,
g
->
element
);
my_zlog_warn
(
"runtime_deps: missing GStreamer element %s"
,
g
->
element
);
return
FALSE
;
return
FALSE
;
}
}
if
(
g
->
element
&&
!
gst_element_ready
(
g
->
element
))
{
if
(
g
->
element
&&
g_strcmp0
(
g
->
element
,
"v4l2src"
)
!=
0
&&
!
gst_element_ready
(
g
->
element
))
{
my_zlog_warn
(
"runtime_deps: element not ready %s"
,
g
->
element
);
my_zlog_warn
(
"runtime_deps: element not ready %s"
,
g
->
element
);
return
FALSE
;
return
FALSE
;
}
}
}
}
/* 摄像头不在 /dev/video0 时,尝试 USB 重置复位到 video0 */
{
gboolean
need_reset
=
TRUE
;
for
(
int
i
=
0
;
i
<
3
;
i
++
)
{
gchar
*
path
=
g_strdup_printf
(
"/dev/video%d"
,
i
);
if
(
g_file_test
(
path
,
G_FILE_TEST_EXISTS
)
&&
i
==
0
)
{
/* video0 存在,检查是否是 capture 设备 */
gchar
*
sys_cap
=
g_strdup_printf
(
"/sys/class/video4linux/video%d/device_caps"
,
i
);
if
(
g_file_test
(
sys_cap
,
G_FILE_TEST_EXISTS
))
{
gchar
*
contents
=
NULL
;
if
(
g_file_get_contents
(
sys_cap
,
&
contents
,
NULL
,
NULL
))
{
guint64
caps
=
g_ascii_strtoull
(
g_strstrip
(
contents
),
NULL
,
0
);
if
((
caps
&
0x00000001U
)
||
(
caps
&
0x00001000U
))
need_reset
=
FALSE
;
g_free
(
contents
);
}
}
g_free
(
sys_cap
);
}
g_free
(
path
);
}
if
(
need_reset
)
{
my_zlog_warn
(
"runtime_deps: camera not on /dev/video0, trying USB reset"
);
/* uvcvideo 模块重载:释放所有 USB 摄像头并重新枚举 */
int
rc1
=
system
(
"modprobe -r uvcvideo 2>/dev/null"
);
if
(
rc1
==
0
)
{
g_usleep
(
500000
);
system
(
"modprobe uvcvideo 2>/dev/null"
);
g_usleep
(
2000000
);
/* 等 2 秒让设备重新枚举 */
my_zlog_info
(
"runtime_deps: USB camera reset done"
);
}
else
{
/* modprobe 失败,尝试 usbreset */
system
(
"for dev in /dev/video*; do usbreset
\"
$dev
\"
2>/dev/null; done"
);
g_usleep
(
1000000
);
my_zlog_info
(
"runtime_deps: usbreset done"
);
}
}
}
if
(
!
gst_h264_encoder_ok
()
||
!
gst_audio_capture_ok
())
if
(
!
gst_h264_encoder_ok
()
||
!
gst_audio_capture_ok
())
return
FALSE
;
return
FALSE
;
return
TRUE
;
return
TRUE
;
...
...
modules/webrtcpush/audio_sink.c
0 → 100644
View file @
4687e6c8
#include "audio_sink.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
/*
* 手机→设备方向的音频播放管道(按键模式):
* appsrc(opus payload) → opusdec → audioconvert → audioresample → volume → alsasink
*
* 手机端是"按住说话"模式(最长 15s),不是持续推流,因此:
* - on_audio_message 收到包后入队,立即返回(不阻塞 libdatachannel 线程)
* - 独立线程消费队列,维护"按键会话"
* - 会话开始(首包到达):pipeline 切到 PLAYING
* - 会话结束(500ms 无包 或 15s 超时):flush appsrc + pipeline 切到 READY
* 切到 READY 释放 ALSA 设备,避免 alsasink 持续占用/空转
*/
#define AUDIO_SINK_SESSION_TIMEOUT_MS 500
/* 500ms 无包认为按键结束 */
#define AUDIO_SINK_SESSION_MAX_MS 15000
/* 单次按键最长 15s */
typedef
struct
{
uint8_t
*
data
;
size_t
size
;
}
SinkPacket
;
struct
AudioSink
{
GstElement
*
pipeline
;
GstElement
*
appsrc
;
GstElement
*
vol
;
GAsyncQueue
*
queue
;
/* 待处理 Opus 包队列 */
GMutex
lock
;
/* 保护 pipeline 状态切换 */
GThread
*
thread
;
/* 消费线程 */
gboolean
quit
;
/* 退出标志 */
gint64
session_start_us
;
/* 当前按键会话开始时间(0=无会话) */
gint64
last_packet_us
;
/* 最后一个包到达时间 */
gboolean
pipeline_playing
;
/* pipeline 当前是否 PLAYING */
};
static
void
flush_queue
(
AudioSink
*
src
)
{
SinkPacket
*
pkt
;
while
((
pkt
=
g_async_queue_try_pop
(
src
->
queue
))
!=
NULL
)
{
g_free
(
pkt
->
data
);
g_free
(
pkt
);
}
}
static
void
set_pipeline_state_locked
(
AudioSink
*
src
,
GstState
state
)
{
if
(
!
src
->
pipeline
)
return
;
/* 切到 READY 时 flush appsrc,避免旧数据残留导致下次会话首帧异常 */
if
(
state
==
GST_STATE_READY
&&
src
->
appsrc
)
{
gst_element_send_event
(
src
->
pipeline
,
gst_event_new_flush_start
());
gst_element_send_event
(
src
->
pipeline
,
gst_event_new_flush_stop
(
FALSE
));
}
gst_element_set_state
(
src
->
pipeline
,
state
);
src
->
pipeline_playing
=
(
state
==
GST_STATE_PLAYING
);
}
static
gpointer
audio_sink_thread
(
gpointer
data
)
{
AudioSink
*
src
=
data
;
gint64
now
;
GstBuffer
*
buf
;
while
(
1
)
{
/* 等待包,超时 100ms 用于检查会话超时 */
SinkPacket
*
pkt
=
g_async_queue_timeout_pop
(
src
->
queue
,
100
*
1000
);
now
=
g_get_monotonic_time
();
g_mutex_lock
(
&
src
->
lock
);
if
(
src
->
quit
)
{
g_mutex_unlock
(
&
src
->
lock
);
if
(
pkt
)
{
g_free
(
pkt
->
data
);
g_free
(
pkt
);
}
break
;
}
/* 会话超时检查 */
if
(
src
->
session_start_us
>
0
)
{
gint64
idle_ms
=
(
now
-
src
->
last_packet_us
)
/
1000
;
gint64
sess_ms
=
(
now
-
src
->
session_start_us
)
/
1000
;
if
(
idle_ms
>=
AUDIO_SINK_SESSION_TIMEOUT_MS
||
sess_ms
>=
AUDIO_SINK_SESSION_MAX_MS
)
{
if
(
src
->
pipeline_playing
)
{
set_pipeline_state_locked
(
src
,
GST_STATE_READY
);
my_zlog_info
(
"audio_sink: session ended (idle=%lldms sess=%lldms)"
,
(
long
long
)
idle_ms
,
(
long
long
)
sess_ms
);
}
src
->
session_start_us
=
0
;
flush_queue
(
src
);
/* 丢弃超时后到达的旧包 */
if
(
pkt
)
{
g_free
(
pkt
->
data
);
g_free
(
pkt
);
pkt
=
NULL
;
}
g_mutex_unlock
(
&
src
->
lock
);
continue
;
}
}
if
(
pkt
)
{
/* 新会话开始 */
if
(
src
->
session_start_us
==
0
)
{
src
->
session_start_us
=
now
;
my_zlog_info
(
"audio_sink: session start"
);
if
(
!
src
->
pipeline_playing
)
set_pipeline_state_locked
(
src
,
GST_STATE_PLAYING
);
}
src
->
last_packet_us
=
now
;
/* push 到 appsrc(pipeline PLAYING 状态) */
if
(
src
->
appsrc
&&
src
->
pipeline_playing
)
{
buf
=
gst_buffer_new_wrapped
(
g_memdup2
(
pkt
->
data
,
pkt
->
size
),
pkt
->
size
);
GST_BUFFER_DTS
(
buf
)
=
GST_CLOCK_TIME_NONE
;
GST_BUFFER_PTS
(
buf
)
=
GST_CLOCK_TIME_NONE
;
if
(
gst_app_src_push_buffer
(
GST_APP_SRC
(
src
->
appsrc
),
buf
)
!=
GST_FLOW_OK
)
{
my_zlog_warn
(
"audio_sink: push_buffer failed"
);
}
}
g_free
(
pkt
->
data
);
g_free
(
pkt
);
}
g_mutex_unlock
(
&
src
->
lock
);
}
return
NULL
;
}
AudioSink
*
audio_sink_start
(
const
char
*
alsa_device
,
char
**
error_message
)
{
AudioSink
*
src
;
GstElement
*
pipe
,
*
asrc
,
*
dec
,
*
conv
,
*
resample
,
*
vol
,
*
sink
;
GstCaps
*
caps
;
GstStateChangeReturn
ret
;
if
(
!
alsa_device
||
!
alsa_device
[
0
])
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"no ALSA device"
);
return
NULL
;
}
src
=
g_new0
(
AudioSink
,
1
);
pipe
=
gst_pipeline_new
(
"audio-sink-pipe"
);
asrc
=
gst_element_factory_make
(
"appsrc"
,
"asrc"
);
dec
=
gst_element_factory_make
(
"opusdec"
,
"dec"
);
conv
=
gst_element_factory_make
(
"audioconvert"
,
"conv"
);
resample
=
gst_element_factory_make
(
"audioresample"
,
"resample"
);
vol
=
gst_element_factory_make
(
"volume"
,
"vol"
);
sink
=
gst_element_factory_make
(
"alsasink"
,
"sink"
);
if
(
!
pipe
||
!
asrc
||
!
dec
||
!
conv
||
!
resample
||
!
vol
||
!
sink
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to create audio sink GStreamer elements"
);
if
(
pipe
)
gst_object_unref
(
pipe
);
g_free
(
src
);
return
NULL
;
}
caps
=
gst_caps_new_empty_simple
(
"audio/x-opus"
);
g_object_set
(
asrc
,
"caps"
,
caps
,
"format"
,
GST_FORMAT_BYTES
,
"is-live"
,
TRUE
,
"emit-signals"
,
FALSE
,
"min-latency"
,
(
gint64
)
0
,
"max-bytes"
,
(
guint64
)(
1
*
1024
*
1024
),
NULL
);
gst_caps_unref
(
caps
);
g_object_set
(
sink
,
"device"
,
alsa_device
,
"buffer-time"
,
(
gint64
)
20000
,
"latency-time"
,
(
gint64
)
10000
,
"sync"
,
FALSE
,
NULL
);
g_object_set
(
vol
,
"volume"
,
0
.
5
,
NULL
);
gst_bin_add_many
(
GST_BIN
(
pipe
),
asrc
,
dec
,
conv
,
resample
,
vol
,
sink
,
NULL
);
if
(
!
gst_element_link_many
(
asrc
,
dec
,
conv
,
resample
,
vol
,
sink
,
NULL
))
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link audio sink chain"
);
gst_object_unref
(
pipe
);
g_free
(
src
);
return
NULL
;
}
src
->
pipeline
=
pipe
;
src
->
appsrc
=
asrc
;
src
->
vol
=
vol
;
src
->
queue
=
g_async_queue_new
();
g_mutex_init
(
&
src
->
lock
);
src
->
session_start_us
=
0
;
src
->
pipeline_playing
=
FALSE
;
/* 初始状态 READY(不占 ALSA 设备,等首包到来再 PLAYING) */
ret
=
gst_element_set_state
(
pipe
,
GST_STATE_READY
);
if
(
ret
==
GST_STATE_CHANGE_FAILURE
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"audio sink pipeline failed to reach READY"
);
gst_element_set_state
(
pipe
,
GST_STATE_NULL
);
gst_object_unref
(
pipe
);
g_async_queue_unref
(
src
->
queue
);
g_mutex_clear
(
&
src
->
lock
);
g_free
(
src
);
return
NULL
;
}
src
->
thread
=
g_thread_new
(
"audio-sink"
,
audio_sink_thread
,
src
);
if
(
!
src
->
thread
)
{
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to create audio sink thread"
);
audio_sink_stop
(
src
);
return
NULL
;
}
my_zlog_info
(
"audio_sink: started device=%s opus=%uch %uHz (push-to-talk)"
,
alsa_device
,
WEBRTCPUSH_OPUS_CHANNELS
,
WEBRTCPUSH_OPUS_CLOCKRATE
);
return
src
;
}
void
audio_sink_stop
(
AudioSink
*
src
)
{
if
(
!
src
)
return
;
if
(
src
->
thread
)
{
g_mutex_lock
(
&
src
->
lock
);
src
->
quit
=
TRUE
;
g_mutex_unlock
(
&
src
->
lock
);
g_thread_join
(
src
->
thread
);
src
->
thread
=
NULL
;
}
if
(
src
->
pipeline
)
{
gst_element_set_state
(
src
->
pipeline
,
GST_STATE_NULL
);
gst_object_unref
(
src
->
pipeline
);
}
if
(
src
->
queue
)
{
flush_queue
(
src
);
g_async_queue_unref
(
src
->
queue
);
}
g_mutex_clear
(
&
src
->
lock
);
g_free
(
src
);
}
gboolean
audio_sink_push_opus
(
AudioSink
*
src
,
const
uint8_t
*
data
,
size_t
size
)
{
SinkPacket
*
pkt
;
if
(
!
src
||
!
src
->
queue
||
!
data
||
size
==
0
)
return
FALSE
;
/* 入队,由消费线程处理(不阻塞 libdatachannel 回调线程) */
pkt
=
g_new0
(
SinkPacket
,
1
);
pkt
->
data
=
(
uint8_t
*
)
g_memdup2
(
data
,
size
);
pkt
->
size
=
size
;
g_async_queue_push
(
src
->
queue
,
pkt
);
return
TRUE
;
}
void
audio_sink_set_volume
(
AudioSink
*
src
,
double
volume
)
{
if
(
!
src
||
!
src
->
vol
)
return
;
if
(
volume
<
0
.
0
)
volume
=
0
.
0
;
if
(
volume
>
1
.
0
)
volume
=
1
.
0
;
g_object_set
(
src
->
vol
,
"volume"
,
volume
,
NULL
);
}
modules/webrtcpush/audio_sink.h
0 → 100644
View file @
4687e6c8
#ifndef AUDIO_SINK_H
#define AUDIO_SINK_H
#include <glib.h>
#include <stddef.h>
#include <stdint.h>
typedef
struct
AudioSink
AudioSink
;
/* alsa_device: plughw:X,Y 用于播放 */
AudioSink
*
audio_sink_start
(
const
char
*
alsa_device
,
char
**
error_message
);
void
audio_sink_stop
(
AudioSink
*
src
);
/* 接收 Opus RTP payload 写入播放管道。data 是 Opus payload(不含 RTP 头),size 是字节数 */
gboolean
audio_sink_push_opus
(
AudioSink
*
src
,
const
uint8_t
*
data
,
size_t
size
);
/* 设置播放音量 0.0~1.0 */
void
audio_sink_set_volume
(
AudioSink
*
src
,
double
volume
);
#endif
modules/webrtcpush/audio_source.c
View file @
4687e6c8
...
@@ -63,9 +63,14 @@ AudioSource *audio_source_start(const char *alsa_device, char **error_message)
...
@@ -63,9 +63,14 @@ 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
,
"bitrate-type"
,
0
,
/* 0=cbr */
"bitrate-type"
,
0
,
/* 0=cbr */
"framesize"
,
20
,
/* 20ms 帧 */
"inband-fec"
,
TRUE
,
"inband-fec"
,
TRUE
,
NULL
);
NULL
);
/* framesize 属性部分 opusenc 不支持,先检查再设置 */
{
GParamSpec
*
pspec
=
g_object_class_find_property
(
G_OBJECT_GET_CLASS
(
enc
),
"framesize"
);
if
(
pspec
)
g_object_set
(
enc
,
"framesize"
,
20
,
NULL
);
}
g_object_set
(
sink
,
g_object_set
(
sink
,
"emit-signals"
,
FALSE
,
"emit-signals"
,
FALSE
,
...
...
modules/webrtcpush/mpp_h264_source.c
View file @
4687e6c8
...
@@ -17,7 +17,8 @@
...
@@ -17,7 +17,8 @@
#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 matches negotiated 42e01f */
#define MPP_H264_PROFILE 66
/* Baseline: 与 SDP profile-level-id=42e01f 一致,低延迟优先 */
#define MPP_IDR_EVENT_RETRY_US 500000
#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)
...
@@ -42,6 +43,8 @@ struct MppH264Source {
...
@@ -42,6 +43,8 @@ struct MppH264Source {
size_t
frame_capacity
;
size_t
frame_capacity
;
guint
keyframe_req
;
guint
keyframe_req
;
volatile
gboolean
pending_idr
;
volatile
gboolean
pending_idr
;
gboolean
pending_idr_event_sent
;
gint64
last_keyframe_event_us
;
volatile
gboolean
pending_recover
;
volatile
gboolean
pending_recover
;
gulong
enc_sink_probe_id
;
gulong
enc_sink_probe_id
;
guint
bus_watch_id
;
guint
bus_watch_id
;
...
@@ -52,6 +55,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
...
@@ -52,6 +55,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
gint
timeout_ms
,
const
uint8_t
**
data
,
gint
timeout_ms
,
const
uint8_t
**
data
,
size_t
*
size
,
gboolean
*
is_idr
,
size_t
*
size
,
gboolean
*
is_idr
,
guint64
*
pts_ns
);
guint64
*
pts_ns
);
static
gboolean
send_force_key_unit
(
MppH264Source
*
src
);
static
gboolean
bus_watch_cb
(
GstBus
*
bus
,
GstMessage
*
msg
,
gpointer
user_data
)
static
gboolean
bus_watch_cb
(
GstBus
*
bus
,
GstMessage
*
msg
,
gpointer
user_data
)
{
{
...
@@ -283,8 +287,12 @@ static void configure_v4l2_src(GstElement *src, gboolean compressed_mjpeg)
...
@@ -283,8 +287,12 @@ 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 缓冲,降低采集延迟 */
/* 减少内核 DQBUF 缓冲,降低采集延迟(部分 v4l2src 无此属性,先检查) */
{
GParamSpec
*
pspec
=
g_object_class_find_property
(
G_OBJECT_GET_CLASS
(
src
),
"buffer-size"
);
if
(
pspec
)
g_object_set
(
src
,
"buffer-size"
,
1024
,
NULL
);
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
,
...
@@ -300,11 +308,19 @@ static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
...
@@ -300,11 +308,19 @@ static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
"gop"
,
MPP_VIDEO_GOP
,
"gop"
,
MPP_VIDEO_GOP
,
"rc-mode"
,
1
,
/* CBR */
"rc-mode"
,
1
,
/* CBR */
"bps"
,
MPP_VIDEO_START_BPS
,
"bps"
,
MPP_VIDEO_START_BPS
,
"bps-min"
,
(
guint
)(
MPP_VIDEO_START_BPS
*
0
.
80
),
"bps-min"
,
(
guint
)(((
guint64
)
MPP_VIDEO_START_BPS
*
"bps-max"
,
(
guint
)(
MPP_VIDEO_START_BPS
*
1
.
08
),
WEBRTCPUSH_MPP_BPS_MIN_PERCENT
)
/
100U
),
"bps-max"
,
(
guint
)(((
guint64
)
MPP_VIDEO_START_BPS
*
WEBRTCPUSH_MPP_BPS_MAX_PERCENT
)
/
100U
),
"header-mode"
,
1
,
"header-mode"
,
1
,
"profile"
,
MPP_H264_PROFILE
,
"profile"
,
MPP_H264_PROFILE
,
"level"
,
31
,
"level"
,
31
,
"qp-min"
,
WEBRTCPUSH_MPP_QP_MIN
,
"qp-min-i"
,
WEBRTCPUSH_MPP_QP_MIN_I
,
"qp-max"
,
WEBRTCPUSH_MPP_QP_MAX
,
"qp-max-i"
,
WEBRTCPUSH_MPP_QP_MAX_I
,
"min-force-key-unit-interval"
,
(
guint64
)
WEBRTCPUSH_MPP_MIN_FORCE_KEY_UNIT_NS
,
"qos"
,
TRUE
,
"qos"
,
TRUE
,
"zero-copy-pkt"
,
TRUE
,
"zero-copy-pkt"
,
TRUE
,
NULL
);
NULL
);
...
@@ -368,31 +384,73 @@ static GstPadProbeReturn enc_sink_idr_probe(GstPad *pad, GstPadProbeInfo *info,
...
@@ -368,31 +384,73 @@ static GstPadProbeReturn enc_sink_idr_probe(GstPad *pad, GstPadProbeInfo *info,
{
{
MppH264Source
*
src
=
user_data
;
MppH264Source
*
src
=
user_data
;
(
void
)
pad
;
if
(
!
(
GST_PAD_PROBE_INFO_TYPE
(
info
)
&
GST_PAD_PROBE_TYPE_BUFFER
))
if
(
!
(
GST_PAD_PROBE_INFO_TYPE
(
info
)
&
GST_PAD_PROBE_TYPE_BUFFER
))
return
GST_PAD_PROBE_OK
;
return
GST_PAD_PROBE_OK
;
if
(
!
src
||
!
src
->
pending_idr
)
if
(
!
src
||
!
src
->
pending_idr
)
return
GST_PAD_PROBE_OK
;
return
GST_PAD_PROBE_OK
;
{
{
GstEvent
*
ev
=
gst_video_event_new_upstream_force_key_unit
(
gint64
now_us
=
g_get_monotonic_time
();
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
if
(
src
->
pending_idr_event_sent
&&
gboolean
ok
=
gst_pad_send_event
(
pad
,
ev
);
now_us
-
src
->
last_keyframe_event_us
<
MPP_IDR_EVENT_RETRY_US
)
return
GST_PAD_PROBE_OK
;
if
(
!
ok
&&
src
->
enc
)
{
src
->
last_keyframe_event_us
=
now_us
;
ev
=
gst_video_event_new_upstream_force_key_unit
(
if
(
send_force_key_unit
(
src
))
{
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
src
->
pending_idr_event_sent
=
TRUE
;
ok
=
gst_element_send_event
(
src
->
enc
,
ev
);
my_zlog_info
(
"mpp_h264_source: IDR request sent (%u)"
,
src
->
keyframe_req
);
}
if
(
ok
)
{
src
->
pending_idr
=
FALSE
;
my_zlog_info
(
"mpp_h264_source: IDR armed in-stream (%u)"
,
src
->
keyframe_req
);
}
else
{
}
else
{
my_zlog_warn
(
"mpp_h264_source: in-stream IDR not handled"
);
src
->
pending_idr_event_sent
=
FALSE
;
my_zlog_warn
(
"mpp_h264_source: in-stream IDR request not handled"
);
}
}
}
}
return
GST_PAD_PROBE_OK
;
return
GST_PAD_PROBE_OK
;
}
}
static
gboolean
send_force_key_unit
(
MppH264Source
*
src
)
{
GstPad
*
src_pad
;
GstEvent
*
ev
;
GstClockTime
running_time
=
0
;
gboolean
ok
=
FALSE
;
if
(
!
src
||
!
src
->
enc
)
return
FALSE
;
if
(
src
->
pipeline
)
{
GstClock
*
clock
=
gst_element_get_clock
(
src
->
pipeline
);
if
(
clock
)
{
GstClockTime
now
=
gst_clock_get_time
(
clock
);
GstClockTime
base
=
gst_element_get_base_time
(
src
->
pipeline
);
if
(
GST_CLOCK_TIME_IS_VALID
(
now
)
&&
GST_CLOCK_TIME_IS_VALID
(
base
)
&&
now
>=
base
)
running_time
=
now
-
base
;
gst_object_unref
(
clock
);
}
}
/*
* force-key-unit 是 upstream event,应从编码器 src pad 往上游发送。
* 发到 sink pad 会触发 wrong-direction,并且 PLI 后经常等不到真正 IDR。
*/
src_pad
=
gst_element_get_static_pad
(
src
->
enc
,
"src"
);
if
(
src_pad
)
{
ev
=
gst_video_event_new_upstream_force_key_unit
(
running_time
,
TRUE
,
src
->
keyframe_req
);
ok
=
gst_pad_send_event
(
src_pad
,
ev
);
gst_object_unref
(
src_pad
);
}
if
(
!
ok
)
{
ev
=
gst_video_event_new_upstream_force_key_unit
(
running_time
,
TRUE
,
src
->
keyframe_req
);
ok
=
gst_element_send_event
(
src
->
enc
,
ev
);
}
return
ok
;
}
static
void
request_keyframe
(
MppH264Source
*
src
)
static
void
request_keyframe
(
MppH264Source
*
src
)
{
{
if
(
!
src
)
if
(
!
src
)
...
@@ -402,23 +460,10 @@ static void request_keyframe(MppH264Source *src)
...
@@ -402,23 +460,10 @@ static void request_keyframe(MppH264Source *src)
src
->
keyframe_req
++
;
src
->
keyframe_req
++
;
src
->
pending_idr
=
TRUE
;
src
->
pending_idr
=
TRUE
;
/* 直接向编码器 sink pad 发 force-key-unit(upstream event 标准发送方式) */
src
->
pending_idr_event_sent
=
FALSE
;
if
(
src
->
enc
)
{
src
->
last_keyframe_event_us
=
0
;
GstPad
*
sink_pad
=
gst_element_get_static_pad
(
src
->
enc
,
"sink"
);
/* IDR 由 enc_sink_idr_probe 在下一个 buffer 经过时发送,
if
(
sink_pad
)
{
避免外部直接发 sink pad 触发 wrong-direction 警告 */
GstEvent
*
ev
=
gst_video_event_new_upstream_force_key_unit
(
GST_CLOCK_TIME_NONE
,
TRUE
,
src
->
keyframe_req
);
gboolean
ok
=
gst_pad_send_event
(
sink_pad
,
ev
);
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
);
}
}
...
@@ -597,12 +642,40 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
...
@@ -597,12 +642,40 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
vsrc
=
gst_element_factory_make
(
"v4l2src"
,
"vsrc"
);
vsrc
=
gst_element_factory_make
(
"v4l2src"
,
"vsrc"
);
caps_in
=
gst_element_factory_make
(
"capsfilter"
,
"caps_in"
);
caps_in
=
gst_element_factory_make
(
"capsfilter"
,
"caps_in"
);
jpegparse
=
gst_element_factory_make
(
"jpegparse"
,
"jpegparse"
);
jpegparse
=
gst_element_factory_make
(
"jpegparse"
,
"jpegparse"
);
if
(
WEBRTCPUSH_MJPEG_CPU_DECODE
)
{
jpegdec
=
gst_element_factory_make
(
"jpegdec"
,
"jpegdec"
);
use_mpp_jpeg
=
FALSE
;
if
(
jpegdec
)
{
conv
=
gst_element_factory_make
(
"videoconvert"
,
"jpegconv"
);
g_object_set
(
jpegdec
,
"idct-method"
,
1
,
/* ifast */
"discard-corrupted-frames"
,
TRUE
,
"qos"
,
TRUE
,
NULL
);
}
else
{
jpegdec
=
gst_element_factory_make
(
"mppjpegdec"
,
"jpegdec"
);
if
(
jpegdec
)
{
use_mpp_jpeg
=
TRUE
;
g_object_set
(
jpegdec
,
"format"
,
23
,
"fast-mode"
,
TRUE
,
NULL
);
}
}
}
else
{
jpegdec
=
gst_element_factory_make
(
"mppjpegdec"
,
"jpegdec"
);
jpegdec
=
gst_element_factory_make
(
"mppjpegdec"
,
"jpegdec"
);
if
(
jpegdec
)
{
if
(
jpegdec
)
{
use_mpp_jpeg
=
TRUE
;
use_mpp_jpeg
=
TRUE
;
g_object_set
(
jpegdec
,
"format"
,
23
,
"fast-mode"
,
TRUE
,
NULL
);
g_object_set
(
jpegdec
,
"format"
,
23
,
"fast-mode"
,
TRUE
,
NULL
);
}
else
{
}
else
{
jpegdec
=
gst_element_factory_make
(
"jpegdec"
,
"jpegdec"
);
jpegdec
=
gst_element_factory_make
(
"jpegdec"
,
"jpegdec"
);
use_mpp_jpeg
=
FALSE
;
if
(
jpegdec
)
{
conv
=
gst_element_factory_make
(
"videoconvert"
,
"jpegconv"
);
g_object_set
(
jpegdec
,
"idct-method"
,
1
,
"discard-corrupted-frames"
,
TRUE
,
"qos"
,
TRUE
,
NULL
);
}
}
}
}
mjpeg_fps
=
(
capture_mode
==
CAPTURE_MJPEG_720P24
)
?
WEBRTCPUSH_H264_FPS
:
30
;
mjpeg_fps
=
(
capture_mode
==
CAPTURE_MJPEG_720P24
)
?
WEBRTCPUSH_H264_FPS
:
30
;
g_object_set
(
vsrc
,
"device"
,
vdev
,
NULL
);
g_object_set
(
vsrc
,
"device"
,
vdev
,
NULL
);
...
@@ -616,9 +689,10 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
...
@@ -616,9 +689,10 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
gst_caps_unref
(
caps
);
gst_caps_unref
(
caps
);
g_free
(
cap_str
);
g_free
(
cap_str
);
}
}
my_zlog_info
(
"mpp_h264_source: 720p MJPEG@%dfps -> %s -> NV12@%dfps"
,
my_zlog_info
(
"mpp_h264_source: 720p MJPEG@%dfps -> %s
%s
-> NV12@%dfps"
,
mjpeg_fps
,
mjpeg_fps
,
use_mpp_jpeg
?
"mppjpegdec"
:
"jpegdec"
,
use_mpp_jpeg
?
"mppjpegdec"
:
"jpegdec"
,
use_mpp_jpeg
?
""
:
"+videoconvert"
,
WEBRTCPUSH_H264_FPS
);
WEBRTCPUSH_H264_FPS
);
}
}
...
@@ -683,10 +757,19 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
...
@@ -683,10 +757,19 @@ MppH264Source *mpp_h264_source_start(const char *video_device, char **error_mess
return
NULL
;
return
NULL
;
}
}
}
else
{
}
else
{
GstElement
*
elems
[]
=
{
vsrc
,
caps_in
,
jpegparse
,
jpegdec
,
vrate
,
caps_nv12
,
gboolean
linked
;
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
};
if
(
use_mpp_jpeg
)
{
if
(
!
jpegparse
||
!
jpegdec
||
GstElement
*
elems
[]
=
{
vsrc
,
caps_in
,
jpegparse
,
jpegdec
,
vrate
,
!
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
)))
{
caps_nv12
,
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
};
linked
=
jpegparse
&&
jpegdec
&&
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
));
}
else
{
GstElement
*
elems
[]
=
{
vsrc
,
caps_in
,
jpegparse
,
jpegdec
,
conv
,
vrate
,
caps_nv12
,
q
,
enc
,
parse
,
caps_h264
,
eq
,
sink
};
linked
=
jpegparse
&&
jpegdec
&&
conv
&&
link_pipeline
(
pipe
,
elems
,
G_N_ELEMENTS
(
elems
));
}
if
(
!
linked
)
{
if
(
error_message
)
if
(
error_message
)
*
error_message
=
g_strdup
(
"failed to link MJPEG 720p chain"
);
*
error_message
=
g_strdup
(
"failed to link MJPEG 720p chain"
);
gst_object_unref
(
pipe
);
gst_object_unref
(
pipe
);
...
@@ -774,7 +857,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
...
@@ -774,7 +857,7 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
appsink
=
GST_APP_SINK
(
src
->
appsink
);
appsink
=
GST_APP_SINK
(
src
->
appsink
);
deadline
=
g_get_monotonic_time
()
+
(
gint64
)
timeout_ms
*
1000
;
deadline
=
g_get_monotonic_time
()
+
(
gint64
)
timeout_ms
*
1000
;
while
(
attempts
<
20
)
{
while
(
attempts
<
120
)
{
/* require_idr 模式下用 deadline 控制超时,不靠 attempts */
gint
wait_ms
=
(
gint
)((
deadline
-
g_get_monotonic_time
())
/
1000
);
gint
wait_ms
=
(
gint
)((
deadline
-
g_get_monotonic_time
())
/
1000
);
if
(
wait_ms
<
1
)
if
(
wait_ms
<
1
)
...
@@ -796,6 +879,12 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
...
@@ -796,6 +879,12 @@ static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
pts
=
GST_BUFFER_PTS
(
buffer
);
pts
=
GST_BUFFER_PTS
(
buffer
);
idr
=
buffer_is_idr
(
src
->
frame_buf
,
src
->
frame_size
);
idr
=
buffer_is_idr
(
src
->
frame_buf
,
src
->
frame_size
);
if
(
idr
&&
src
->
pending_idr
)
{
src
->
pending_idr
=
FALSE
;
src
->
pending_idr_event_sent
=
FALSE
;
src
->
last_keyframe_event_us
=
0
;
my_zlog_info
(
"mpp_h264_source: IDR output confirmed (%u)"
,
src
->
keyframe_req
);
}
gst_sample_unref
(
sample
);
gst_sample_unref
(
sample
);
if
(
require_idr
&&
!
idr
)
{
if
(
require_idr
&&
!
idr
)
{
...
@@ -826,8 +915,11 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
...
@@ -826,8 +915,11 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
return
FALSE
;
return
FALSE
;
/* bus 报 ERROR 后 pipeline 卡死,这里及时恢复避免长期黑屏 */
/* bus 报 ERROR 后 pipeline 卡死,这里及时恢复避免长期黑屏 */
if
(
src
->
pending_recover
&&
mpp_h264_source_recover
(
src
))
if
(
src
->
pending_recover
&&
mpp_h264_source_recover
(
src
))
{
src
->
pending_idr
=
TRUE
;
src
->
pending_idr
=
TRUE
;
src
->
pending_idr_event_sent
=
FALSE
;
src
->
last_keyframe_event_us
=
0
;
}
if
(
timeout_ms
<
1
)
if
(
timeout_ms
<
1
)
timeout_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
60
;
timeout_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
60
;
...
@@ -838,6 +930,10 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
...
@@ -838,6 +930,10 @@ gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
if
(
pull_sample
(
src
,
TRUE
,
idr_timeout
,
data
,
size
,
is_idr
,
pts_ns
))
if
(
pull_sample
(
src
,
TRUE
,
idr_timeout
,
data
,
size
,
is_idr
,
pts_ns
))
return
TRUE
;
return
TRUE
;
my_zlog_warn
(
"mpp_h264_source: IDR wait timeout, drain latest frame"
);
my_zlog_warn
(
"mpp_h264_source: IDR wait timeout, drain latest frame"
);
/* 超时后清 pending_idr,避免无限重试导致卡死;下一个 PLI 会重新请求 */
src
->
pending_idr
=
FALSE
;
src
->
pending_idr_event_sent
=
FALSE
;
src
->
last_keyframe_event_us
=
0
;
if
(
pull_sample
(
src
,
FALSE
,
500
,
data
,
size
,
is_idr
,
pts_ns
))
if
(
pull_sample
(
src
,
FALSE
,
500
,
data
,
size
,
is_idr
,
pts_ns
))
return
TRUE
;
return
TRUE
;
return
FALSE
;
return
FALSE
;
...
@@ -862,13 +958,21 @@ void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
...
@@ -862,13 +958,21 @@ void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
f
=
gst_element_get_factory
(
src
->
enc
);
f
=
gst_element_get_factory
(
src
->
enc
);
name
=
f
?
gst_plugin_feature_get_name
(
GST_PLUGIN_FEATURE
(
f
))
:
NULL
;
name
=
f
?
gst_plugin_feature_get_name
(
GST_PLUGIN_FEATURE
(
f
))
:
NULL
;
if
(
name
&&
g_str_has_prefix
(
name
,
"mpph264enc"
))
{
if
(
name
&&
g_str_has_prefix
(
name
,
"mpph264enc"
))
{
guint
bmin
=
(
guint
)(
bitrate_bps
*
0
.
80
);
guint
bmin
=
(
guint
)(((
guint64
)
bitrate_bps
*
guint
bmax
=
(
guint
)(
bitrate_bps
*
1
.
08
);
WEBRTCPUSH_MPP_BPS_MIN_PERCENT
)
/
100U
);
if
(
bmin
<
MPP_VIDEO_MIN_BPS
)
guint
bmax
=
(
guint
)(((
guint64
)
bitrate_bps
*
bmin
=
MPP_VIDEO_MIN_BPS
;
WEBRTCPUSH_MPP_BPS_MAX_PERCENT
)
/
100U
);
if
(
bmin
<
WEBRTCPUSH_MPP_BPS_MIN_FLOOR
)
bmin
=
WEBRTCPUSH_MPP_BPS_MIN_FLOOR
;
if
(
bmax
<
bitrate_bps
)
bmax
=
bitrate_bps
;
if
(
bmax
>
MPP_VIDEO_MAX_BPS
)
if
(
bmax
>
MPP_VIDEO_MAX_BPS
)
bmax
=
MPP_VIDEO_MAX_BPS
;
bmax
=
MPP_VIDEO_MAX_BPS
;
g_object_set
(
src
->
enc
,
"bps"
,
bitrate_bps
,
"bps-min"
,
bmin
,
"bps-max"
,
bmax
,
NULL
);
g_object_set
(
src
->
enc
,
"bps"
,
bitrate_bps
,
"bps-min"
,
bmin
,
"bps-max"
,
bmax
,
NULL
);
}
else
if
(
name
&&
g_str_has_prefix
(
name
,
"x264enc"
))
{
}
else
if
(
name
&&
g_str_has_prefix
(
name
,
"x264enc"
))
{
g_object_set
(
src
->
enc
,
"bitrate"
,
bitrate_bps
/
1000
,
NULL
);
g_object_set
(
src
->
enc
,
"bitrate"
,
bitrate_bps
/
1000
,
NULL
);
}
}
...
...
modules/webrtcpush/rtc_client.c
View file @
4687e6c8
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
#include "mpp_h264_source.h"
#include "mpp_h264_source.h"
#include "audio_source.h"
#include "audio_source.h"
#include "audio_sink.h"
#include "volume_control.h"
#include "webrtcpush_config.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include "webrtcpush_log.h"
#include "ws_signaling.h"
#include "ws_signaling.h"
...
@@ -27,6 +29,7 @@ typedef struct {
...
@@ -27,6 +29,7 @@ typedef struct {
gint
payload_type
;
gint
payload_type
;
gchar
*
mid
;
gchar
*
mid
;
gboolean
found
;
gboolean
found
;
gint
direction
;
/* 0=unknown 1=sendonly 2=recvonly 3=sendrecv 4=inactive */
}
OfferAudio
;
}
OfferAudio
;
typedef
struct
{
typedef
struct
{
...
@@ -58,6 +61,7 @@ typedef struct {
...
@@ -58,6 +61,7 @@ typedef struct {
gboolean
track_open
;
gboolean
track_open
;
gboolean
need_idr
;
gboolean
need_idr
;
gboolean
first_frame_sent
;
/* 首帧 IDR 不受 pacing flush 影响 */
gboolean
first_frame_sent
;
/* 首帧 IDR 不受 pacing flush 影响 */
gint64
last_idr_sent_us
;
gboolean
answer_sent
;
gboolean
answer_sent
;
int
pc
;
int
pc
;
int
track
;
int
track
;
...
@@ -80,8 +84,12 @@ typedef struct {
...
@@ -80,8 +84,12 @@ typedef struct {
/* 音频 Opus track */
/* 音频 Opus track */
AudioSource
*
audio_source
;
AudioSource
*
audio_source
;
AudioSink
*
audio_sink
;
VolumeControl
*
volume_ctrl
;
int
audio_track
;
int
audio_track
;
gboolean
audio_track_open
;
gboolean
audio_track_open
;
gboolean
audio_send_enabled
;
gboolean
audio_recv_enabled
;
GThread
*
audio_send_thread
;
GThread
*
audio_send_thread
;
guint32
audio_rtp_timestamp
;
guint32
audio_rtp_timestamp
;
guint64
audio_pts_base
;
guint64
audio_pts_base
;
...
@@ -155,15 +163,23 @@ static void frame_queue_stop(RtcFrameQueue *q)
...
@@ -155,15 +163,23 @@ static void frame_queue_stop(RtcFrameQueue *q)
g_mutex_unlock
(
&
q
->
lock
);
g_mutex_unlock
(
&
q
->
lock
);
}
}
static
void
frame_queue_clear
(
RtcClient
*
client
)
static
guint
frame_queue_clear
(
RtcClient
*
client
)
{
{
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcFrameQueue
*
q
=
&
client
->
frame_queue
;
RtcH264Frame
*
frame
;
RtcH264Frame
*
frame
;
guint
dropped
=
0
;
if
(
!
client
)
return
0
;
g_mutex_lock
(
&
q
->
lock
);
g_mutex_lock
(
&
q
->
lock
);
while
((
frame
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
while
((
frame
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
{
dropped
++
;
q
->
dropped_total
++
;
rtc_h264_frame_free
(
frame
);
rtc_h264_frame_free
(
frame
);
}
g_mutex_unlock
(
&
q
->
lock
);
g_mutex_unlock
(
&
q
->
lock
);
return
dropped
;
}
}
static
void
frame_queue_push
(
RtcClient
*
client
,
RtcH264Frame
*
frame
)
static
void
frame_queue_push
(
RtcClient
*
client
,
RtcH264Frame
*
frame
)
...
@@ -172,6 +188,7 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
...
@@ -172,6 +188,7 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
RtcH264Frame
*
old
;
RtcH264Frame
*
old
;
gboolean
overflowed
=
FALSE
;
gboolean
overflowed
=
FALSE
;
gboolean
request_resync
=
FALSE
;
gboolean
request_resync
=
FALSE
;
guint
dropped
=
0
;
g_mutex_lock
(
&
q
->
lock
);
g_mutex_lock
(
&
q
->
lock
);
if
(
q
->
stopping
)
{
if
(
q
->
stopping
)
{
...
@@ -182,11 +199,13 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
...
@@ -182,11 +199,13 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
if
(
g_queue_get_length
(
&
q
->
queue
)
>=
q
->
max_depth
)
{
if
(
g_queue_get_length
(
&
q
->
queue
)
>=
q
->
max_depth
)
{
overflowed
=
TRUE
;
overflowed
=
TRUE
;
while
((
old
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
{
while
((
old
=
g_queue_pop_head
(
&
q
->
queue
))
!=
NULL
)
{
dropped
++
;
q
->
dropped_total
++
;
q
->
dropped_total
++
;
rtc_h264_frame_free
(
old
);
rtc_h264_frame_free
(
old
);
}
}
}
}
if
(
overflowed
&&
!
frame
->
is_idr
)
{
if
(
overflowed
&&
!
frame
->
is_idr
)
{
dropped
++
;
q
->
dropped_total
++
;
q
->
dropped_total
++
;
rtc_h264_frame_free
(
frame
);
rtc_h264_frame_free
(
frame
);
request_resync
=
TRUE
;
request_resync
=
TRUE
;
...
@@ -203,7 +222,8 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
...
@@ -203,7 +222,8 @@ static void frame_queue_push(RtcClient *client, RtcH264Frame *frame)
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
client
->
last_idr_request_us
=
g_get_monotonic_time
();
client
->
last_idr_request_us
=
g_get_monotonic_time
();
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_warn
(
"libdatachannel: encoded-frame queue overflow; request clean IDR"
);
my_zlog_warn
(
"libdatachannel: encoded-frame queue overflow; dropped=%u frame(s), request clean IDR"
,
dropped
);
}
}
}
}
...
@@ -437,6 +457,7 @@ static void RTC_API on_local_description(int pc, const char *sdp,
...
@@ -437,6 +457,7 @@ static void RTC_API on_local_description(int pc, const char *sdp,
g_free
(
patched
);
g_free
(
patched
);
patched
=
with_msid
;
patched
=
with_msid
;
}
}
my_zlog_info
(
"libdatachannel: SDP answer:
\n
%s"
,
patched
?
patched
:
sdp
);
json
=
build_description_json
(
client
->
app
,
"answer"
,
patched
?
patched
:
sdp
);
json
=
build_description_json
(
client
->
app
,
"answer"
,
patched
?
patched
:
sdp
);
g_free
(
patched
);
g_free
(
patched
);
if
(
!
json
)
{
if
(
!
json
)
{
...
@@ -462,6 +483,7 @@ static void RTC_API on_local_candidate(int pc, const char *candidate,
...
@@ -462,6 +483,7 @@ static void RTC_API on_local_candidate(int pc, const char *candidate,
if
(
!
client
||
!
candidate
||
!
candidate
[
0
])
if
(
!
client
||
!
candidate
||
!
candidate
[
0
])
return
;
return
;
my_zlog_info
(
"libdatachannel: local candidate mid=%s: %s"
,
mid
?
mid
:
"?"
,
candidate
);
json
=
build_candidate_json
(
client
,
candidate
,
mid
);
json
=
build_candidate_json
(
client
,
candidate
,
mid
);
if
(
!
json
)
if
(
!
json
)
return
;
return
;
...
@@ -560,6 +582,31 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr)
...
@@ -560,6 +582,31 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr)
}
}
}
}
/* 接收手机发来的 Opus RTP,depayload 后送入播放管道 */
static
void
RTC_API
on_audio_message
(
int
track
,
const
char
*
message
,
int
size
,
void
*
ptr
)
{
RtcClient
*
client
=
ptr
;
const
guint8
*
rtp
=
(
const
guint8
*
)
message
;
const
guint8
*
payload
;
int
payload_len
,
header_len
;
(
void
)
track
;
if
(
!
client
||
!
client
->
audio_sink
||
!
client
->
audio_recv_enabled
||
!
message
||
size
<
12
)
return
;
header_len
=
12
+
((
rtp
[
0
]
&
0x0F
)
*
4
);
if
(
size
>
header_len
&&
(
rtp
[
0
]
&
0x10
))
{
int
ext_len
=
((
rtp
[
header_len
+
2
]
<<
8
)
|
rtp
[
header_len
+
3
])
*
4
+
4
;
header_len
+=
ext_len
;
}
if
(
size
<=
header_len
)
return
;
payload
=
rtp
+
header_len
;
payload_len
=
size
-
header_len
;
audio_sink_push_opus
(
client
->
audio_sink
,
payload
,
(
size_t
)
payload_len
);
}
static
void
RTC_API
on_track_open
(
int
track
,
void
*
ptr
)
static
void
RTC_API
on_track_open
(
int
track
,
void
*
ptr
)
{
{
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
...
@@ -573,6 +620,7 @@ static void RTC_API on_track_open(int track, void *ptr)
...
@@ -573,6 +620,7 @@ static void RTC_API on_track_open(int track, void *ptr)
client
->
track_open
=
TRUE
;
client
->
track_open
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
last_idr_sent_us
=
0
;
video_open
=
TRUE
;
video_open
=
TRUE
;
}
}
if
(
client
->
audio_track
==
track
)
{
if
(
client
->
audio_track
==
track
)
{
...
@@ -604,6 +652,7 @@ static void RTC_API on_track_closed(int track, void *ptr)
...
@@ -604,6 +652,7 @@ static void RTC_API on_track_closed(int track, void *ptr)
if
(
client
->
track
==
track
)
{
if
(
client
->
track
==
track
)
{
client
->
track_open
=
FALSE
;
client
->
track_open
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
last_idr_sent_us
=
0
;
video_closed
=
TRUE
;
video_closed
=
TRUE
;
}
}
if
(
client
->
audio_track
==
track
)
{
if
(
client
->
audio_track
==
track
)
{
...
@@ -630,6 +679,7 @@ static void RTC_API on_pli(int track, void *ptr)
...
@@ -630,6 +679,7 @@ static void RTC_API on_pli(int track, void *ptr)
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
gboolean
requested
;
gboolean
requested
;
int
dropped
=
0
;
int
dropped
=
0
;
guint
dropped_frames
=
0
;
if
(
!
client
||
client
->
track
!=
track
)
if
(
!
client
||
client
->
track
!=
track
)
return
;
return
;
requested
=
request_idr
(
client
,
FALSE
);
requested
=
request_idr
(
client
,
FALSE
);
...
@@ -643,9 +693,9 @@ static void RTC_API on_pli(int track, void *ptr)
...
@@ -643,9 +693,9 @@ static void RTC_API on_pli(int track, void *ptr)
g_mutex_lock
(
&
client
->
send_lock
);
g_mutex_lock
(
&
client
->
send_lock
);
dropped
=
rtcClearPacingQueue
(
track
);
dropped
=
rtcClearPacingQueue
(
track
);
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
frame_queue_clear
(
client
);
dropped_frames
=
frame_queue_clear
(
client
);
my_zlog_info
(
"libdatachannel:
PLI received, dropped %d queued RTP packet(s), next is IDR
"
,
my_zlog_info
(
"libdatachannel:
RTCP PLI -> MPP IDR; dropped %d RTP packet(s), %u encoded frame(s)
"
,
dropped
>
0
?
dropped
:
0
);
dropped
>
0
?
dropped
:
0
,
dropped_frames
);
}
}
static
guint
clamp_bitrate
(
guint
bitrate
)
static
guint
clamp_bitrate
(
guint
bitrate
)
...
@@ -702,11 +752,12 @@ static gboolean pacing_queue_snapshot(RtcClient *client, int track,
...
@@ -702,11 +752,12 @@ static gboolean pacing_queue_snapshot(RtcClient *client, int track,
return
TRUE
;
return
TRUE
;
}
}
static
int
clear_stale_pacing
(
RtcClient
*
client
,
int
track
)
static
int
clear_stale_pacing
(
RtcClient
*
client
,
int
track
,
guint
*
dropped_frames_out
)
{
{
int
dropped
=
0
;
int
dropped
=
0
;
guint
dropped_frames
;
request_idr
(
client
,
TRUE
);
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
);
...
@@ -714,22 +765,29 @@ static int clear_stale_pacing(RtcClient *client, int track)
...
@@ -714,22 +765,29 @@ static int clear_stale_pacing(RtcClient *client, int track)
if
(
client
->
track
==
track
)
if
(
client
->
track
==
track
)
dropped
=
rtcClearPacingQueue
(
track
);
dropped
=
rtcClearPacingQueue
(
track
);
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
frame_queue_clear
(
client
);
dropped_frames
=
frame_queue_clear
(
client
);
if
(
dropped_frames_out
)
*
dropped_frames_out
=
dropped_frames
;
return
dropped
;
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
;
guint
old_b
;
if
(
!
client
)
if
(
!
client
)
return
;
return
;
b
=
clamp_bitrate
(
bitrate
);
b
=
clamp_bitrate
(
bitrate
);
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
old_b
=
client
->
target_bitrate
;
client
->
target_bitrate
=
b
;
client
->
target_bitrate
=
b
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
old_b
==
b
)
return
;
if
(
track
>=
0
)
{
if
(
track
>=
0
)
{
g_mutex_lock
(
&
client
->
send_lock
);
g_mutex_lock
(
&
client
->
send_lock
);
if
(
client
->
track
==
track
)
if
(
client
->
track
==
track
)
...
@@ -737,6 +795,9 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
...
@@ -737,6 +795,9 @@ static void apply_bitrate(RtcClient *client, int track, guint bitrate)
g_mutex_unlock
(
&
client
->
send_lock
);
g_mutex_unlock
(
&
client
->
send_lock
);
}
}
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
b
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
b
);
my_zlog_info
(
"libdatachannel: RTCP bitrate control -> MPP encoder=%u->%u kbps pacing=%u kbps"
,
old_b
/
1000
,
b
/
1000
,
pacing_bitrate_for_encoder
(
b
)
/
1000
);
}
}
/* Public Internet: climb slowly so a transient REMB spike cannot build latency. */
/* Public Internet: climb slowly so a transient REMB spike cannot build latency. */
...
@@ -780,6 +841,25 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
...
@@ -780,6 +841,25 @@ static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
cur
/
1000
,
next
/
1000
,
tgt
/
1000
);
cur
/
1000
,
next
/
1000
,
tgt
/
1000
);
}
}
static
guint
bitrate_step_down_toward
(
guint
current
,
guint
ceiling
)
{
guint
diff
;
guint
step
;
if
(
ceiling
>=
current
)
return
current
;
diff
=
current
-
ceiling
;
step
=
(
guint
)(((
guint64
)
current
*
WEBRTCPUSH_REMB_DOWN_STEP_PERCENT
)
/
100U
);
if
(
step
<
WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS
)
step
=
WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS
;
if
(
step
>
WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS
)
step
=
WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS
;
if
(
step
>
diff
)
step
=
diff
;
return
current
-
step
;
}
static
void
RTC_API
on_remb
(
int
track
,
unsigned
int
bitrate
,
void
*
ptr
)
static
void
RTC_API
on_remb
(
int
track
,
unsigned
int
bitrate
,
void
*
ptr
)
{
{
RtcClient
*
client
=
ptr
;
RtcClient
*
client
=
ptr
;
...
@@ -790,8 +870,8 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -790,8 +870,8 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
guint
next
=
0
;
guint
next
=
0
;
guint
down_samples
=
0
;
guint
down_samples
=
0
;
gboolean
severe_down
=
FALSE
;
gboolean
severe_down
=
FALSE
;
gboolean
can_step_down
=
FALSE
;
gint64
now_us
;
gint64
now_us
;
int
dropped
=
0
;
if
(
!
client
||
bitrate
==
0
)
if
(
!
client
||
bitrate
==
0
)
return
;
return
;
...
@@ -803,6 +883,8 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -803,6 +883,8 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
current
=
client
->
target_bitrate
;
current
=
client
->
target_bitrate
;
client
->
last_remb_bitrate
=
bitrate
;
client
->
last_remb_bitrate
=
bitrate
;
can_step_down
=
now_us
-
client
->
last_bitrate_ramp_us
>=
(
gint64
)
WEBRTCPUSH_BITRATE_RAMP_DOWN_MS
*
1000
;
if
(
client
->
remb_filtered
==
0
)
{
if
(
client
->
remb_filtered
==
0
)
{
filtered
=
bitrate
;
filtered
=
bitrate
;
...
@@ -834,15 +916,17 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -834,15 +916,17 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
down_samples
=
client
->
remb_down_samples
;
down_samples
=
client
->
remb_down_samples
;
if
(
severe_down
||
if
(
severe_down
||
client
->
remb_down_samples
>=
WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
)
{
client
->
remb_down_samples
>=
WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
)
{
if
(
!
can_step_down
)
{
g_mutex_unlock
(
&
client
->
lock
);
my_zlog_debug
(
"libdatachannel: REMB down held raw=%u filtered=%u ceiling=%u current=%u kbps"
,
bitrate
/
1000
,
filtered
/
1000
,
ceiling
/
1000
,
current
/
1000
);
return
;
}
client
->
remb_down_samples
=
0
;
client
->
remb_down_samples
=
0
;
client
->
target_bitrate
=
ceiling
;
/* target_bitrate is committed by apply_bitrate() after optional flush. */
client
->
last_bitrate_ramp_us
=
now_us
;
client
->
last_bitrate_ramp_us
=
now_us
;
next
=
ceiling
;
next
=
bitrate_step_down_toward
(
current
,
ceiling
);
}
if
(
severe_down
)
{
client
->
need_idr
=
TRUE
;
client
->
last_idr_request_us
=
now_us
;
client
->
source_pts_valid
=
FALSE
;
}
}
}
else
if
(
ceiling
>=
current
)
{
}
else
if
(
ceiling
>=
current
)
{
client
->
remb_down_samples
=
0
;
client
->
remb_down_samples
=
0
;
...
@@ -850,25 +934,15 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
...
@@ -850,25 +934,15 @@ static void RTC_API on_remb(int track, unsigned int bitrate, void *ptr)
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
next
)
{
if
(
next
)
{
g_mutex_lock
(
&
client
->
send_lock
);
apply_bitrate
(
client
,
track
,
next
);
if
(
client
->
track
==
track
)
{
if
(
severe_down
)
{
dropped
=
rtcClearPacingQueue
(
track
);
frame_queue_clear
(
client
);
}
rtcSetPacingBitrate
(
track
,
pacing_bitrate_for_encoder
(
next
));
}
g_mutex_unlock
(
&
client
->
send_lock
);
mpp_h264_source_set_bitrate
(
client
->
h264_source
,
next
);
if
(
severe_down
)
{
if
(
severe_down
)
{
my_zlog_info
(
"libdatachannel: R
EMB raw=%u filtered=%u kbps, encoder=%u pacing=%u kbps, resync dropped=%d RTP packets
"
,
my_zlog_info
(
"libdatachannel: R
TCP REMB raw=%u filtered=%u kbps, encoder=%u kbps ceiling=%u kbps, severe smoothed
"
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
pacing_bitrate_for_encoder
(
next
)
/
1000
,
ceiling
/
1000
);
dropped
>
0
?
dropped
:
0
);
}
else
{
}
else
{
my_zlog_info
(
"libdatachannel: R
EMB raw=%u filtered=%u kbps, encoder=%u pac
ing=%u kbps after %u samples"
,
my_zlog_info
(
"libdatachannel: R
TCP REMB raw=%u filtered=%u kbps, encoder=%u kbps ceil
ing=%u kbps after %u samples"
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
bitrate
/
1000
,
filtered
/
1000
,
next
/
1000
,
pacing_bitrate_for_encoder
(
next
)
/
1000
,
down_samples
);
ceiling
/
1000
,
down_samples
);
}
}
}
else
if
(
ceiling
>
current
)
{
}
else
if
(
ceiling
>
current
)
{
my_zlog_debug
(
"libdatachannel: REMB raw=%u filtered=%u ceiling=%u kbps (slow ramp)"
,
my_zlog_debug
(
"libdatachannel: REMB raw=%u filtered=%u ceiling=%u kbps (slow ramp)"
,
...
@@ -916,13 +990,11 @@ static gpointer pull_thread_main(gpointer data)
...
@@ -916,13 +990,11 @@ static gpointer pull_thread_main(gpointer data)
RtcClient
*
client
=
data
;
RtcClient
*
client
=
data
;
const
gint
pull_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
100
;
const
gint
pull_ms
=
(
1000
/
WEBRTCPUSH_H264_FPS
)
+
100
;
guint
pull_fail_streak
=
0
;
guint
pull_fail_streak
=
0
;
guint
idr_discard_streak
=
0
;
gint64
last_pull_warn_us
=
0
;
gint64
last_pull_warn_us
=
0
;
for
(;;)
{
for
(;;)
{
gboolean
stopping
;
gboolean
stopping
;
gboolean
track_open
;
gboolean
track_open
;
gboolean
force_idr
;
gboolean
need_idr_now
;
gboolean
need_idr_now
;
const
guint8
*
frame_data
;
const
guint8
*
frame_data
;
size_t
frame_size
;
size_t
frame_size
;
...
@@ -933,17 +1005,22 @@ static gpointer pull_thread_main(gpointer data)
...
@@ -933,17 +1005,22 @@ static gpointer pull_thread_main(gpointer data)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
stopping
=
client
->
stopping
;
stopping
=
client
->
stopping
;
track_open
=
client
->
track_open
&&
client
->
track
>=
0
;
track_open
=
client
->
track_open
&&
client
->
track
>=
0
;
force_idr
=
track_open
&&
client
->
need_idr
;
need_idr_now
=
track_open
&&
client
->
need_idr
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
if
(
stopping
)
break
;
break
;
if
(
!
track_open
)
{
g_usleep
(
20000
);
continue
;
}
{
if
(
need_idr_now
)
gint
pull_timeout
=
force_idr
?
2500
:
pull_ms
;
mpp_h264_source_request_keyframe
(
client
->
h264_source
)
;
if
(
!
mpp_h264_source_pull
(
client
->
h264_source
,
force_idr
,
&
frame_data
,
{
if
(
!
mpp_h264_source_pull
(
client
->
h264_source
,
FALSE
,
&
frame_data
,
&
frame_size
,
&
is_idr
,
&
source_pts
,
&
frame_size
,
&
is_idr
,
&
source_pts
,
pull_
timeout
))
{
pull_
ms
))
{
gint64
now_us
=
g_get_monotonic_time
();
gint64
now_us
=
g_get_monotonic_time
();
pull_fail_streak
++
;
pull_fail_streak
++
;
if
(
now_us
-
last_pull_warn_us
>=
3000000
||
if
(
now_us
-
last_pull_warn_us
>=
3000000
||
...
@@ -967,22 +1044,14 @@ static gpointer pull_thread_main(gpointer data)
...
@@ -967,22 +1044,14 @@ static gpointer pull_thread_main(gpointer data)
if
(
!
track_open
)
if
(
!
track_open
)
continue
;
continue
;
if
(
is_idr
)
{
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
need_idr_now
=
client
->
need_idr
;
if
(
client
->
need_idr
)
{
g_mutex_unlock
(
&
client
->
lock
);
if
(
need_idr_now
&&
!
is_idr
)
{
idr_discard_streak
++
;
if
(
idr_discard_streak
>=
WEBRTCPUSH_H264_FPS
)
{
g_mutex_lock
(
&
client
->
lock
);
client
->
need_idr
=
FALSE
;
client
->
need_idr
=
FALSE
;
g_mutex_unlock
(
&
client
->
lock
);
client
->
last_idr_sent_us
=
g_get_monotonic_time
();
idr_discard_streak
=
0
;
my_zlog_warn
(
"libdatachannel: IDR wait >2s, resume with latest frames"
);
}
}
continue
;
g_mutex_unlock
(
&
client
->
lock
)
;
}
}
idr_discard_streak
=
0
;
queued
=
rtc_h264_frame_new
(
frame_data
,
frame_size
,
is_idr
,
source_pts
);
queued
=
rtc_h264_frame_new
(
frame_data
,
frame_size
,
is_idr
,
source_pts
);
frame_queue_push
(
client
,
queued
);
frame_queue_push
(
client
,
queued
);
...
@@ -1047,11 +1116,33 @@ static gpointer send_thread_main(gpointer data)
...
@@ -1047,11 +1116,33 @@ static gpointer send_thread_main(gpointer data)
client
->
first_frame_sent
&&
client
->
first_frame_sent
&&
now
-
last_pacing_flush_us
>=
now
-
last_pacing_flush_us
>=
(
gint64
)
WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS
*
1000
)
{
(
gint64
)
WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS
*
1000
)
{
int
dropped
=
clear_stale_pacing
(
client
,
track
);
guint
dropped_frames
=
0
;
int
dropped
=
clear_stale_pacing
(
client
,
track
,
&
dropped_frames
);
gboolean
should_request_idr
;
gint64
last_idr_us
;
gboolean
need_idr_pending
;
last_pacing_flush_us
=
now
;
last_pacing_flush_us
=
now
;
my_zlog_warn
(
"libdatachannel: pacing backlog %u packets/%u bytes ~= %ums; dropped=%d and request IDR"
,
g_mutex_lock
(
&
client
->
lock
);
last_idr_us
=
client
->
last_idr_sent_us
;
need_idr_pending
=
client
->
need_idr
;
should_request_idr
=
!
need_idr_pending
&&
(
last_idr_us
==
0
||
now
-
last_idr_us
>=
(
gint64
)
WEBRTCPUSH_PACING_RESYNC_IDR_MS
*
1000
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
should_request_idr
)
{
request_idr
(
client
,
TRUE
);
my_zlog_warn
(
"libdatachannel: pacing backlog %u packets/%u bytes ~= %ums; dropped=%d RTP/%u encoded and request IDR"
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
dropped
>
0
?
dropped
:
0
);
dropped
>
0
?
dropped
:
0
,
dropped_frames
);
}
else
{
my_zlog_warn
(
"libdatachannel: pacing backlog %u packets/%u bytes ~= %ums; dropped=%d RTP/%u encoded without IDR (recent/pending)"
,
pacing_packets
,
pacing_bytes
,
pacing_delay_ms
,
dropped
>
0
?
dropped
:
0
,
dropped_frames
);
}
}
}
}
}
...
@@ -1145,8 +1236,10 @@ static gpointer send_thread_main(gpointer data)
...
@@ -1145,8 +1236,10 @@ static gpointer send_thread_main(gpointer data)
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
rtp_timestamp
=
timestamp
+
timestamp_step
;
client
->
rtp_timestamp
=
timestamp
+
timestamp_step
;
if
(
frame
->
is_idr
)
if
(
frame
->
is_idr
)
{
client
->
need_idr
=
FALSE
;
client
->
need_idr
=
FALSE
;
client
->
last_idr_sent_us
=
g_get_monotonic_time
();
}
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
rtc_h264_frame_free
(
frame
);
rtc_h264_frame_free
(
frame
);
...
@@ -1164,6 +1257,7 @@ static gpointer audio_send_thread_main(gpointer data)
...
@@ -1164,6 +1257,7 @@ static gpointer audio_send_thread_main(gpointer data)
for
(;;)
{
for
(;;)
{
gboolean
stopping
;
gboolean
stopping
;
gboolean
audio_send_enabled
;
int
track
;
int
track
;
const
uint8_t
*
frame_data
;
const
uint8_t
*
frame_data
;
size_t
frame_size
;
size_t
frame_size
;
...
@@ -1177,14 +1271,17 @@ static gpointer audio_send_thread_main(gpointer data)
...
@@ -1177,14 +1271,17 @@ static gpointer audio_send_thread_main(gpointer data)
break
;
break
;
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
while
(
!
client
->
stopping
&&
(
!
client
->
audio_track_open
||
client
->
audio_track
<
0
))
while
(
!
client
->
stopping
&&
(
!
client
->
audio_track_open
||
client
->
audio_track
<
0
||
!
client
->
audio_send_enabled
))
g_cond_wait
(
&
client
->
track_cond
,
&
client
->
lock
);
g_cond_wait
(
&
client
->
track_cond
,
&
client
->
lock
);
stopping
=
client
->
stopping
;
stopping
=
client
->
stopping
;
track
=
client
->
audio_track
;
track
=
client
->
audio_track
;
audio_send_enabled
=
client
->
audio_send_enabled
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
if
(
stopping
)
if
(
stopping
)
break
;
break
;
if
(
track
<
0
)
if
(
track
<
0
||
!
audio_send_enabled
)
continue
;
continue
;
if
(
!
audio_source_pull
(
client
->
audio_source
,
&
frame_data
,
&
frame_size
,
if
(
!
audio_source_pull
(
client
->
audio_source
,
&
frame_data
,
&
frame_size
,
...
@@ -1239,6 +1336,7 @@ static void offer_video_clear(OfferVideo *video)
...
@@ -1239,6 +1336,7 @@ static void offer_video_clear(OfferVideo *video)
static
void
offer_audio_clear
(
OfferAudio
*
audio
)
static
void
offer_audio_clear
(
OfferAudio
*
audio
)
{
{
audio
->
direction
=
0
;
g_free
(
audio
->
mid
);
g_free
(
audio
->
mid
);
memset
(
audio
,
0
,
sizeof
(
*
audio
));
memset
(
audio
,
0
,
sizeof
(
*
audio
));
audio
->
payload_type
=
-
1
;
audio
->
payload_type
=
-
1
;
...
@@ -1290,6 +1388,14 @@ static gboolean parse_offer(const char *sdp, OfferVideo *video, OfferAudio *audi
...
@@ -1290,6 +1388,14 @@ static gboolean parse_offer(const char *sdp, OfferVideo *video, OfferAudio *audi
if
(
g_str_has_prefix
(
line
,
"a=mid:"
))
{
if
(
g_str_has_prefix
(
line
,
"a=mid:"
))
{
g_free
(
audio
->
mid
);
g_free
(
audio
->
mid
);
audio
->
mid
=
g_strdup
(
line
+
strlen
(
"a=mid:"
));
audio
->
mid
=
g_strdup
(
line
+
strlen
(
"a=mid:"
));
}
else
if
(
g_str_has_prefix
(
line
,
"a=sendonly"
))
{
audio
->
direction
=
1
;
}
else
if
(
g_str_has_prefix
(
line
,
"a=recvonly"
))
{
audio
->
direction
=
2
;
}
else
if
(
g_str_has_prefix
(
line
,
"a=sendrecv"
))
{
audio
->
direction
=
3
;
}
else
if
(
g_str_has_prefix
(
line
,
"a=inactive"
))
{
audio
->
direction
=
4
;
}
else
if
(
g_str_has_prefix
(
line
,
"a=rtpmap:"
))
{
}
else
if
(
g_str_has_prefix
(
line
,
"a=rtpmap:"
))
{
gint
pt
=
-
1
;
gint
pt
=
-
1
;
gchar
codec
[
32
]
=
{
0
};
gchar
codec
[
32
]
=
{
0
};
...
@@ -1328,8 +1434,11 @@ static void destroy_peer(RtcClient *client)
...
@@ -1328,8 +1434,11 @@ static void destroy_peer(RtcClient *client)
client
->
track
=
-
1
;
client
->
track
=
-
1
;
client
->
track_open
=
FALSE
;
client
->
track_open
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
first_frame_sent
=
FALSE
;
client
->
last_idr_sent_us
=
0
;
client
->
audio_track
=
-
1
;
client
->
audio_track
=
-
1
;
client
->
audio_track_open
=
FALSE
;
client
->
audio_track_open
=
FALSE
;
client
->
audio_send_enabled
=
FALSE
;
client
->
audio_recv_enabled
=
FALSE
;
client
->
answer_sent
=
FALSE
;
client
->
answer_sent
=
FALSE
;
client
->
need_idr
=
TRUE
;
client
->
need_idr
=
TRUE
;
client
->
source_pts_valid
=
FALSE
;
client
->
source_pts_valid
=
FALSE
;
...
@@ -1426,12 +1535,34 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
...
@@ -1426,12 +1535,34 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
rtcTrackInit
track_init
;
rtcTrackInit
track_init
;
rtcPacketizerInit
packetizer
;
rtcPacketizerInit
packetizer
;
guint32
ssrc
=
g_random_int
();
guint32
ssrc
=
g_random_int
();
gboolean
offer_send
;
gboolean
offer_recv
;
gboolean
send_audio
;
gboolean
recv_audio
;
int
track
;
int
track
;
if
(
ssrc
==
0
)
if
(
ssrc
==
0
)
ssrc
=
1
;
ssrc
=
1
;
memset
(
&
track_init
,
0
,
sizeof
(
track_init
));
memset
(
&
track_init
,
0
,
sizeof
(
track_init
));
/* SDP 未声明 direction 时按 WebRTC 默认 sendrecv 处理。 */
offer_send
=
(
audio
->
direction
==
0
||
audio
->
direction
==
1
||
audio
->
direction
==
3
);
offer_recv
=
(
audio
->
direction
==
0
||
audio
->
direction
==
2
||
audio
->
direction
==
3
);
send_audio
=
offer_recv
&&
client
->
audio_source
;
recv_audio
=
offer_send
&&
client
->
audio_sink
;
if
(
send_audio
&&
recv_audio
)
track_init
.
direction
=
RTC_DIRECTION_SENDRECV
;
else
if
(
send_audio
)
track_init
.
direction
=
RTC_DIRECTION_SENDONLY
;
track_init
.
direction
=
RTC_DIRECTION_SENDONLY
;
else
if
(
recv_audio
)
track_init
.
direction
=
RTC_DIRECTION_RECVONLY
;
else
track_init
.
direction
=
RTC_DIRECTION_INACTIVE
;
my_zlog_info
(
"libdatachannel: audio track direction: offer=%d answer=%d"
,
audio
->
direction
,
(
int
)
track_init
.
direction
);
track_init
.
codec
=
RTC_CODEC_OPUS
;
track_init
.
codec
=
RTC_CODEC_OPUS
;
track_init
.
payloadType
=
audio
->
payload_type
;
track_init
.
payloadType
=
audio
->
payload_type
;
track_init
.
ssrc
=
ssrc
;
track_init
.
ssrc
=
ssrc
;
...
@@ -1447,6 +1578,8 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
...
@@ -1447,6 +1578,8 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
rtcSetOpenCallback
(
track
,
on_track_open
);
rtcSetOpenCallback
(
track
,
on_track_open
);
rtcSetClosedCallback
(
track
,
on_track_closed
);
rtcSetClosedCallback
(
track
,
on_track_closed
);
rtcSetErrorCallback
(
track
,
on_track_error
);
rtcSetErrorCallback
(
track
,
on_track_error
);
if
(
recv_audio
)
rtcSetMessageCallback
(
track
,
on_audio_message
);
memset
(
&
packetizer
,
0
,
sizeof
(
packetizer
));
memset
(
&
packetizer
,
0
,
sizeof
(
packetizer
));
packetizer
.
ssrc
=
ssrc
;
packetizer
.
ssrc
=
ssrc
;
...
@@ -1456,16 +1589,21 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
...
@@ -1456,16 +1589,21 @@ static gboolean setup_audio_track(RtcClient *client, int pc,
packetizer
.
sequenceNumber
=
(
guint16
)
g_random_int
();
packetizer
.
sequenceNumber
=
(
guint16
)
g_random_int
();
packetizer
.
timestamp
=
g_random_int
();
packetizer
.
timestamp
=
g_random_int
();
if
(
send_audio
)
{
if
(
rtcSetOpusPacketizer
(
track
,
&
packetizer
)
<
0
||
if
(
rtcSetOpusPacketizer
(
track
,
&
packetizer
)
<
0
||
rtcChainRtcpSrReporter
(
track
)
<
0
||
rtcChainRtcpSrReporter
(
track
)
<
0
||
rtcChainRtcpNackResponder
(
track
,
WEBRTCPUSH_NACK_PACKETS
)
<
0
)
{
rtcChainRtcpNackResponder
(
track
,
WEBRTCPUSH_NACK_PACKETS
)
<
0
)
{
rtcDeleteTrack
(
track
);
rtcDeleteTrack
(
track
);
return
FALSE
;
return
FALSE
;
}
}
}
/* recv_audio 的 RTP payload 由 on_audio_message 送入 audio_sink。 */
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
audio_rtp_timestamp
=
packetizer
.
timestamp
;
client
->
audio_rtp_timestamp
=
packetizer
.
timestamp
;
client
->
audio_pts_valid
=
FALSE
;
client
->
audio_pts_valid
=
FALSE
;
client
->
audio_send_enabled
=
send_audio
;
client
->
audio_recv_enabled
=
recv_audio
;
g_mutex_unlock
(
&
client
->
lock
);
g_mutex_unlock
(
&
client
->
lock
);
*
track_out
=
track
;
*
track_out
=
track
;
return
TRUE
;
return
TRUE
;
...
@@ -1547,6 +1685,22 @@ int rtc_client_start(AppState *app)
...
@@ -1547,6 +1685,22 @@ int rtc_client_start(AppState *app)
g_free
(
audio_err
);
g_free
(
audio_err
);
}
}
}
}
/* 音频播放(手机->设备) */
{
char
*
play_err
=
NULL
;
client
->
audio_sink
=
audio_sink_start
(
WEBRTCPUSH_AUDIO_PLAYBACK_DEVICE
,
&
play_err
);
if
(
client
->
audio_sink
)
{
my_zlog_info
(
"libdatachannel: audio sink started device=%s"
,
WEBRTCPUSH_AUDIO_PLAYBACK_DEVICE
);
if
(
app
->
dev_room
&&
app
->
dev_room
[
0
])
{
client
->
volume_ctrl
=
volume_control_start
(
app
->
dev_room
,
client
->
audio_sink
);
my_zlog_info
(
"libdatachannel: volume control started vehicle=%s"
,
app
->
dev_room
);
}
}
else
{
my_zlog_warn
(
"libdatachannel: audio sink start failed: %s"
,
play_err
?
play_err
:
"?"
);
g_free
(
play_err
);
}
}
my_zlog_info
(
"libdatachannel: MPP live source started device=%s mpp=%d fps=%d threads=pull+send queue=%u"
,
my_zlog_info
(
"libdatachannel: MPP live source started device=%s mpp=%d fps=%d threads=pull+send queue=%u"
,
video_device
,
video_device
,
mpp_h264_source_is_mpp_encoder
(
client
->
h264_source
)
?
1
:
0
,
mpp_h264_source_is_mpp_encoder
(
client
->
h264_source
)
?
1
:
0
,
...
@@ -1580,6 +1734,10 @@ void rtc_client_stop(AppState *app)
...
@@ -1580,6 +1734,10 @@ void rtc_client_stop(AppState *app)
g_thread_join
(
client
->
send_thread
);
g_thread_join
(
client
->
send_thread
);
if
(
client
->
audio_send_thread
)
if
(
client
->
audio_send_thread
)
g_thread_join
(
client
->
audio_send_thread
);
g_thread_join
(
client
->
audio_send_thread
);
if
(
client
->
volume_ctrl
)
volume_control_stop
(
client
->
volume_ctrl
);
if
(
client
->
audio_sink
)
audio_sink_stop
(
client
->
audio_sink
);
if
(
client
->
audio_source
)
if
(
client
->
audio_source
)
audio_source_stop
(
client
->
audio_source
);
audio_source_stop
(
client
->
audio_source
);
...
@@ -1653,16 +1811,18 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
...
@@ -1653,16 +1811,18 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
return
-
1
;
return
-
1
;
}
}
/* 若手机 offer 包含 audio m-line 且设备端音频源已启动,建 Opus track */
/* 若手机 offer 包含 audio m-line,按本机麦克风/喇叭能力协商方向。 */
if
(
audio
.
found
&&
audio
.
payload_type
>=
0
&&
client
->
audio_source
)
{
if
(
audio
.
found
&&
audio
.
payload_type
>=
0
&&
(
client
->
audio_source
||
client
->
audio_sink
))
{
if
(
setup_audio_track
(
client
,
pc
,
&
audio
,
&
audio_track
))
{
if
(
setup_audio_track
(
client
,
pc
,
&
audio
,
&
audio_track
))
{
my_zlog_info
(
"libdatachannel: Opus audio track created mid=%s pt=%d"
,
my_zlog_info
(
"libdatachannel: Opus audio track created mid=%s pt=%d
dir=%d
"
,
audio
.
mid
,
audio
.
payload_type
);
audio
.
mid
,
audio
.
payload_type
,
audio
.
direction
);
}
else
{
}
else
{
my_zlog_warn
(
"libdatachannel: failed to create Opus audio track"
);
my_zlog_warn
(
"libdatachannel: failed to create Opus audio track"
);
}
}
}
}
g_mutex_lock
(
&
client
->
lock
);
g_mutex_lock
(
&
client
->
lock
);
client
->
pc
=
pc
;
client
->
pc
=
pc
;
client
->
track
=
track
;
client
->
track
=
track
;
...
...
modules/webrtcpush/volume_control.c
0 → 100644
View file @
4687e6c8
#include "volume_control.h"
#include "webrtcpush_log.h"
#include <libsoup/soup.h>
#include <json-glib/json-glib.h>
#include <math.h>
/*
* 定时拉取后端音量接口并设置到 AudioSink。
*
* 接口:GET https://fcrs-api.yd-ss.com/api/drive/use/status/{vehicleId}
* 响应 JSON 中 businessJson 是字符串(需二次解析),取 volume 字段(0~1)。
* businessJson 可能出现在 data.businessJson / data.data.businessJson / data.result.businessJson。
* 每 3 秒拉一次,与手机端逻辑对齐。
*/
#define VC_INTERVAL_SEC 3
#define VC_DEFAULT_VOLUME 0.5
#define VC_VOLUME_EPSILON 0.001
/* 音量变化阈值,避免重复设置 */
#define VC_HTTP_TIMEOUT_SEC 5
#define VC_API_BASE "https://fcrs-api.yd-ss.com/api/drive/use/status/"
struct
VolumeControl
{
gchar
*
vehicle_id
;
AudioSink
*
sink
;
SoupSession
*
session
;
GThread
*
thread
;
gint
stop
;
/* 原子停止标志 */
gdouble
current_volume
;
};
/*
* 在 JsonObject 中查找 businessJson 字符串。
* 兼容 data.businessJson / data.data.businessJson / data.result.businessJson
* 以及顶层直接 businessJson 的情况。
*/
static
const
gchar
*
find_business_json
(
JsonObject
*
obj
)
{
JsonNode
*
node
;
JsonObject
*
child
;
if
(
!
obj
)
return
NULL
;
/* 顶层 businessJson */
if
(
json_object_has_member
(
obj
,
"businessJson"
))
return
json_object_get_string_member
(
obj
,
"businessJson"
);
/* data.* */
if
(
json_object_has_member
(
obj
,
"data"
))
{
node
=
json_object_get_member
(
obj
,
"data"
);
if
(
JSON_NODE_HOLDS_OBJECT
(
node
))
{
child
=
json_node_get_object
(
node
);
if
(
json_object_has_member
(
child
,
"businessJson"
))
return
json_object_get_string_member
(
child
,
"businessJson"
);
/* data.data.businessJson */
if
(
json_object_has_member
(
child
,
"data"
))
{
JsonNode
*
n2
=
json_object_get_member
(
child
,
"data"
);
if
(
JSON_NODE_HOLDS_OBJECT
(
n2
))
{
JsonObject
*
c2
=
json_node_get_object
(
n2
);
if
(
json_object_has_member
(
c2
,
"businessJson"
))
return
json_object_get_string_member
(
c2
,
"businessJson"
);
}
}
/* data.result.businessJson */
if
(
json_object_has_member
(
child
,
"result"
))
{
JsonNode
*
n2
=
json_object_get_member
(
child
,
"result"
);
if
(
JSON_NODE_HOLDS_OBJECT
(
n2
))
{
JsonObject
*
c2
=
json_node_get_object
(
n2
);
if
(
json_object_has_member
(
c2
,
"businessJson"
))
return
json_object_get_string_member
(
c2
,
"businessJson"
);
}
}
}
}
/* result.businessJson */
if
(
json_object_has_member
(
obj
,
"result"
))
{
node
=
json_object_get_member
(
obj
,
"result"
);
if
(
JSON_NODE_HOLDS_OBJECT
(
node
))
{
child
=
json_node_get_object
(
node
);
if
(
json_object_has_member
(
child
,
"businessJson"
))
return
json_object_get_string_member
(
child
,
"businessJson"
);
}
}
return
NULL
;
}
/* 从 HTTP 响应体解析音量,范围 0~1。失败返回 -1.0 */
static
gdouble
parse_volume_from_response
(
const
gchar
*
body
,
gsize
len
)
{
JsonParser
*
parser
;
JsonNode
*
root
;
JsonObject
*
obj
;
const
gchar
*
biz_json
;
gdouble
volume
=
-
1
.
0
;
GError
*
err
=
NULL
;
parser
=
json_parser_new
();
if
(
!
json_parser_load_from_data
(
parser
,
body
,
len
,
&
err
))
{
my_zlog_warn
(
"volume_control: parse outer JSON failed: %s"
,
err
?
err
->
message
:
"unknown"
);
if
(
err
)
g_error_free
(
err
);
g_object_unref
(
parser
);
return
-
1
.
0
;
}
root
=
json_parser_get_root
(
parser
);
if
(
!
root
||
!
JSON_NODE_HOLDS_OBJECT
(
root
))
{
g_object_unref
(
parser
);
return
-
1
.
0
;
}
obj
=
json_node_get_object
(
root
);
biz_json
=
find_business_json
(
obj
);
if
(
!
biz_json
||
!
biz_json
[
0
])
{
g_object_unref
(
parser
);
return
-
1
.
0
;
}
/* businessJson 是 JSON 字符串,二次解析取 volume */
{
JsonParser
*
p2
=
json_parser_new
();
JsonNode
*
root2
;
if
(
!
json_parser_load_from_data
(
p2
,
biz_json
,
-
1
,
&
err
))
{
my_zlog_warn
(
"volume_control: parse businessJson failed: %s"
,
err
?
err
->
message
:
"unknown"
);
if
(
err
)
g_error_free
(
err
);
g_object_unref
(
p2
);
g_object_unref
(
parser
);
return
-
1
.
0
;
}
root2
=
json_parser_get_root
(
p2
);
if
(
root2
&&
JSON_NODE_HOLDS_OBJECT
(
root2
))
{
JsonObject
*
bobj
=
json_node_get_object
(
root2
);
if
(
json_object_has_member
(
bobj
,
"volume"
))
{
JsonNode
*
vn
=
json_object_get_member
(
bobj
,
"volume"
);
if
(
!
json_node_is_null
(
vn
))
volume
=
json_node_get_double
(
vn
);
}
}
g_object_unref
(
p2
);
}
g_object_unref
(
parser
);
return
volume
;
}
static
void
fetch_and_apply_volume
(
VolumeControl
*
vc
)
{
SoupMessage
*
msg
;
gchar
*
url
;
guint
status
;
const
gchar
*
body
;
gsize
body_len
;
gdouble
vol
;
url
=
g_strdup_printf
(
"%s%s"
,
VC_API_BASE
,
vc
->
vehicle_id
);
msg
=
soup_message_new
(
"GET"
,
url
);
if
(
!
msg
)
{
my_zlog_warn
(
"volume_control: failed to create request for %s"
,
url
);
g_free
(
url
);
return
;
}
status
=
soup_session_send_message
(
vc
->
session
,
msg
);
if
(
status
!=
200
)
{
my_zlog_warn
(
"volume_control: HTTP %u for %s"
,
status
,
url
);
g_object_unref
(
msg
);
g_free
(
url
);
return
;
}
body
=
msg
->
response_body
->
data
;
body_len
=
msg
->
response_body
->
length
;
if
(
!
body
||
body_len
==
0
)
{
my_zlog_warn
(
"volume_control: empty response body for %s"
,
url
);
g_object_unref
(
msg
);
g_free
(
url
);
return
;
}
vol
=
parse_volume_from_response
(
body
,
body_len
);
if
(
vol
<
0
.
0
)
{
my_zlog_warn
(
"volume_control: volume not found in response"
);
}
else
if
(
fabs
(
vol
-
vc
->
current_volume
)
>
VC_VOLUME_EPSILON
)
{
/* 音量有变化才设置并记日志,避免噪音 */
audio_sink_set_volume
(
vc
->
sink
,
vol
);
vc
->
current_volume
=
vol
;
my_zlog_info
(
"volume_control: volume set to %.3f"
,
vol
);
}
g_object_unref
(
msg
);
g_free
(
url
);
}
static
gpointer
volume_control_thread
(
gpointer
data
)
{
VolumeControl
*
vc
=
data
;
while
(
g_atomic_int_get
(
&
vc
->
stop
)
==
0
)
{
fetch_and_apply_volume
(
vc
);
/* 分段睡眠(3s = 30 * 100ms),便于快速响应停止 */
for
(
gint
i
=
0
;
i
<
VC_INTERVAL_SEC
*
10
;
i
++
)
{
if
(
g_atomic_int_get
(
&
vc
->
stop
)
!=
0
)
break
;
g_usleep
(
100
*
1000
);
/* 100ms */
}
}
return
NULL
;
}
VolumeControl
*
volume_control_start
(
const
char
*
vehicle_id
,
AudioSink
*
sink
)
{
VolumeControl
*
vc
;
if
(
!
vehicle_id
||
!
vehicle_id
[
0
]
||
!
sink
)
{
my_zlog_error
(
"volume_control: invalid args"
);
return
NULL
;
}
vc
=
g_new0
(
VolumeControl
,
1
);
vc
->
vehicle_id
=
g_strdup
(
vehicle_id
);
vc
->
sink
=
sink
;
vc
->
current_volume
=
VC_DEFAULT_VOLUME
;
vc
->
stop
=
0
;
/* 启动时先设默认音量 0.5 */
audio_sink_set_volume
(
sink
,
VC_DEFAULT_VOLUME
);
/* libsoup 同步 session + HTTP 超时(部分版本不支持 async 属性,用 soup_session_new 兜底) */
vc
->
session
=
soup_session_new
();
if
(
vc
->
session
)
{
GParamSpec
*
pspec
;
pspec
=
g_object_class_find_property
(
G_OBJECT_GET_CLASS
(
vc
->
session
),
"timeout"
);
if
(
pspec
)
g_object_set
(
vc
->
session
,
"timeout"
,
(
guint
)
VC_HTTP_TIMEOUT_SEC
,
NULL
);
pspec
=
g_object_class_find_property
(
G_OBJECT_GET_CLASS
(
vc
->
session
),
"idle-timeout"
);
if
(
pspec
)
g_object_set
(
vc
->
session
,
"idle-timeout"
,
(
guint
)
VC_HTTP_TIMEOUT_SEC
,
NULL
);
}
vc
->
thread
=
g_thread_new
(
"volume-control"
,
volume_control_thread
,
vc
);
my_zlog_info
(
"volume_control: started vehicle_id=%s interval=%ds"
,
vehicle_id
,
VC_INTERVAL_SEC
);
return
vc
;
}
void
volume_control_stop
(
VolumeControl
*
vc
)
{
if
(
!
vc
)
return
;
g_atomic_int_set
(
&
vc
->
stop
,
1
);
if
(
vc
->
thread
)
g_thread_join
(
vc
->
thread
);
if
(
vc
->
session
)
g_object_unref
(
vc
->
session
);
g_free
(
vc
->
vehicle_id
);
g_free
(
vc
);
}
modules/webrtcpush/volume_control.h
0 → 100644
View file @
4687e6c8
#ifndef VOLUME_CONTROL_H
#define VOLUME_CONTROL_H
#include "audio_sink.h"
typedef
struct
VolumeControl
VolumeControl
;
/* vehicle_id: 设备号如 CN010200000132;audio_sink: 播放 sink 用于设置音量 */
VolumeControl
*
volume_control_start
(
const
char
*
vehicle_id
,
AudioSink
*
sink
);
void
volume_control_stop
(
VolumeControl
*
vc
);
#endif
modules/webrtcpush/webrtcpush_config.h
View file @
4687e6c8
...
@@ -8,35 +8,46 @@
...
@@ -8,35 +8,46 @@
*
*
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
*/
*/
#define WEBRTCPUSH_USE_MPP
1
#define WEBRTCPUSH_USE_MPP
0
/* 正常固定 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 * 3)
/* 3s; PLI
still requests IDR
*/
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS * 3)
/* 3s; PLI
按需请求 IDR,减少 I 帧码率尖峰
*/
/* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */
/*
#define WEBRTCPUSH_INITIAL_BITRATE 1400000U
* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略。
* 首屏先用 900kbps,不像 1.4Mbps 那样猛冲,也不要低到一进来就糊。
* RTCP REMB 只作为码率趋势,下降也做阶梯平滑,避免 MPP 动态切码率时卡顿。
*/
#define WEBRTCPUSH_INITIAL_BITRATE 900000U
#define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MAX_BITRATE
32
00000U
#define WEBRTCPUSH_MAX_BITRATE
28
00000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
3
U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS
4
U
#define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 2500U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 2500U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 500U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 1500U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 125U
#define WEBRTCPUSH_REMB_DOWN_STEP_PERCENT 15U
#define WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS 80000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS 180000U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 140U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_MAX_BITRATE 4000000U
#define WEBRTCPUSH_PACING_MAX_BITRATE 4000000U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 300U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 250U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 1000U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 2000U
/* 首个/刚恢复的 IDR 允许短暂排队,避免首屏关键帧刚发出就被清队列 */
#define WEBRTCPUSH_PACING_IDR_GRACE_MS 800U
/* pacing 清队列后,若近期已有 IDR,不要反复强制 IDR 造成 I 帧风暴 */
#define WEBRTCPUSH_PACING_RESYNC_IDR_MS 5000U
/* 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
#define WEBRTCPUSH_NACK_PACKETS 512U
#define WEBRTCPUSH_NACK_PACKETS 512U
/* PLI/IDR 节流:避免频繁 force-key-unit 拉高瞬时码率 */
/* PLI/IDR 节流:避免频繁 force-key-unit 拉高瞬时码率 */
#define WEBRTCPUSH_PLI_IDR_THROTTLE_MS
2
00U
#define WEBRTCPUSH_PLI_IDR_THROTTLE_MS
5
00U
/* 周期性推流指标日志间隔 */
/* 周期性推流指标日志间隔 */
#define WEBRTCPUSH_STATS_LOG_INTERVAL_MS 8000U
#define WEBRTCPUSH_STATS_LOG_INTERVAL_MS 8000U
...
@@ -48,13 +59,26 @@
...
@@ -48,13 +59,26 @@
#define WEBRTCPUSH_H264_PROFILE_LEVEL_ID "42e01f"
#define WEBRTCPUSH_H264_PROFILE_LEVEL_ID "42e01f"
/* 拉帧线程与发送线程之间的有界队列(leaky,满则丢最旧帧) */
/* 拉帧线程与发送线程之间的有界队列(leaky,满则丢最旧帧) */
#define WEBRTCPUSH_SEND_QUEUE_DEPTH
2
U
#define WEBRTCPUSH_SEND_QUEUE_DEPTH
1
U
/* 管道缓冲:运动场景需适度缓存,过低易 pull timeout / 糊屏 */
/* 管道缓冲:运动场景需适度缓存,过低易 pull timeout / 糊屏 */
#define WEBRTCPUSH_MPP_PRE_ENC_BUFFERS 1
/* 编码前 leaky,低延迟 */
#define WEBRTCPUSH_MPP_PRE_ENC_BUFFERS 1
/* 编码前 leaky,低延迟 */
#define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 1
/* 编码后保留 AU */
#define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 1
/* 编码后保留 AU */
#define WEBRTCPUSH_APPSINK_MAX_BUFFERS 1
#define WEBRTCPUSH_APPSINK_MAX_BUFFERS 1
/* MJPEG 解压:1=GStreamer CPU jpegdec 优先;0=mppjpegdec 优先 */
#define WEBRTCPUSH_MJPEG_CPU_DECODE 1
/* MPP 码控收紧:低 REMB 时实际 H264 不能长期高于目标太多 */
#define WEBRTCPUSH_MPP_BPS_MIN_PERCENT 60U
#define WEBRTCPUSH_MPP_BPS_MAX_PERCENT 102U
#define WEBRTCPUSH_MPP_BPS_MIN_FLOOR 200000U
#define WEBRTCPUSH_MPP_QP_MAX 48
#define WEBRTCPUSH_MPP_QP_MAX_I 48
#define WEBRTCPUSH_MPP_QP_MIN 18
#define WEBRTCPUSH_MPP_QP_MIN_I 18
#define WEBRTCPUSH_MPP_MIN_FORCE_KEY_UNIT_NS 2000000000ULL
/* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
/* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
#define WEBRTCPUSH_VIDEO_DEVICE "auto"
#define WEBRTCPUSH_VIDEO_DEVICE "auto"
...
@@ -70,6 +94,15 @@
...
@@ -70,6 +94,15 @@
#define WEBRTCPUSH_OPUS_CHANNELS 1
#define WEBRTCPUSH_OPUS_CHANNELS 1
#define WEBRTCPUSH_AUDIO_QUEUE_DEPTH 8U
#define WEBRTCPUSH_AUDIO_QUEUE_DEPTH 8U
/* 音频播放(手机->设备):ALSA 喇叭设备 */
#define WEBRTCPUSH_AUDIO_PLAYBACK_DEVICE "plughw:2,0"
/* 后端音量控制接口 */
#define WEBRTCPUSH_VOLUME_API_BASE "https://fcrs-api.yd-ss.com/api/drive/use/status/"
#define WEBRTCPUSH_VOLUME_INTERVAL_SEC 3
#define WEBRTCPUSH_VOLUME_DEFAULT 0.5
/*
/*
* 设备侧 DataChannel(myDataChannel)与手机 createDataChannel('init') 争用 SCTP,
* 设备侧 DataChannel(myDataChannel)与手机 createDataChannel('init') 争用 SCTP,
* 易触发 sctpenc association error 并导致管道闪断/进程崩溃。仅推流可不建。
* 易触发 sctpenc association error 并导致管道闪断/进程崩溃。仅推流可不建。
...
...
modules/wifi_config/wifi_config_agent.c
View file @
4687e6c8
...
@@ -445,23 +445,38 @@ static void handle_wifi_switch(int sock, const struct sockaddr_in *addr, cJSON *
...
@@ -445,23 +445,38 @@ static void handle_wifi_switch(int sock, const struct sockaddr_in *addr, cJSON *
ssid
=
ssid_item
->
valuestring
;
ssid
=
ssid_item
->
valuestring
;
my_zlog_info
(
"WiFi配置助手: 收到 wifi_switch SSID=%s"
,
ssid
);
my_zlog_info
(
"WiFi配置助手: 收到 wifi_switch SSID=%s"
,
ssid
);
rc
=
app_wifi_switch
(
ssid
);
/* 先校验 SSID 是否已保存(不连接),据此先回复 app,
success
=
(
rc
==
0
);
* 再实际发起切换。避免切到新 WiFi 后 app 收不到响应。 */
if
(
rc
==
0
)
rc
=
app_wifi_check_saved
(
ssid
);
snprintf
(
message
,
sizeof
(
message
),
"WiFi 切换成功,正在连接 %s"
,
ssid
);
if
(
rc
!=
0
)
{
else
if
(
rc
==
-
1
)
/* 未保存:直接回失败 */
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"wifi_switch_result"
);
cJSON_AddBoolToObject
(
resp
,
"success"
,
0
);
snprintf
(
message
,
sizeof
(
message
),
"未找到已保存的 WiFi:%s"
,
ssid
);
snprintf
(
message
,
sizeof
(
message
),
"未找到已保存的 WiFi:%s"
,
ssid
);
else
/* rc == -2:已保存但连接失败 */
cJSON_AddStringToObject
(
resp
,
"message"
,
message
);
snprintf
(
message
,
sizeof
(
message
),
"WiFi 已保存但连接失败:%s(信号弱或不可达)"
,
ssid
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
return
;
}
/* 已保存:立即回成功,告知 app 已发起切换 */
resp
=
cJSON_CreateObject
();
resp
=
cJSON_CreateObject
();
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"wifi_switch_result"
);
cJSON_AddStringToObject
(
resp
,
"cmd"
,
"wifi_switch_result"
);
cJSON_AddBoolToObject
(
resp
,
"success"
,
success
?
1
:
0
);
cJSON_AddBoolToObject
(
resp
,
"success"
,
1
);
snprintf
(
message
,
sizeof
(
message
),
"WiFi 切换成功,正在连接 %s"
,
ssid
);
cJSON_AddStringToObject
(
resp
,
"message"
,
message
);
cJSON_AddStringToObject
(
resp
,
"message"
,
message
);
payload
=
cJSON_PrintUnformatted
(
resp
);
payload
=
cJSON_PrintUnformatted
(
resp
);
if
(
payload
)
{
send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
if
(
payload
)
{
send_json
(
sock
,
addr
,
payload
);
free
(
payload
);
}
cJSON_Delete
(
resp
);
cJSON_Delete
(
resp
);
/* 回复完毕,再实际发起切换(切换结果不再回,切网后 app 收不到) */
rc
=
app_wifi_switch
(
ssid
);
if
(
rc
!=
0
)
my_zlog_error
(
"WiFi配置助手: wifi_switch 实际切换失败 SSID=%s rc=%d"
,
ssid
,
rc
);
else
my_zlog_info
(
"WiFi配置助手: wifi_switch 已发起切换 SSID=%s"
,
ssid
);
}
}
static
void
handle_wifi_delete
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
)
static
void
handle_wifi_delete
(
int
sock
,
const
struct
sockaddr_in
*
addr
,
cJSON
*
root
)
...
...
zlog.conf
View file @
4687e6c8
...
@@ -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
6
.log"
;
millisecond
my_log
.*
"/home/orangepi/car/master/log/log_2026-07-0
8
.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