Commit 2eb47ff8 authored by 957dd's avatar 957dd

未修改完第二次保存提交

parent edadce9a
...@@ -4,6 +4,9 @@ deviceback ...@@ -4,6 +4,9 @@ deviceback
Deviceld.txt Deviceld.txt
main main
# 本地日报/笔记,不提交
docs/
# CMake / build 输出(默认全部忽略) # CMake / build 输出(默认全部忽略)
build/* build/*
build/.ninja_* build/.ninja_*
......
...@@ -95,9 +95,4 @@ target_compile_options(main PRIVATE ${WEBRTCPUSH_CFLAGS}) ...@@ -95,9 +95,4 @@ target_compile_options(main PRIVATE ${WEBRTCPUSH_CFLAGS})
# 安装规则 # 安装规则
install(TARGETS main DESTINATION bin) install(TARGETS main DESTINATION bin)
# carstart.service 使用 /home/orangepi/car/master/main
add_custom_command(TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:main> ${CMAKE_SOURCE_DIR}/main
COMMENT "Copy main to project root for carstart.service"
)
enable_testing() enable_testing()
...@@ -8,7 +8,8 @@ static char s_wifi_last_password[SSID_MAX_LEN]= {0} ; ...@@ -8,7 +8,8 @@ static char s_wifi_last_password[SSID_MAX_LEN]= {0} ;
//获取当前连接wifi //获取当前连接wifi
void get_current_wifi() { void get_current_wifi() {
//显示已连接代码 g_current_ssid[0] = '\0';
FILE *fp = popen("nmcli -t -f ACTIVE,SSID dev wifi", "r"); FILE *fp = popen("nmcli -t -f ACTIVE,SSID dev wifi", "r");
if (!fp) { if (!fp) {
my_zlog_error("{\"error\": \"无法执行扫描命令\"}"); my_zlog_error("{\"error\": \"无法执行扫描命令\"}");
...@@ -413,7 +414,7 @@ void connect_wifi_jking() { ...@@ -413,7 +414,7 @@ void connect_wifi_jking() {
if(system("sudo reboot")!=0){//重启香橙派 if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail"); my_zlog_error("reboot fail");
} }
my_zlog_debug("重启成功"); my_zlog_info("重启成功");
} }
} }
...@@ -500,7 +501,7 @@ void wifi_change_recmqtt(cJSON *body){ ...@@ -500,7 +501,7 @@ void wifi_change_recmqtt(cJSON *body){
if(system("sudo reboot")!=0){//重启香橙派 if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail"); my_zlog_error("reboot fail");
} }
my_zlog_debug("重启成功"); my_zlog_info("重启成功");
} }
if(ret == 0){ if(ret == 0){
if(can_access_internet()==0){ if(can_access_internet()==0){
...@@ -508,7 +509,7 @@ void wifi_change_recmqtt(cJSON *body){ ...@@ -508,7 +509,7 @@ void wifi_change_recmqtt(cJSON *body){
my_zlog_debug("ssid:%S",g_current_ssid); my_zlog_debug("ssid:%S",g_current_ssid);
if(system("sudo reboot")!=0){//重启香橙派 if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail"); my_zlog_error("reboot fail");
my_zlog_debug("重启成功"); my_zlog_info("重启成功");
}else { }else {
delete_wifi_conf(); delete_wifi_conf();
if(change_wifi_connect(default_SSID,default_password)==0) delete_wifi_by_ssid(g_current_ssid); if(change_wifi_connect(default_SSID,default_password)==0) delete_wifi_by_ssid(g_current_ssid);
...@@ -528,7 +529,7 @@ void wifi_change_recmqtt(cJSON *body){ ...@@ -528,7 +529,7 @@ void wifi_change_recmqtt(cJSON *body){
if(system("sudo reboot")!=0){//重启香橙派 if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail"); my_zlog_error("reboot fail");
} }
my_zlog_debug("重启成功"); my_zlog_info("重启成功");
} }
} }
......
...@@ -3,98 +3,245 @@ ...@@ -3,98 +3,245 @@
#include "device_wifi_manager.h" #include "device_wifi_manager.h"
#include "modules_common.h" #include "modules_common.h"
#define MAX_WIFI_ENTRIES 11 #define MAX_WIFI_ENTRIES 32
#define MAX_SAVED_WIFI_SLOTS 10 #define MAX_SAVED_WIFI_SLOTS 10
#define SSID_MAX_LENGTH 64 #define SSID_MAX_LENGTH 64
#define WIFI_PSK_MAX_LENGTH 63
#define NM_CONN_DIR "/etc/NetworkManager/system-connections/" #define NM_CONN_DIR "/etc/NetworkManager/system-connections/"
static ThreadPool_t *s_wifi_pool = NULL;
static pthread_mutex_t s_wifi_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct { typedef struct {
char ssid[SSID_MAX_LENGTH]; char ssid[SSID_MAX_LENGTH];
time_t mtime; time_t mtime;
} saved_wifi_entry_t; } saved_wifi_entry_t;
typedef struct {
char ssid[SSID_MAX_LENGTH];
char password[WIFI_PSK_MAX_LENGTH + 1];
} wifi_save_task_t;
typedef struct {
char ssid[SSID_MAX_LENGTH];
} wifi_delete_task_t;
static void wifi_save_worker(void *arg);
static void wifi_delete_worker(void *arg);
static int submit_wifi_task(void (*worker)(void *), void *arg, const char *what);
static int is_default_wifi_ssid(const char *ssid) { static int is_default_wifi_ssid(const char *ssid) {
return ssid != NULL && strcmp(ssid, default_SSID) == 0; return ssid != NULL && strcmp(ssid, default_SSID) == 0;
} }
static int connection_name_from_filename(const char *filename, char *ssid_out, size_t ssid_size) { static int entry_index_by_name(const saved_wifi_entry_t *entries, int count,
const char *dot = strrchr(filename, '.'); const char *name) {
if (dot && strcmp(dot, ".nmconnection") == 0) { for (int i = 0; i < count; i++) {
size_t name_len = (size_t)(dot - filename); if (strcmp(entries[i].ssid, name) == 0)
if (name_len == 0 || name_len >= ssid_size) { return i;
return -1;
}
strncpy(ssid_out, filename, name_len);
ssid_out[name_len] = '\0';
return 0;
} }
return -1;
}
static void trim_line(char *line) {
size_t n;
if (!line)
return;
n = strcspn(line, "\r\n");
line[n] = '\0';
}
static int read_profile_id(const char *filepath, char *id_out, size_t id_size) {
FILE *fp;
char line[256];
if (strlen(filename) >= ssid_size) { fp = fopen(filepath, "r");
if (!fp)
return -1; return -1;
while (fgets(line, sizeof(line), fp)) {
trim_line(line);
if (strncmp(line, "id=", 3) == 0) {
strncpy(id_out, line + 3, id_size - 1);
id_out[id_size - 1] = '\0';
fclose(fp);
return id_out[0] ? 0 : -1;
}
} }
strncpy(ssid_out, filename, ssid_size - 1);
ssid_out[ssid_size - 1] = '\0'; fclose(fp);
return 0; return -1;
} }
static int is_wifi_nmconnection_file(const char *filepath) { static int is_wifi_profile_file(const char *filepath) {
FILE *fp = fopen(filepath, "r"); FILE *fp;
if (!fp) { char line[256];
fp = fopen(filepath, "r");
if (!fp)
return 0; return 0;
}
char line[256];
int is_wifi = 0;
while (fgets(line, sizeof(line), fp)) { while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "type=wifi", 9) == 0 || trim_line(line);
strncmp(line, "type=802-11-wireless", 20) == 0) { if (strcmp(line, "type=wifi") == 0 ||
is_wifi = 1; strcmp(line, "type=802-11-wireless") == 0) {
break; fclose(fp);
return 1;
} }
} }
fclose(fp); fclose(fp);
return is_wifi; return 0;
} }
static int wifi_connection_exists(const char *ssid) { static int is_wifi_connection_type(const char *type) {
return type && (strcmp(type, "802-11-wireless") == 0 || strcmp(type, "wifi") == 0);
}
/* 解析 nmcli -t 一行:支持 name:device:type 与 name:type 两种格式 */
static int parse_nmcli_connection_line(const char *line, char *name, size_t name_sz,
char *device, size_t dev_sz,
char *type, size_t type_sz) {
const char *c1;
const char *c2;
size_t len;
if (!line || !name || !device || !type)
return -1;
name[0] = device[0] = type[0] = '\0';
c1 = strchr(line, ':');
if (!c1)
return -1;
len = (size_t)(c1 - line);
if (len >= name_sz)
len = name_sz - 1;
memcpy(name, line, len);
name[len] = '\0';
c2 = strchr(c1 + 1, ':');
if (!c2) {
strncpy(type, c1 + 1, type_sz - 1);
type[type_sz - 1] = '\0';
trim_line(type);
return 0;
}
len = (size_t)(c2 - c1 - 1);
if (len >= dev_sz)
len = dev_sz - 1;
memcpy(device, c1 + 1, len);
device[len] = '\0';
strncpy(type, c2 + 1, type_sz - 1);
type[type_sz - 1] = '\0';
trim_line(type);
return 0;
}
/* 无需读文件内容,stat 即可拿到 mtime(普通用户也可 stat root 600 文件) */
static time_t wifi_profile_mtime_from_disk(const char *ssid) {
char path[512]; char path[512];
struct stat st;
DIR *dir;
struct dirent *entry;
time_t oldest = 0;
size_t ssid_len;
if (!ssid || !ssid[0])
return 0;
snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, ssid); snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, ssid);
return access(path, F_OK) == 0; if (stat(path, &st) == 0)
oldest = st.st_mtime;
ssid_len = strlen(ssid);
dir = opendir(NM_CONN_DIR);
if (!dir)
return oldest;
while ((entry = readdir(dir)) != NULL) {
if (!strstr(entry->d_name, ".nmconnection"))
continue;
if (strncmp(entry->d_name, ssid, ssid_len) != 0)
continue;
if (entry->d_name[ssid_len] != '\0' &&
entry->d_name[ssid_len] != '-' &&
entry->d_name[ssid_len] != '.')
continue;
snprintf(path, sizeof(path), "%s%s", NM_CONN_DIR, entry->d_name);
if (stat(path, &st) == 0 && (oldest == 0 || st.st_mtime < oldest))
oldest = st.st_mtime;
}
closedir(dir);
return oldest;
} }
static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count) { static void merge_wifi_entry(saved_wifi_entry_t *entries, int *count, int max_count,
DIR *dir = opendir(NM_CONN_DIR); const char *ssid, time_t mtime) {
if (!dir) { int idx;
my_zlog_error("无法打开NetworkManager配置目录"); time_t mt;
return -1;
if (!ssid || !ssid[0] || !entries || !count)
return;
idx = entry_index_by_name(entries, *count, ssid);
mt = mtime > 0 ? mtime : wifi_profile_mtime_from_disk(ssid);
if (idx >= 0) {
if (mt > 0 && (entries[idx].mtime == 0 || mt < entries[idx].mtime))
entries[idx].mtime = mt;
return;
} }
if (*count >= max_count)
return;
strncpy(entries[*count].ssid, ssid, sizeof(entries[*count].ssid) - 1);
entries[*count].ssid[sizeof(entries[*count].ssid) - 1] = '\0';
entries[*count].mtime = mt;
(*count)++;
}
static int collect_saved_wifi_entries_from_files(saved_wifi_entry_t *entries, int max_count) {
DIR *dir;
struct dirent *entry; struct dirent *entry;
int count = 0; int count = 0;
dir = opendir(NM_CONN_DIR);
if (!dir) {
my_zlog_error("无法打开NetworkManager配置目录");
return -1;
}
while ((entry = readdir(dir)) != NULL && count < max_count) { while ((entry = readdir(dir)) != NULL && count < max_count) {
if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) { char filepath[512];
continue; char profile_id[SSID_MAX_LENGTH];
} struct stat st;
char ssid[SSID_MAX_LENGTH]; if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN)
if (connection_name_from_filename(entry->d_name, ssid, sizeof(ssid)) != 0) { continue;
if (!strstr(entry->d_name, ".nmconnection"))
continue; continue;
}
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s%s", NM_CONN_DIR, entry->d_name); snprintf(filepath, sizeof(filepath), "%s%s", NM_CONN_DIR, entry->d_name);
if (!is_wifi_nmconnection_file(filepath)) { if (!is_wifi_profile_file(filepath))
continue; continue;
} if (read_profile_id(filepath, profile_id, sizeof(profile_id)) != 0)
continue;
struct stat st; if (stat(filepath, &st) != 0)
if (stat(filepath, &st) != 0) {
continue; continue;
{
int idx = entry_index_by_name(entries, count, profile_id);
if (idx >= 0) {
if (st.st_mtime < entries[idx].mtime)
entries[idx].mtime = st.st_mtime;
continue;
}
} }
strncpy(entries[count].ssid, ssid, sizeof(entries[count].ssid) - 1); strncpy(entries[count].ssid, profile_id, sizeof(entries[count].ssid) - 1);
entries[count].ssid[sizeof(entries[count].ssid) - 1] = '\0'; entries[count].ssid[sizeof(entries[count].ssid) - 1] = '\0';
entries[count].mtime = st.st_mtime; entries[count].mtime = st.st_mtime;
count++; count++;
...@@ -104,94 +251,431 @@ static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count ...@@ -104,94 +251,431 @@ static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count
return count; return count;
} }
/* 以 nmcli 列表为准,兼容 name:type 两列输出 */
static int collect_saved_wifi_entries_from_nmcli(saved_wifi_entry_t *entries, int max_count) {
FILE *fp;
char line[256];
char name[SSID_MAX_LENGTH];
char device[64];
char conn_type[64];
int count = 0;
fp = popen("nmcli -t -f NAME,TYPE connection show", "r");
if (!fp)
return 0;
while (fgets(line, sizeof(line), fp) && count < max_count) {
trim_line(line);
if (!line[0])
continue;
if (parse_nmcli_connection_line(line, name, sizeof(name),
device, sizeof(device),
conn_type, sizeof(conn_type)) != 0)
continue;
if (!is_wifi_connection_type(conn_type))
continue;
{
int idx = entry_index_by_name(entries, count, name);
if (idx >= 0)
continue;
}
strncpy(entries[count].ssid, name, sizeof(entries[count].ssid) - 1);
entries[count].ssid[sizeof(entries[count].ssid) - 1] = '\0';
entries[count].mtime = wifi_profile_mtime_from_disk(name);
count++;
}
pclose(fp);
return count;
}
static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count) {
saved_wifi_entry_t file_entries[MAX_WIFI_ENTRIES];
saved_wifi_entry_t nmcli_entries[MAX_WIFI_ENTRIES];
int count = 0;
int file_count;
int nmcli_count;
file_count = collect_saved_wifi_entries_from_files(file_entries, MAX_WIFI_ENTRIES);
if (file_count < 0)
file_count = 0;
for (int i = 0; i < file_count; i++)
merge_wifi_entry(entries, &count, max_count, file_entries[i].ssid, file_entries[i].mtime);
nmcli_count = collect_saved_wifi_entries_from_nmcli(nmcli_entries, MAX_WIFI_ENTRIES);
for (int i = 0; i < nmcli_count; i++)
merge_wifi_entry(entries, &count, max_count, nmcli_entries[i].ssid, nmcli_entries[i].mtime);
return count;
}
static int nmcli_wifi_connection_exists(const char *ssid) {
FILE *fp;
char line[256];
char name[SSID_MAX_LENGTH];
char device[64];
char conn_type[64];
if (!ssid || !ssid[0])
return 0;
fp = popen("nmcli -t -f NAME,TYPE connection show", "r");
if (!fp)
return 0;
while (fgets(line, sizeof(line), fp)) {
trim_line(line);
if (parse_nmcli_connection_line(line, name, sizeof(name),
device, sizeof(device),
conn_type, sizeof(conn_type)) != 0)
continue;
if (!is_wifi_connection_type(conn_type))
continue;
if (strcmp(name, ssid) == 0) {
pclose(fp);
return 1;
}
}
pclose(fp);
return 0;
}
static int wifi_connection_exists(const char *ssid) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
if (count < 0)
return nmcli_wifi_connection_exists(ssid);
if (entry_index_by_name(entries, count, ssid) >= 0)
return 1;
return nmcli_wifi_connection_exists(ssid);
}
static int count_non_default_saved_wifi(void) { static int count_non_default_saved_wifi(void) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES]; saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES); int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
if (count < 0) { if (count < 0)
return -1; return -1;
}
int non_default_count = 0; int non_default_count = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
if (!is_default_wifi_ssid(entries[i].ssid)) { if (!is_default_wifi_ssid(entries[i].ssid))
non_default_count++; non_default_count++;
}
} }
return non_default_count; return non_default_count;
} }
static int find_oldest_non_default_wifi(char *ssid_out, size_t ssid_size) { static int connection_is_active(const char *name) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES]; FILE *fp;
int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES); char line[256];
if (count <= 0) { char conn_name[SSID_MAX_LENGTH];
return -1; char device[64];
char conn_type[64];
if (!name || !name[0])
return 0;
fp = popen("nmcli -t -f NAME,DEVICE,TYPE connection show", "r");
if (!fp)
return 0;
while (fgets(line, sizeof(line), fp)) {
trim_line(line);
if (parse_nmcli_connection_line(line, conn_name, sizeof(conn_name),
device, sizeof(device),
conn_type, sizeof(conn_type)) != 0)
continue;
if (strcmp(conn_name, name) != 0)
continue;
if (!is_wifi_connection_type(conn_type))
continue;
pclose(fp);
return device[0] != '\0';
} }
int found = 0; pclose(fp);
time_t oldest_mtime = 0; return 0;
}
for (int i = 0; i < count; i++) { /* wlan0 当前绑定的连接配置名,与 .nmconnection 里 id= 一致 */
if (is_default_wifi_ssid(entries[i].ssid)) { static void get_active_wifi_profile_name(char *name_out, size_t name_size) {
FILE *fp;
char line[256];
char *value;
if (!name_out || name_size == 0)
return;
name_out[0] = '\0';
fp = popen("nmcli -t -f GENERAL.CONNECTION dev show wlan0", "r");
if (!fp)
return;
while (fgets(line, sizeof(line), fp)) {
trim_line(line);
if (strncmp(line, "GENERAL.CONNECTION:", 19) != 0)
continue; continue;
} value = line + 19;
if (!found || entries[i].mtime < oldest_mtime) { if (!value[0] || strcmp(value, "--") == 0)
oldest_mtime = entries[i].mtime; break;
strncpy(ssid_out, entries[i].ssid, ssid_size - 1); strncpy(name_out, value, name_size - 1);
ssid_out[ssid_size - 1] = '\0'; name_out[name_size - 1] = '\0';
found = 1; break;
}
} }
return found ? 0 : -1; pclose(fp);
} }
static int enforce_saved_wifi_limit(const char *new_ssid) { static int wifi_profile_is_in_use(const char *ssid) {
if (is_default_wifi_ssid(new_ssid) || wifi_connection_exists(new_ssid)) { char active_profile[SSID_MAX_LENGTH];
if (!ssid || !ssid[0])
return 0; return 0;
get_active_wifi_profile_name(active_profile, sizeof(active_profile));
if (active_profile[0] && strcmp(ssid, active_profile) == 0)
return 1;
if (g_current_ssid[0] && strcmp(ssid, g_current_ssid) == 0)
return 1;
/* 仅当 DEVICE 非空才算在用,且兼容 name:type 两列输出 */
return connection_is_active(ssid) != 0;
}
static int remove_wifi_profile_files(const char *ssid) {
DIR *dir;
struct dirent *entry;
char filepath[512];
char profile_id[SSID_MAX_LENGTH];
int removed = 0;
size_t ssid_len;
if (!ssid || !ssid[0])
return -1;
ssid_len = strlen(ssid);
snprintf(filepath, sizeof(filepath), "%s%s.nmconnection", NM_CONN_DIR, ssid);
if (unlink(filepath) == 0)
removed = 1;
dir = opendir(NM_CONN_DIR);
if (!dir)
return removed ? 0 : -1;
while ((entry = readdir(dir)) != NULL) {
if (!strstr(entry->d_name, ".nmconnection"))
continue;
snprintf(filepath, sizeof(filepath), "%s%s", NM_CONN_DIR, entry->d_name);
if (strncmp(entry->d_name, ssid, ssid_len) == 0 &&
(entry->d_name[ssid_len] == '\0' ||
entry->d_name[ssid_len] == '-' ||
entry->d_name[ssid_len] == '.')) {
if (unlink(filepath) == 0)
removed = 1;
continue;
}
if (read_profile_id(filepath, profile_id, sizeof(profile_id)) != 0)
continue;
if (strcmp(profile_id, ssid) != 0)
continue;
if (unlink(filepath) == 0)
removed = 1;
} }
int count = count_non_default_saved_wifi(); closedir(dir);
if (count < 0) { return removed ? 0 : -1;
}
/* 同名连接在 nmcli 里可能有多条,循环删到干净 */
static int delete_nmcli_connection_by_name(const char *ssid) {
char command[280];
int deleted = 0;
if (!ssid || !ssid[0])
return -1;
for (int i = 0; i < 8; i++) {
snprintf(command, sizeof(command),
"nmcli connection delete '%s' 2>/dev/null", ssid);
if (system(command) != 0)
break;
deleted = 1;
}
return deleted ? 0 : -1;
}
/* 与 MQTT 2009 共用:删配置文件 + nmcli 连接(仅未在用的) */
static int device_wifi_delete_saved_profile(const char *ssid) {
int file_rc;
int nmcli_rc;
if (!ssid || !ssid[0])
return -1; return -1;
if (is_default_wifi_ssid(ssid)) {
my_zlog_warn("默认WiFi %s 不允许删除", default_SSID);
return -2;
}
if (wifi_profile_is_in_use(ssid)) {
my_zlog_warn("WiFi %s 正在使用,跳过删除", ssid);
return -3;
}
file_rc = remove_wifi_profile_files(ssid);
nmcli_rc = delete_nmcli_connection_by_name(ssid);
if (file_rc == 0 || nmcli_rc == 0)
return 0;
my_zlog_error("删除WiFi失败(文件rc=%d, nmcli rc=%d): %s", file_rc, nmcli_rc, ssid);
return -1;
}
/* 从最旧往新找:跳过默认 WiFi 与正在使用的配置 */
static int find_oldest_deletable_wifi(char *ssid_out, size_t ssid_size) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
int best = -1;
if (count <= 0)
return -1;
for (int i = 0; i < count; i++) {
if (is_default_wifi_ssid(entries[i].ssid))
continue;
if (wifi_profile_is_in_use(entries[i].ssid))
continue;
if (best < 0 || entries[i].mtime < entries[best].mtime)
best = i;
} }
while (count >= MAX_SAVED_WIFI_SLOTS) { if (best < 0)
char oldest_ssid[SSID_MAX_LENGTH]; return -1;
if (find_oldest_non_default_wifi(oldest_ssid, sizeof(oldest_ssid)) != 0) {
my_zlog_error("非默认WiFi已达上限,但未找到可删除的最旧配置"); strncpy(ssid_out, entries[best].ssid, ssid_size - 1);
ssid_out[ssid_size - 1] = '\0';
return 0;
}
static int enforce_saved_wifi_limit(const char *new_ssid) {
char oldest_ssid[SSID_MAX_LENGTH];
int count;
if (is_default_wifi_ssid(new_ssid) || wifi_connection_exists(new_ssid))
return 0;
for (int guard = 0; guard < MAX_WIFI_ENTRIES; guard++) {
int rc;
get_current_wifi();
count = count_non_default_saved_wifi();
if (count < 0)
return -1;
if (count < MAX_SAVED_WIFI_SLOTS)
return 0;
if (find_oldest_deletable_wifi(oldest_ssid, sizeof(oldest_ssid)) != 0) {
my_zlog_error("非默认WiFi已满%d个,且无未在用的配置可删(当前非默认%d个)",
MAX_SAVED_WIFI_SLOTS, count);
return -1; return -1;
} }
if (delete_wifi_by_ssid(oldest_ssid) != 0) { rc = device_wifi_delete_saved_profile(oldest_ssid);
my_zlog_error("删除最旧WiFi失败: %s", oldest_ssid); if (rc == -3) {
my_zlog_warn("最旧WiFi %s 正在使用,尝试删除下一旧配置", oldest_ssid);
continue;
}
if (rc != 0) {
my_zlog_error("删除最旧未使用WiFi失败: %s (rc=%d)", oldest_ssid, rc);
return -1; return -1;
} }
my_zlog_info("非默认WiFi已达%d个上限,删除最旧配置: %s", MAX_SAVED_WIFI_SLOTS, oldest_ssid); my_zlog_info("非默认WiFi已满%d个,删除最旧未使用配置: %s",
count--; MAX_SAVED_WIFI_SLOTS, oldest_ssid);
} }
return 0; my_zlog_error("非默认WiFi自动清理超过最大轮次");
return -1;
} }
static int update_wifi_password(const char *ssid, const char *password) { static void chmod_wifi_profile_files(const char *ssid) {
char cmd[256]; char path[512];
snprintf(cmd, sizeof(cmd), DIR *dir;
"nmcli connection modify \"%s\" wifi-sec.key-mgmt wpa-psk wifi-sec.psk \"%s\"", struct dirent *entry;
ssid, password); char profile_id[SSID_MAX_LENGTH];
if (system(cmd) != 0) { snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, ssid);
my_zlog_error("更新WiFi密码失败: %s", ssid); chmod(path, 0600);
dir = opendir(NM_CONN_DIR);
if (!dir)
return;
while ((entry = readdir(dir)) != NULL) {
if (!strstr(entry->d_name, ".nmconnection"))
continue;
snprintf(path, sizeof(path), "%s%s", NM_CONN_DIR, entry->d_name);
if (read_profile_id(path, profile_id, sizeof(profile_id)) != 0)
continue;
if (strcmp(profile_id, ssid) == 0)
chmod(path, 0600);
}
closedir(dir);
}
/* 与老代码一致:用 nmcli 保存,避免直接写文件触发 NM 重载 wlan0 */
static int save_wifi_via_nmcli(const char *ssid, const char *password) {
char command[512];
int ret;
if (!wifi_connection_exists(ssid)) {
snprintf(command, sizeof(command),
"nmcli connection add type wifi con-name '%s' ifname wlan0 ssid '%s'",
ssid, ssid);
ret = system(command);
if (ret != 0) {
my_zlog_error("nmcli add 失败: %s (ret=%d)", ssid, ret);
return -1;
}
}
snprintf(command, sizeof(command),
"nmcli connection modify '%s' connection.autoconnect no "
"wifi-sec.key-mgmt wpa-psk wifi-sec.psk '%s'",
ssid, password);
ret = system(command);
if (ret != 0) {
my_zlog_error("nmcli modify 失败: %s (ret=%d)", ssid, ret);
return -1; return -1;
} }
my_zlog_info("已更新WiFi密码: %s", ssid); chmod_wifi_profile_files(ssid);
return 0;
}
static int validate_wifi_credentials(const char *ssid, const char *password) {
if (!ssid || !password || strlen(ssid) == 0 || strlen(password) == 0) {
my_zlog_error("SSID或密码为空");
return -1;
}
if (strlen(ssid) >= SSID_MAX_LENGTH) {
my_zlog_error("SSID过长");
return -1;
}
if (strlen(password) > WIFI_PSK_MAX_LENGTH) {
my_zlog_error("WiFi密码过长");
return -1;
}
if (strpbrk(ssid, "\"`$|;\\") || strpbrk(password, "\"`$|;\\")) {
my_zlog_error("WiFi凭据含非法字符");
return -1;
}
return 0; return 0;
} }
/*成功发送3010:0保存成功,1删除成功,2保存失败,3默认WiFi不可删,4删除失败*/ /*成功发送3010:0保存成功,1删除成功,2保存失败,3默认WiFi不可删,4删除失败*/
void device_wifi_public_sendmqtt(int wifi_status) { void device_wifi_public_sendmqtt(int wifi_status) {
get_current_wifi();
cJSON *root = cJSON_CreateObject(); cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject(); cJSON *body = cJSON_CreateObject();
cJSON *head = cJSON_CreateObject(); cJSON *head = cJSON_CreateObject();
...@@ -210,90 +694,126 @@ void device_wifi_public_sendmqtt(int wifi_status) { ...@@ -210,90 +694,126 @@ void device_wifi_public_sendmqtt(int wifi_status) {
cJSON_Delete(root); cJSON_Delete(root);
} }
void device_wifi_rec_delete(cJSON *body) { int get_saved_wifi_list(char ssid_list[][SSID_MAX_LENGTH], int max_count) {
cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid"); saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
if (!cJSON_IsString(wifi_ssid)) { int limit = max_count < MAX_WIFI_ENTRIES ? max_count : MAX_WIFI_ENTRIES;
return; int count = collect_saved_wifi_entries(entries, limit);
} if (count < 0)
return -1;
char *ssid = wifi_ssid->valuestring; for (int i = 0; i < count; i++) {
if (is_default_wifi_ssid(ssid)) { strncpy(ssid_list[i], entries[i].ssid, SSID_MAX_LENGTH - 1);
my_zlog_warn("默认WiFi %s 不允许删除", default_SSID); ssid_list[i][SSID_MAX_LENGTH - 1] = '\0';
device_wifi_public_sendmqtt(3);
return;
} }
if (delete_wifi_by_ssid(ssid) == 0) { return count;
my_zlog_info("删除成功");
device_wifi_public_sendmqtt(1);
} else {
my_zlog_info("删除失败");
device_wifi_public_sendmqtt(4);
}
} }
int orange_pi_save_wifi(const char *ssid, const char *password) { int orange_pi_save_wifi(const char *ssid, const char *password) {
if (!ssid || !password || strlen(ssid) == 0 || strlen(password) == 0) { if (validate_wifi_credentials(ssid, password) != 0)
fprintf(stderr, "[ERROR] SSID或密码为空\n"); return -1;
if (!wifi_connection_exists(ssid) && enforce_saved_wifi_limit(ssid) != 0)
return -1; return -1;
}
if (strpbrk(ssid, "\"`$|") || strpbrk(password, "\"`$|")) { if (save_wifi_via_nmcli(ssid, password) != 0) {
fprintf(stderr, "[ERROR] 检测到非法字符\n"); my_zlog_error("[ERROR] 保存配置失败: %s", ssid);
return -1; return -1;
} }
if (wifi_connection_exists(ssid)) { my_zlog_info("[SUCCESS] 已保存WiFi配置: %s", ssid);
return update_wifi_password(ssid, password); return 0;
}
static int ensure_wifi_pool(void) {
pthread_mutex_lock(&s_wifi_pool_mutex);
if (!s_wifi_pool)
s_wifi_pool = thread_pool_init(1, 1);
pthread_mutex_unlock(&s_wifi_pool_mutex);
return s_wifi_pool ? 0 : -1;
}
static void wifi_save_worker(void *arg) {
wifi_save_task_t *task = arg;
int rc;
if (!task)
return;
rc = orange_pi_save_wifi(task->ssid, task->password);
device_wifi_public_sendmqtt(rc == 0 ? 0 : 2);
free(task);
}
static void wifi_delete_worker(void *arg) {
wifi_delete_task_t *task = arg;
int rc;
if (!task)
return;
rc = device_wifi_delete_saved_profile(task->ssid);
if (rc == 0) {
my_zlog_info("删除成功");
device_wifi_public_sendmqtt(1);
} else if (rc == -2) {
device_wifi_public_sendmqtt(3);
} else {
my_zlog_info("删除失败");
device_wifi_public_sendmqtt(4);
} }
free(task);
}
if (enforce_saved_wifi_limit(ssid) != 0) { static int submit_wifi_task(void (*worker)(void *), void *arg, const char *what) {
if (ensure_wifi_pool() != 0 || !s_wifi_pool) {
my_zlog_error("%s: wifi thread pool init failed", what);
free(arg);
return -1; return -1;
} }
if (thread_pool_add_task(s_wifi_pool, worker, arg) != 0) {
char cmd[512]; my_zlog_error("%s: thread_pool_add_task failed", what);
snprintf(cmd, sizeof(cmd), free(arg);
"nmcli connection add type wifi con-name \"%s\" ifname wlan0 ssid \"%s\" wifi-sec.key-mgmt wpa-psk wifi-sec.psk \"%s\"",
ssid, ssid, password);
if (system(cmd) != 0) {
my_zlog_error("[ERROR] 保存配置失败: %s", ssid);
return -1; return -1;
} }
my_zlog_info("[SUCCESS] 已保存WiFi配置: %s", ssid);
return 0; return 0;
} }
void device_wifi_rec_sava(cJSON *body) { void device_wifi_rec_sava(cJSON *body) {
cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid"); cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid");
cJSON *wifi_password = cJSON_GetObjectItem(body, "wifi_password"); cJSON *wifi_password = cJSON_GetObjectItem(body, "wifi_password");
if (cJSON_IsString(wifi_ssid) && cJSON_IsString(wifi_password)) { wifi_save_task_t *task;
char *ssid = wifi_ssid->valuestring;
char *password = wifi_password->valuestring; if (!cJSON_IsString(wifi_ssid) || !cJSON_IsString(wifi_password))
my_zlog_debug("接收到保存WiFi名称和密码"); return;
if (orange_pi_save_wifi(ssid, password) == 0) {
device_wifi_public_sendmqtt(0); task = calloc(1, sizeof(*task));
} else { if (!task) {
device_wifi_public_sendmqtt(2); device_wifi_public_sendmqtt(2);
} return;
} }
strncpy(task->ssid, wifi_ssid->valuestring, sizeof(task->ssid) - 1);
strncpy(task->password, wifi_password->valuestring, sizeof(task->password) - 1);
my_zlog_debug("接收到保存WiFi,后台 nmcli 保存");
if (submit_wifi_task(wifi_save_worker, task, "save wifi") != 0)
device_wifi_public_sendmqtt(2);
} }
int get_saved_wifi_list(char ssid_list[][SSID_MAX_LENGTH], int max_count) { void device_wifi_rec_delete(cJSON *body) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES]; cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid");
int limit = max_count < MAX_WIFI_ENTRIES ? max_count : MAX_WIFI_ENTRIES; wifi_delete_task_t *task;
int count = collect_saved_wifi_entries(entries, limit);
if (count < 0) {
return -1;
}
for (int i = 0; i < count; i++) { if (!cJSON_IsString(wifi_ssid))
strncpy(ssid_list[i], entries[i].ssid, SSID_MAX_LENGTH - 1); return;
ssid_list[i][SSID_MAX_LENGTH - 1] = '\0';
}
return count; task = calloc(1, sizeof(*task));
if (!task) {
device_wifi_public_sendmqtt(4);
return;
}
strncpy(task->ssid, wifi_ssid->valuestring, sizeof(task->ssid) - 1);
my_zlog_debug("接收到删除WiFi,后台删配置文件: %s", task->ssid);
if (submit_wifi_task(wifi_delete_worker, task, "delete wifi") != 0)
device_wifi_public_sendmqtt(4);
} }
void device_send_saved_wifi() { void device_send_saved_wifi() {
......
...@@ -211,7 +211,11 @@ void *thread_open_browser(void *arg) ...@@ -211,7 +211,11 @@ void *thread_open_browser(void *arg)
while (1) { while (1) {
if (webrtcpush_should_use_mpp()) { if (webrtcpush_should_use_mpp()) {
my_zlog_info("WEBRTCPUSH: libdatachannel native push mode"); my_zlog_info("WEBRTCPUSH: libdatachannel native push mode");
(void)system("pkill chromium"); {
int pkill_rc = system("pkill chromium");
if (pkill_rc == -1)
my_zlog_warn("pkill chromium: system() failed");
}
delay_s(2); delay_s(2);
while (s_webrtc_index == 1 && webrtcpush_should_use_mpp()) { while (s_webrtc_index == 1 && webrtcpush_should_use_mpp()) {
if (webrtcpush_is_running()) { if (webrtcpush_is_running()) {
......
No preview for this file type
...@@ -303,7 +303,7 @@ int hardware_iic_config_init(){ ...@@ -303,7 +303,7 @@ int hardware_iic_config_init(){
}else if(res ==0){ }else if(res ==0){
delay_ms(200); delay_ms(200);
int ret=system("sudo reboot");//重启香橙派 int ret=system("sudo reboot");//重启香橙派
if(ret==0) my_zlog_debug("重启成功"); if(ret==0) my_zlog_info("重启成功");
return 1; return 1;
} }
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <stdio.h> #include <stdio.h>
#include <strings.h> #include <strings.h>
#include <unistd.h> #include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0" #define AUDIO_USB_ALSA_DEVICE "hw:2,0"
...@@ -260,6 +261,31 @@ void audioplay_cycle(){ ...@@ -260,6 +261,31 @@ void audioplay_cycle(){
} }
} }
static void trim_line(char *s)
{
if (!s)
return;
s[strcspn(s, "\r\n")] = '\0';
}
static int pulse_pactl_exit_code(int status)
{
if (status == -1)
return -1;
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
static int pulse_output_means_already_loaded(const char *output)
{
if (!output || !output[0])
return 0;
return strstr(output, "already loaded") != NULL ||
strstr(output, "Already loaded") != NULL ||
strstr(output, "Module exists") != NULL;
}
static int pulse_module_exists(const char *module_name, const char *device) static int pulse_module_exists(const char *module_name, const char *device)
{ {
FILE *fp = popen("pactl list modules short 2>/dev/null", "r"); FILE *fp = popen("pactl list modules short 2>/dev/null", "r");
...@@ -283,23 +309,58 @@ static int pulse_module_exists(const char *module_name, const char *device) ...@@ -283,23 +309,58 @@ static int pulse_module_exists(const char *module_name, const char *device)
static int pulse_load_alsa_module(const char *module_name, const char *label) static int pulse_load_alsa_module(const char *module_name, const char *label)
{ {
char cmd[192]; char cmd[256];
char output[512];
char line[256];
if (pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) { if (pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_debug("%s 已注册 (%s %s),跳过", label, module_name, AUDIO_USB_ALSA_DEVICE); my_zlog_info("%s 已注册,跳过 (%s device=%s)",
label, module_name, AUDIO_USB_ALSA_DEVICE);
return 0; return 0;
} }
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd), "pactl load-module %s device=%s 2>&1",
"pactl load-module %s device=%s >/dev/null 2>&1",
module_name, AUDIO_USB_ALSA_DEVICE); module_name, AUDIO_USB_ALSA_DEVICE);
int rc = system(cmd);
if (rc == 0) { FILE *fp = popen(cmd, "r");
my_zlog_debug("%s 注册成功", label); if (!fp) {
my_zlog_warn("%s 注册失败: 无法执行 pactl (%s)", label, strerror(errno));
return -1;
}
output[0] = '\0';
while (fgets(line, sizeof(line), fp) != NULL) {
trim_line(line);
if (!line[0])
continue;
if (output[0]) {
strncat(output, "; ", sizeof(output) - strlen(output) - 1);
}
strncat(output, line, sizeof(output) - strlen(output) - 1);
}
int status = pclose(fp);
int exit_code = pulse_pactl_exit_code(status);
if (exit_code == 0) {
my_zlog_info("%s 注册成功 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ", pactl: " : "", output[0] ? output : "");
return 0;
}
if (pulse_output_means_already_loaded(output) ||
pulse_module_exists(module_name, AUDIO_USB_ALSA_DEVICE)) {
my_zlog_info("%s 已注册,跳过 (%s device=%s)%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE,
output[0] ? ": " : "", output[0] ? output : "");
return 0; return 0;
} }
my_zlog_warn("%s 注册失败,返回状态码: %d", label, rc); my_zlog_warn("%s 注册失败 (%s device=%s): exit=%d%s%s",
label, module_name, AUDIO_USB_ALSA_DEVICE, exit_code,
output[0] ? ", pactl: " : ", pactl 无输出",
output[0] ? output : "");
return -1; return -1;
} }
......
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBRTCPUSH_GST REQUIRED pkg_check_modules(WEBRTCPUSH_GST REQUIRED
gstreamer-1.0>=1.18 gstreamer-1.0>=1.18
gstreamer-app-1.0
gstreamer-webrtc-1.0 gstreamer-webrtc-1.0
gstreamer-sdp-1.0 gstreamer-sdp-1.0
gstreamer-video-1.0 gstreamer-video-1.0
......
...@@ -429,9 +429,11 @@ int device_message_receive(cJSON *json) ...@@ -429,9 +429,11 @@ int device_message_receive(cJSON *json)
my_zlog_debug("删除已保存wifi"); my_zlog_debug("删除已保存wifi");
break; break;
case 2011: case 2011:
int res = system("sudo reboot"); // 重启香橙派 {
if (res == 0) int res = system("sudo reboot");
my_zlog_info("重启成功"); if (res == 0)
my_zlog_info("重启成功");
}
break; break;
case 2012: case 2012:
refresh_cam(); refresh_cam();
......
...@@ -9,6 +9,7 @@ mqttclient g_clients_t[MAX_SERVERS]; ...@@ -9,6 +9,7 @@ mqttclient g_clients_t[MAX_SERVERS];
static char s_uuid_mqtt_topic_id[MAX_SERVERS][56]; static char s_uuid_mqtt_topic_id[MAX_SERVERS][56];
static bool s_mosquitto_lib_inited = false; static bool s_mosquitto_lib_inited = false;
static pthread_mutex_t s_mqtt_publish_mutex = PTHREAD_MUTEX_INITIALIZER;
// struct mosquitto *mosq; // struct mosquitto *mosq;
// 新增:用于向其他MQTT服务器发送通知的函数 // 新增:用于向其他MQTT服务器发送通知的函数
...@@ -47,6 +48,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos) ...@@ -47,6 +48,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos)
return 0; return 0;
} }
pthread_mutex_lock(&s_mqtt_publish_mutex);
for (int i = 0; i < total_count; i++) { for (int i = 0; i < total_count; i++) {
if (!g_clients_t[i].mosq || g_clients_t[i].permanently_failed) { if (!g_clients_t[i].mosq || g_clients_t[i].permanently_failed) {
continue; continue;
...@@ -55,6 +57,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos) ...@@ -55,6 +57,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos)
sent_count++; sent_count++;
} }
} }
pthread_mutex_unlock(&s_mqtt_publish_mutex);
return sent_count; return sent_count;
} }
......
...@@ -345,14 +345,9 @@ static int ensure_webrtc_stack(void) ...@@ -345,14 +345,9 @@ static int ensure_webrtc_stack(void)
static int ensure_libdatachannel_stack(void) static int ensure_libdatachannel_stack(void)
{ {
/* libdatachannel and its ICE/SRTP dependencies are linked into main. */ if (ensure_webrtc_stack() != 0)
if (access(WEBRTCPUSH_H264_FILE, R_OK) != 0) {
my_zlog_error("runtime_deps: H264 test file is not readable: %s",
WEBRTCPUSH_H264_FILE);
return -1; return -1;
} my_zlog_info("runtime_deps: libdatachannel native sender ready (MPP live encode)");
my_zlog_info("runtime_deps: libdatachannel native sender ready, H264=%s",
WEBRTCPUSH_H264_FILE);
return 0; return 0;
} }
......
#include "mpp_h264_source.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include <gst/app/gstappsink.h>
#include <gst/video/video-event.h>
#include <string.h>
#define MPP_VIDEO_WIDTH 1280
#define MPP_VIDEO_HEIGHT 720
#define MPP_VIDEO_GOP WEBRTCPUSH_H264_GOP
#define MPP_VIDEO_START_BPS 1400000u
#define MPP_VIDEO_MIN_BPS WEBRTCPUSH_MIN_BITRATE
#define MPP_VIDEO_MAX_BPS WEBRTCPUSH_MAX_BITRATE
#define WRTC_V4L2_CAP_VIDEO_CAPTURE (1u << 0)
#define WRTC_V4L2_CAP_VIDEO_CAPTURE_MPLANE (1u << 12)
struct MppH264Source {
GstElement *pipeline;
GstElement *enc;
GstElement *parse;
GstElement *appsink;
gboolean use_mpp;
guint8 *frame_buf;
size_t frame_size;
size_t frame_capacity;
guint keyframe_req;
};
static gchar *pick_v4l2_capture_device(void)
{
for (int i = 0; i < 32; i++) {
gchar *path = g_strdup_printf("/dev/video%d", i);
if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
g_free(path);
continue;
}
gchar *sys_cap = g_strdup_printf("/sys/class/video4linux/video%d/device_caps", i);
gboolean use = FALSE;
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);
use = (caps & WRTC_V4L2_CAP_VIDEO_CAPTURE) != 0 ||
(caps & WRTC_V4L2_CAP_VIDEO_CAPTURE_MPLANE) != 0;
g_free(contents);
}
} else {
use = TRUE;
}
g_free(sys_cap);
if (use)
return path;
g_free(path);
}
return NULL;
}
static void configure_leaky_queue(GstElement *q)
{
if (!q)
return;
g_object_set(q,
"max-size-buffers", 1,
"max-size-bytes", 0,
"max-size-time", (guint64)0,
"leaky", 2,
NULL);
}
static void configure_v4l2_low_latency(GstElement *src)
{
if (!src)
return;
g_object_set(src, "do-timestamp", TRUE, NULL);
}
static GstElement *make_h264_encoder(gboolean prefer_mpp, gboolean use_test,
gboolean *use_mpp_out)
{
GstElement *enc = NULL;
if (prefer_mpp && !use_test) {
enc = gst_element_factory_make("mpph264enc", "enc");
if (enc) {
*use_mpp_out = TRUE;
g_object_set(enc,
"gop", MPP_VIDEO_GOP,
"rc-mode", 1,
"bps", MPP_VIDEO_START_BPS,
"bps-min", (guint)(MPP_VIDEO_START_BPS * 0.80),
"bps-max", (guint)(MPP_VIDEO_START_BPS * 1.08),
"header-mode", 1,
"profile", 66,
"level", 31,
"qos", TRUE,
"zero-copy-pkt", TRUE,
NULL);
my_zlog_info("mpp_h264_source: mpph264enc %dx%d@%dfps GOP=%d",
MPP_VIDEO_WIDTH, MPP_VIDEO_HEIGHT, WEBRTCPUSH_H264_FPS,
MPP_VIDEO_GOP);
return enc;
}
my_zlog_warn("mpp_h264_source: mpph264enc unavailable");
}
enc = gst_element_factory_make("x264enc", "enc");
if (enc) {
g_object_set(enc,
"tune", "zerolatency",
"speed-preset", "ultrafast",
"bitrate", MPP_VIDEO_START_BPS / 1000,
"key-int-max", MPP_VIDEO_GOP,
"byte-stream", TRUE,
"profile", "baseline",
NULL);
my_zlog_info("mpp_h264_source: x264enc software fallback");
return enc;
}
return NULL;
}
static gboolean buffer_is_idr(const guint8 *data, size_t size)
{
size_t i = 0;
while (i + 3 < size) {
size_t sc = 0;
if (i + 4 <= size && data[i] == 0 && data[i + 1] == 0 &&
data[i + 2] == 0 && data[i + 3] == 1)
sc = 4;
else if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
sc = 3;
else {
i++;
continue;
}
i += sc;
if (i >= size)
break;
if ((data[i] & 0x1f) == 5)
return TRUE;
}
return FALSE;
}
static gboolean send_force_key_unit_on_pad(GstPad *pad)
{
GstEvent *event;
if (!pad)
return FALSE;
event = gst_video_event_new_upstream_force_key_unit(
GST_CLOCK_TIME_NONE, TRUE, 0);
return gst_pad_send_event(pad, event);
}
static void request_keyframe(MppH264Source *src)
{
gboolean ok = FALSE;
if (!src)
return;
src->keyframe_req++;
/* Upstream force-key-unit 必须交给编码器的 src pad 处理。 */
if (src->enc) {
GstPad *pad = gst_element_get_static_pad(src->enc, "src");
ok = send_force_key_unit_on_pad(pad);
if (pad)
gst_object_unref(pad);
}
if (ok)
my_zlog_info("mpp_h264_source: requested IDR (%u)", src->keyframe_req);
else
my_zlog_warn("mpp_h264_source: IDR request not handled");
}
void mpp_h264_source_request_keyframe(MppH264Source *src)
{
request_keyframe(src);
}
static gboolean copy_frame(MppH264Source *src, GstBuffer *buffer)
{
GstMapInfo map;
if (!gst_buffer_map(buffer, &map, GST_MAP_READ))
return FALSE;
if (map.size > src->frame_capacity) {
src->frame_buf = g_realloc(src->frame_buf, map.size);
src->frame_capacity = map.size;
}
memcpy(src->frame_buf, map.data, map.size);
src->frame_size = map.size;
gst_buffer_unmap(buffer, &map);
return TRUE;
}
MppH264Source *mpp_h264_source_start(const char *video_device, char **error_message)
{
MppH264Source *src;
GstElement *pipe, *vsrc, *caps_jpeg = NULL, *jpegparse = NULL, *jpegdec = NULL;
GstElement *vrate, *conv, *caps1, *enc, *parse, *q, *caps_h264, *sink;
gchar *auto_v4l = NULL;
const gchar *vdev = video_device;
gboolean use_test = FALSE;
gboolean use_auto = FALSE;
gboolean use_mjpg = FALSE;
gboolean use_mpp = FALSE;
gboolean use_mpp_jpeg = FALSE;
GstStateChangeReturn ret;
if (!gst_is_initialized())
gst_init(NULL, NULL);
src = g_new0(MppH264Source, 1);
use_test = (vdev && g_strcmp0(vdev, "test") == 0);
use_auto = (!vdev || g_strcmp0(vdev, "auto") == 0);
if (use_auto && !use_test) {
auto_v4l = pick_v4l2_capture_device();
if (auto_v4l) {
my_zlog_info("mpp_h264_source: auto V4L2 %s", auto_v4l);
vdev = auto_v4l;
} else {
my_zlog_warn("mpp_h264_source: no V4L2 device, videotestsrc");
use_test = TRUE;
}
}
pipe = gst_pipeline_new("mpp-h264-pipe");
if (use_test) {
vsrc = gst_element_factory_make("videotestsrc", "vsrc");
g_object_set(vsrc, "is-live", TRUE, NULL);
} else {
vsrc = gst_element_factory_make("v4l2src", "vsrc");
g_object_set(vsrc, "device", vdev, NULL);
configure_v4l2_low_latency(vsrc);
caps_jpeg = gst_element_factory_make("capsfilter", "caps_jpeg");
jpegparse = gst_element_factory_make("jpegparse", "jpegparse");
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("jpegdec", "jpegdec");
}
if (caps_jpeg && jpegparse && jpegdec) {
gchar *jc_str = g_strdup_printf(
"image/jpeg,width=%d,height=%d,framerate=30/1",
MPP_VIDEO_WIDTH, MPP_VIDEO_HEIGHT);
GstCaps *jc = gst_caps_from_string(jc_str);
g_object_set(caps_jpeg, "caps", jc, NULL);
gst_caps_unref(jc);
g_free(jc_str);
use_mjpg = TRUE;
my_zlog_info("mpp_h264_source: JPEG decoder=%s",
use_mpp_jpeg ? "mppjpegdec" : "jpegdec software fallback");
}
}
conv = gst_element_factory_make("videoconvert", "conv");
vrate = gst_element_factory_make("videorate", "vrate");
caps1 = gst_element_factory_make("capsfilter", "caps1");
q = gst_element_factory_make("queue", "vq");
enc = make_h264_encoder(TRUE, use_test, &use_mpp);
parse = gst_element_factory_make("h264parse", "parse");
caps_h264 = gst_element_factory_make("capsfilter", "caps_h264");
sink = gst_element_factory_make("appsink", "sink");
if (!pipe || !vsrc || !conv || !vrate || !caps1 || !q || !enc || !parse ||
!caps_h264 || !sink || (!use_test && !use_mjpg)) {
g_set_error_literal(NULL, 0, 0, "GStreamer element create failed");
if (error_message)
*error_message = g_strdup("GStreamer element create failed");
g_free(auto_v4l);
g_free(src);
return NULL;
}
g_object_set(vrate, "max-rate", WEBRTCPUSH_H264_FPS, "drop-only", TRUE, NULL);
configure_leaky_queue(q);
g_object_set(parse, "config-interval", -1, NULL);
{
gchar *cap_str = g_strdup_printf(
"video/x-raw,format=%s,width=%d,height=%d,framerate=%d/1",
use_mpp ? "NV12" : "I420",
use_test ? 640 : MPP_VIDEO_WIDTH,
use_test ? 480 : MPP_VIDEO_HEIGHT,
WEBRTCPUSH_H264_FPS);
GstCaps *caps = gst_caps_from_string(cap_str);
g_object_set(caps1, "caps", caps, NULL);
gst_caps_unref(caps);
g_free(cap_str);
}
{
GstCaps *hc = gst_caps_from_string(
"video/x-h264,stream-format=byte-stream,alignment=au");
g_object_set(caps_h264, "caps", hc, NULL);
gst_caps_unref(hc);
}
g_object_set(sink,
"emit-signals", FALSE,
"sync", FALSE,
"max-buffers", 1,
"drop", TRUE,
NULL);
if (use_mjpg) {
gst_bin_add_many(GST_BIN(pipe), vsrc, caps_jpeg, jpegparse, jpegdec, conv, vrate,
caps1, q, enc, parse, caps_h264, sink, NULL);
if (!gst_element_link_many(vsrc, caps_jpeg, jpegparse, jpegdec, conv, vrate,
caps1, q, enc, parse, caps_h264, sink, NULL)) {
if (error_message)
*error_message = g_strdup("failed to link MJPG capture chain");
gst_object_unref(pipe);
g_free(auto_v4l);
g_free(src);
return NULL;
}
} else {
gst_bin_add_many(GST_BIN(pipe), vsrc, conv, vrate, caps1, q, enc, parse,
caps_h264, sink, NULL);
if (!gst_element_link_many(vsrc, conv, vrate, caps1, q, enc, parse,
caps_h264, sink, NULL)) {
if (error_message)
*error_message = g_strdup("failed to link video chain");
gst_object_unref(pipe);
g_free(auto_v4l);
g_free(src);
return NULL;
}
}
src->pipeline = pipe;
src->enc = enc;
src->parse = parse;
src->appsink = sink;
src->use_mpp = use_mpp;
gst_pipeline_set_latency(GST_PIPELINE(pipe), 0);
ret = gst_element_set_state(pipe, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
if (error_message)
*error_message = g_strdup("pipeline failed to reach PLAYING");
mpp_h264_source_stop(src);
g_free(auto_v4l);
return NULL;
}
my_zlog_info("mpp_h264_source: live encoder started (mpp=%d)", use_mpp ? 1 : 0);
g_free(auto_v4l);
return src;
}
static gboolean pull_sample(MppH264Source *src, gboolean require_idr,
gint timeout_ms, const uint8_t **data,
size_t *size, gboolean *is_idr,
guint64 *pts_ns)
{
GstAppSink *appsink;
GstSample *sample;
GstBuffer *buffer;
gboolean idr;
GstClockTime pts;
gint64 deadline;
gint attempts = 0;
if (!src || !src->appsink || !data || !size || timeout_ms < 1)
return FALSE;
appsink = GST_APP_SINK(src->appsink);
deadline = g_get_monotonic_time() + (gint64)timeout_ms * 1000;
while (attempts < 40) {
gint wait_ms = (gint)((deadline - g_get_monotonic_time()) / 1000);
if (wait_ms < 1)
break;
sample = gst_app_sink_try_pull_sample(appsink, (guint64)wait_ms * GST_MSECOND);
if (!sample) {
if (g_get_monotonic_time() >= deadline)
break;
continue;
}
buffer = gst_sample_get_buffer(sample);
if (!buffer || !copy_frame(src, buffer)) {
gst_sample_unref(sample);
attempts++;
continue;
}
pts = GST_BUFFER_PTS(buffer);
idr = buffer_is_idr(src->frame_buf, src->frame_size);
gst_sample_unref(sample);
if (require_idr && !idr) {
attempts++;
continue;
}
*data = src->frame_buf;
*size = src->frame_size;
if (is_idr)
*is_idr = idr;
if (pts_ns)
*pts_ns = GST_CLOCK_TIME_IS_VALID(pts) ? (guint64)pts : G_MAXUINT64;
return TRUE;
}
return FALSE;
}
gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
const uint8_t **data, size_t *size,
gboolean *is_idr, guint64 *pts_ns,
gint timeout_ms)
{
gint idr_timeout;
if (!src || !src->appsink || !data || !size)
return FALSE;
if (timeout_ms < 1)
timeout_ms = 80;
if (force_idr) {
idr_timeout = timeout_ms < 2000 ? 2000 : timeout_ms;
request_keyframe(src);
if (pull_sample(src, TRUE, idr_timeout, data, size, is_idr, pts_ns))
return TRUE;
my_zlog_warn("mpp_h264_source: IDR wait timeout, fallback to latest frame");
return pull_sample(src, FALSE, 200, data, size, is_idr, pts_ns);
}
return pull_sample(src, FALSE, timeout_ms, data, size, is_idr, pts_ns);
}
void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps)
{
GstElementFactory *f;
const gchar *name;
if (!src || !src->enc || !bitrate_bps)
return;
if (bitrate_bps < MPP_VIDEO_MIN_BPS)
bitrate_bps = MPP_VIDEO_MIN_BPS;
if (bitrate_bps > MPP_VIDEO_MAX_BPS)
bitrate_bps = MPP_VIDEO_MAX_BPS;
f = gst_element_get_factory(src->enc);
name = f ? gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(f)) : NULL;
if (name && g_str_has_prefix(name, "mpph264enc")) {
guint bmin = (guint)(bitrate_bps * 0.80);
guint bmax = (guint)(bitrate_bps * 1.08);
if (bmin < MPP_VIDEO_MIN_BPS)
bmin = MPP_VIDEO_MIN_BPS;
if (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);
} else if (name && g_str_has_prefix(name, "x264enc")) {
g_object_set(src->enc, "bitrate", bitrate_bps / 1000, NULL);
}
}
gboolean mpp_h264_source_is_mpp_encoder(const MppH264Source *src)
{
return src && src->use_mpp;
}
void mpp_h264_source_stop(MppH264Source *src)
{
if (!src)
return;
if (src->pipeline) {
gst_element_set_state(src->pipeline, GST_STATE_NULL);
gst_object_unref(src->pipeline);
src->pipeline = NULL;
}
src->enc = NULL;
src->parse = NULL;
src->appsink = NULL;
g_free(src->frame_buf);
g_free(src);
}
#ifndef MPP_H264_SOURCE_H
#define MPP_H264_SOURCE_H
#include <glib.h>
#include <stddef.h>
#include <stdint.h>
typedef struct MppH264Source MppH264Source;
/* video_device: "auto" | /dev/videoN | "test" (videotestsrc) */
MppH264Source *mpp_h264_source_start(const char *video_device, char **error_message);
void mpp_h264_source_stop(MppH264Source *src);
/* 拉一帧 Annex-B AU;数据在下次 pull 前有效。timeout_ms:appsink 等待毫秒。 */
gboolean mpp_h264_source_pull(MppH264Source *src, gboolean force_idr,
const uint8_t **data, size_t *size,
gboolean *is_idr, guint64 *pts_ns,
gint timeout_ms);
void mpp_h264_source_set_bitrate(MppH264Source *src, guint bitrate_bps);
void mpp_h264_source_request_keyframe(MppH264Source *src);
gboolean mpp_h264_source_is_mpp_encoder(const MppH264Source *src);
#endif
#include "rtc_client.h" #include "rtc_client.h"
#include "h264_reader.h" #include "mpp_h264_source.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,7 +27,7 @@ typedef struct { ...@@ -27,7 +27,7 @@ typedef struct {
GMutex lock; GMutex lock;
GMutex send_lock; GMutex send_lock;
GThread *sender_thread; GThread *sender_thread;
H264Reader *reader; MppH264Source *h264_source;
gboolean stopping; gboolean stopping;
gboolean track_open; gboolean track_open;
gboolean need_idr; gboolean need_idr;
...@@ -35,9 +35,13 @@ typedef struct { ...@@ -35,9 +35,13 @@ typedef struct {
int pc; int pc;
int track; int track;
guint32 rtp_timestamp; guint32 rtp_timestamp;
guint32 rtp_timestamp_base;
guint64 source_pts_base;
guint64 last_source_pts;
gboolean source_pts_valid;
guint target_bitrate; guint target_bitrate;
guint remb_ceiling; guint remb_ceiling;
gint64 last_bitrate_raise_us; gint64 last_bitrate_ramp_us;
gchar *remote_ice_ufrag; gchar *remote_ice_ufrag;
gchar *video_mid; gchar *video_mid;
gint video_mline_index; gint video_mline_index;
...@@ -244,6 +248,7 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr) ...@@ -244,6 +248,7 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr)
if (state == RTC_CONNECTED) { if (state == RTC_CONNECTED) {
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
client->need_idr = TRUE; client->need_idr = TRUE;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
} }
} }
...@@ -257,6 +262,7 @@ static void RTC_API on_track_open(int track, void *ptr) ...@@ -257,6 +262,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->need_idr = TRUE; client->need_idr = TRUE;
client->source_pts_valid = FALSE;
} }
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
my_zlog_info("libdatachannel: H264 track open, requesting SPS/PPS+IDR"); my_zlog_info("libdatachannel: H264 track open, requesting SPS/PPS+IDR");
...@@ -284,13 +290,19 @@ static void RTC_API on_track_error(int track, const char *error, void *ptr) ...@@ -284,13 +290,19 @@ 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;
(void)track; int dropped = 0;
if (!client) if (!client)
return; return;
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
client->need_idr = TRUE; client->need_idr = TRUE;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
my_zlog_info("libdatachannel: PLI received, next frame is SPS/PPS+IDR"); g_mutex_lock(&client->send_lock);
if (client->track == track)
dropped = rtcClearPacingQueue(track);
g_mutex_unlock(&client->send_lock);
my_zlog_info("libdatachannel: PLI received, dropped %d queued RTP packet(s), next is IDR",
dropped > 0 ? dropped : 0);
} }
static guint clamp_bitrate(guint bitrate) static guint clamp_bitrate(guint bitrate)
...@@ -302,56 +314,162 @@ static guint clamp_bitrate(guint bitrate) ...@@ -302,56 +314,162 @@ static guint clamp_bitrate(guint bitrate)
return bitrate; return bitrate;
} }
static guint pacing_bitrate_for_encoder(guint encoder_bitrate)
{
guint64 pacing = ((guint64)clamp_bitrate(encoder_bitrate) *
WEBRTCPUSH_PACING_HEADROOM_PERCENT) / 100;
if (pacing > WEBRTCPUSH_PACING_MAX_BITRATE)
pacing = WEBRTCPUSH_PACING_MAX_BITRATE;
return (guint)pacing;
}
static void apply_bitrate(RtcClient *client, int track, guint bitrate)
{
guint b;
if (!client)
return;
b = clamp_bitrate(bitrate);
g_mutex_lock(&client->lock);
client->target_bitrate = b;
g_mutex_unlock(&client->lock);
if (track >= 0) {
g_mutex_lock(&client->send_lock);
if (client->track == track)
rtcSetPacingBitrate(track, pacing_bitrate_for_encoder(b));
g_mutex_unlock(&client->send_lock);
}
mpp_h264_source_set_bitrate(client->h264_source, b);
}
/* 与 gst_webrtc_pipeline::ramp_video_bitrate 同档升降步长 */
static void ramp_bitrate_toward_ceiling(RtcClient *client, int track)
{
guint cur;
guint tgt;
guint next;
guint diff;
guint step;
if (!client || track < 0)
return;
g_mutex_lock(&client->lock);
cur = client->target_bitrate;
tgt = client->remb_ceiling;
g_mutex_unlock(&client->lock);
if (!cur || !tgt || cur == tgt)
return;
diff = (cur > tgt) ? (cur - tgt) : (tgt - cur);
if (cur < tgt) {
step = diff / 8;
if (step < 50000)
step = 50000;
if (step > 150000)
step = 150000;
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)
return;
apply_bitrate(client, track, next);
my_zlog_info("libdatachannel: bitrate ramp %u -> %u kbps (REMB ceiling %u kbps)",
cur / 1000, next / 1000, tgt / 1000);
}
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;
guint current; guint current;
guint next = 0;
guint ceiling; guint ceiling;
(void)track; guint next = 0;
gboolean severe_down = FALSE;
int dropped = 0;
if (!client || bitrate == 0) if (!client || bitrate == 0)
return; return;
ceiling = clamp_bitrate((guint)(((guint64)bitrate * 85) / 100));
ceiling = clamp_bitrate(
(guint)(((guint64)bitrate * WEBRTCPUSH_REMB_UTIL_PERCENT) / 100));
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
current = client->target_bitrate; current = client->target_bitrate;
client->remb_ceiling = ceiling; client->remb_ceiling = ceiling;
if (ceiling < current) { if (ceiling < current) {
client->target_bitrate = ceiling; client->target_bitrate = ceiling;
next = ceiling; next = ceiling;
severe_down = ((guint64)ceiling * 100) < ((guint64)current * 75);
if (severe_down) {
client->need_idr = TRUE;
client->source_pts_valid = FALSE;
}
} }
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
if (next) { if (next) {
rtcSetPacingBitrate(track, next); g_mutex_lock(&client->send_lock);
my_zlog_info("libdatachannel: REMB=%u, pacing immediately down to %u bps", if (client->track == track) {
bitrate, next); if (severe_down)
dropped = rtcClearPacingQueue(track);
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) {
my_zlog_info("libdatachannel: REMB=%u kbps, encoder=%u kbps pacing=%u kbps, resync dropped=%d RTP packets",
bitrate / 1000, next / 1000,
pacing_bitrate_for_encoder(next) / 1000,
dropped > 0 ? dropped : 0);
} else {
my_zlog_info("libdatachannel: REMB=%u kbps, encoder=%u kbps pacing=%u kbps",
bitrate / 1000, next / 1000,
pacing_bitrate_for_encoder(next) / 1000);
}
} else if (ceiling > current) {
my_zlog_debug("libdatachannel: REMB=%u kbps, ceiling %u kbps (ramping up)",
bitrate / 1000, ceiling / 1000);
} }
} }
static void maybe_raise_bitrate(RtcClient *client, int track, gint64 now_us) static void maybe_ramp_bitrate(RtcClient *client, int track, gint64 now_us)
{ {
guint next = 0; if (!client || track < 0)
return;
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
if (client->remb_ceiling > client->target_bitrate && if (now_us - client->last_bitrate_ramp_us <
now_us - client->last_bitrate_raise_us >= 2000000) { (gint64)WEBRTCPUSH_BITRATE_RAMP_MS * 1000) {
guint raised = (guint)(((guint64)client->target_bitrate * 112) / 100); g_mutex_unlock(&client->lock);
if (raised > client->remb_ceiling) return;
raised = client->remb_ceiling;
client->target_bitrate = clamp_bitrate(raised);
client->last_bitrate_raise_us = now_us;
next = client->target_bitrate;
} }
client->last_bitrate_ramp_us = now_us;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
if (next) { ramp_bitrate_toward_ceiling(client, track);
g_mutex_lock(&client->send_lock); }
if (client->track == track)
rtcSetPacingBitrate(track, next); static guint32 pts_delta_to_rtp(guint64 delta_ns)
g_mutex_unlock(&client->send_lock); {
my_zlog_info("libdatachannel: pacing slowly up to %u bps", next); const guint64 second = G_GUINT64_CONSTANT(1000000000);
} guint64 ticks = (delta_ns / second) * 90000;
ticks += ((delta_ns % second) * 90000) / second;
return (guint32)ticks;
} }
static gpointer sender_thread_main(gpointer data) static gpointer sender_thread_main(gpointer data)
...@@ -370,6 +488,7 @@ static gpointer sender_thread_main(gpointer data) ...@@ -370,6 +488,7 @@ static gpointer sender_thread_main(gpointer data)
const guint8 *frame; const guint8 *frame;
size_t frame_size; size_t frame_size;
gboolean is_idr; gboolean is_idr;
guint64 source_pts = G_MAXUINT64;
gint64 now; gint64 now;
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
...@@ -377,8 +496,6 @@ static gpointer sender_thread_main(gpointer data) ...@@ -377,8 +496,6 @@ static gpointer sender_thread_main(gpointer data)
open = client->track_open; open = client->track_open;
track = client->track; track = client->track;
force_idr = client->need_idr; force_idr = client->need_idr;
if (open && force_idr)
client->need_idr = FALSE;
timestamp = client->rtp_timestamp; timestamp = client->rtp_timestamp;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
...@@ -391,21 +508,46 @@ static gpointer sender_thread_main(gpointer data) ...@@ -391,21 +508,46 @@ static gpointer sender_thread_main(gpointer data)
} }
now = g_get_monotonic_time(); now = g_get_monotonic_time();
maybe_raise_bitrate(client, track, now); maybe_ramp_bitrate(client, track, now);
if (next_frame_us == 0 || now - next_frame_us > frame_us * 4) { if (next_frame_us == 0 || now - next_frame_us > frame_us * 4)
next_frame_us = now; next_frame_us = now;
force_idr = TRUE;
}
if (now < next_frame_us) { if (now < next_frame_us) {
g_usleep((gulong)(next_frame_us - now)); g_usleep((gulong)(next_frame_us - now));
continue; continue;
} }
if (!h264_reader_next(client->reader, force_idr, &frame, &frame_size, &is_idr)) { {
my_zlog_error("libdatachannel: failed to get H264 frame"); gint pull_timeout = force_idr ? 4000 : 120;
g_usleep(100000);
if (!mpp_h264_source_pull(client->h264_source, force_idr, &frame, &frame_size,
&is_idr, &source_pts, pull_timeout)) {
my_zlog_warn("libdatachannel: live H264 frame pull timeout");
next_frame_us = now + frame_us / 2;
continue;
}
}
g_mutex_lock(&client->lock);
if (client->need_idr && !is_idr) {
g_mutex_unlock(&client->lock);
my_zlog_warn("libdatachannel: discard non-IDR while waiting for resync");
next_frame_us = g_get_monotonic_time();
continue; continue;
} }
if (source_pts != G_MAXUINT64) {
if (!client->source_pts_valid || source_pts < client->last_source_pts) {
client->source_pts_base = source_pts;
client->last_source_pts = source_pts;
client->rtp_timestamp_base = client->rtp_timestamp;
client->source_pts_valid = TRUE;
}
timestamp = client->rtp_timestamp_base +
pts_delta_to_rtp(source_pts - client->source_pts_base);
client->last_source_pts = source_pts;
} else {
timestamp = client->rtp_timestamp;
}
g_mutex_unlock(&client->lock);
g_mutex_lock(&client->send_lock); g_mutex_lock(&client->send_lock);
if (!rtcIsOpen(track) || if (!rtcIsOpen(track) ||
...@@ -419,7 +561,9 @@ static gpointer sender_thread_main(gpointer data) ...@@ -419,7 +561,9 @@ static gpointer sender_thread_main(gpointer data)
g_mutex_unlock(&client->send_lock); g_mutex_unlock(&client->send_lock);
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
client->rtp_timestamp += timestamp_step; client->rtp_timestamp = timestamp + timestamp_step;
if (is_idr)
client->need_idr = FALSE;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
next_frame_us += frame_us; next_frame_us += frame_us;
} }
...@@ -496,6 +640,7 @@ static void destroy_peer(RtcClient *client) ...@@ -496,6 +640,7 @@ static void destroy_peer(RtcClient *client)
client->track_open = FALSE; client->track_open = FALSE;
client->answer_sent = FALSE; client->answer_sent = FALSE;
client->need_idr = TRUE; client->need_idr = TRUE;
client->source_pts_valid = FALSE;
clear_local_ice(client); clear_local_ice(client);
g_clear_pointer(&client->remote_ice_ufrag, g_free); g_clear_pointer(&client->remote_ice_ufrag, g_free);
g_clear_pointer(&client->video_mid, g_free); g_clear_pointer(&client->video_mid, g_free);
...@@ -557,7 +702,8 @@ static gboolean setup_track(RtcClient *client, int pc, ...@@ -557,7 +702,8 @@ static gboolean setup_track(RtcClient *client, int pc,
rtcChainRtcpNackResponder(track, WEBRTCPUSH_NACK_PACKETS) < 0 || rtcChainRtcpNackResponder(track, WEBRTCPUSH_NACK_PACKETS) < 0 ||
rtcChainPliHandler(track, on_pli) < 0 || rtcChainPliHandler(track, on_pli) < 0 ||
rtcChainRembHandler(track, on_remb) < 0 || rtcChainRembHandler(track, on_remb) < 0 ||
rtcChainPacingHandler(track, WEBRTCPUSH_INITIAL_BITRATE, rtcChainPacingHandler(track,
pacing_bitrate_for_encoder(WEBRTCPUSH_INITIAL_BITRATE),
WEBRTCPUSH_PACING_INTERVAL_MS) < 0) { WEBRTCPUSH_PACING_INTERVAL_MS) < 0) {
rtcDeleteTrack(track); rtcDeleteTrack(track);
return FALSE; return FALSE;
...@@ -565,6 +711,8 @@ static gboolean setup_track(RtcClient *client, int pc, ...@@ -565,6 +711,8 @@ static gboolean setup_track(RtcClient *client, int pc,
g_mutex_lock(&client->lock); g_mutex_lock(&client->lock);
client->rtp_timestamp = packetizer.timestamp; client->rtp_timestamp = packetizer.timestamp;
client->rtp_timestamp_base = packetizer.timestamp;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
*track_out = track; *track_out = track;
return TRUE; return TRUE;
...@@ -591,24 +739,29 @@ int rtc_client_start(AppState *app) ...@@ -591,24 +739,29 @@ int rtc_client_start(AppState *app)
{ {
RtcClient *client; RtcClient *client;
char *error_message = NULL; char *error_message = NULL;
const char *video_device = WEBRTCPUSH_VIDEO_DEVICE;
if (!app) if (!app)
return -1; return -1;
if (app->video_device && app->video_device[0])
video_device = app->video_device;
client = g_new0(RtcClient, 1); client = g_new0(RtcClient, 1);
client->app = app; client->app = app;
client->pc = -1; client->pc = -1;
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_bitrate_raise_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();
g_mutex_init(&client->lock); g_mutex_init(&client->lock);
g_mutex_init(&client->send_lock); g_mutex_init(&client->send_lock);
client->reader = h264_reader_open(WEBRTCPUSH_H264_FILE, &error_message);
if (!client->reader) { client->h264_source = mpp_h264_source_start(video_device, &error_message);
my_zlog_error("libdatachannel: cannot open %s: %s", if (!client->h264_source) {
WEBRTCPUSH_H264_FILE, error_message ? error_message : "unknown"); my_zlog_error("libdatachannel: live encoder start failed: %s",
error_message ? error_message : "unknown");
g_free(error_message); g_free(error_message);
g_queue_free(client->pending_local_ice); g_queue_free(client->pending_local_ice);
g_queue_free(client->pending_remote_ice); g_queue_free(client->pending_remote_ice);
...@@ -617,10 +770,14 @@ int rtc_client_start(AppState *app) ...@@ -617,10 +770,14 @@ int rtc_client_start(AppState *app)
g_free(client); g_free(client);
return -1; return -1;
} }
app->rtc_client = client; app->rtc_client = client;
client->sender_thread = g_thread_new("rtc-h264-send", sender_thread_main, client); client->sender_thread = g_thread_new("rtc-h264-send", sender_thread_main, client);
my_zlog_info("libdatachannel: loaded %zu H264 frames from %s", mpp_h264_source_set_bitrate(client->h264_source, client->target_bitrate);
h264_reader_frame_count(client->reader), WEBRTCPUSH_H264_FILE); my_zlog_info("libdatachannel: MPP live source started device=%s mpp=%d fps=%d",
video_device,
mpp_h264_source_is_mpp_encoder(client->h264_source) ? 1 : 0,
WEBRTCPUSH_H264_FPS);
return 0; return 0;
} }
...@@ -649,7 +806,7 @@ void rtc_client_stop(AppState *app) ...@@ -649,7 +806,7 @@ void rtc_client_stop(AppState *app)
clear_local_ice(client); clear_local_ice(client);
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
g_queue_free(client->pending_local_ice); g_queue_free(client->pending_local_ice);
h264_reader_close(client->reader); mpp_h264_source_stop(client->h264_source);
g_mutex_clear(&client->send_lock); g_mutex_clear(&client->send_lock);
g_mutex_clear(&client->lock); g_mutex_clear(&client->lock);
app->rtc_client = NULL; app->rtc_client = NULL;
...@@ -714,8 +871,9 @@ int rtc_client_handle_offer(AppState *app, const char *sdp) ...@@ -714,8 +871,9 @@ 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_bitrate_raise_us = g_get_monotonic_time(); client->last_bitrate_ramp_us = g_get_monotonic_time();
client->need_idr = TRUE; client->need_idr = TRUE;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock); g_mutex_unlock(&client->lock);
my_zlog_info("libdatachannel: applying offer mid=%s pt=%d ufrag=%s", my_zlog_info("libdatachannel: applying offer mid=%s pt=%d ufrag=%s",
......
...@@ -4,32 +4,36 @@ ...@@ -4,32 +4,36 @@
/* /*
* WEBRTCPUSH_USE_MPP — 推流方式(与 Chromium 浏览器互斥,共用 thread_open_browser 线程) * WEBRTCPUSH_USE_MPP — 推流方式(与 Chromium 浏览器互斥,共用 thread_open_browser 线程)
* 0:走 Chromium 浏览器推流(opencamsh,原有逻辑) * 0:走 Chromium 浏览器推流(opencamsh,原有逻辑)
* 1:走 libdatachannel 原生推流,不打开浏览器 * 1:走 libdatachannel 原生推流(摄像头 → MPP/x264 → libdatachannel)
* *
* WEBRTCPUSH_USE_MPP 是历史配置名。第一阶段原生分支从本地 Annex-B * 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* H.264 文件发送;验证通路后,只替换输入为 MPP 实时编码,入口仍是这一条 * 音频尚未接入,视频跑通后再接 ALSA→Opus track
*/ */
#define WEBRTCPUSH_USE_MPP 1 #define WEBRTCPUSH_USE_MPP 1
/* libdatachannel 第一阶段:720p Annex-B 测试流。 */ /* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FILE PROJECT_ROOT_DIR "/testdata/rtc_test_720p.h264" #define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_FPS 25 #define WEBRTCPUSH_H264_MIN_FPS 22
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS * 3)
/* REMB 慢升快降,最高码率按当前要求限制为 3.2 Mbps。 */ /* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */
#define WEBRTCPUSH_INITIAL_BITRATE 1000000U #define WEBRTCPUSH_INITIAL_BITRATE 1400000U /* 起播 1.4 Mbps */
#define WEBRTCPUSH_MIN_BITRATE 250000U #define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MAX_BITRATE 3200000U #define WEBRTCPUSH_MAX_BITRATE 3200000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 82U /* 链路容量扣除音频/RTP 余量 */
#define WEBRTCPUSH_BITRATE_RAMP_MS 1000U /* 每秒向目标码率逼近一档 */
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U #define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 120U
#define WEBRTCPUSH_PACING_MAX_BITRATE 3750000U
#define WEBRTCPUSH_NACK_PACKETS 1024U #define WEBRTCPUSH_NACK_PACKETS 1024U
/* 视频采集:auto | /dev/video0 | test(测试图案) */ /* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
#define WEBRTCPUSH_VIDEO_DEVICE "auto" #define WEBRTCPUSH_VIDEO_DEVICE "auto"
/* /*
* 麦克风采集(仅上行推流到手机,与车机喇叭播放 audioplay/tts 无关)。 * 麦克风采集(仅上行推流到手机,与车机喇叭播放 audioplay/tts 无关)。
* 留空表示不推音频;hw:X,Y 运行时会转为 plughw:X,Y,并优先经 Pulse 的 input 源采集。 * 留空表示不推音频;hw:X,Y 运行时会转为 plughw:X,Y,并优先经 Pulse 的 input 源采集。
*/ */
/* auto:读 /proc/asound/pcm 选第一个 capture;hw:X,Y 固定卡号 */
#define WEBRTCPUSH_ALSA_DEVICE "auto" #define WEBRTCPUSH_ALSA_DEVICE "auto"
/* /*
...@@ -41,10 +45,6 @@ ...@@ -41,10 +45,6 @@
/* WebRTC 信令 WebSocket 主机(路径 /websocket?dev=设备号) */ /* WebRTC 信令 WebSocket 主机(路径 /websocket?dev=设备号) */
#define WEBRTCPUSH_SIGNAL_HOST "signal.yd-ss.com" #define WEBRTCPUSH_SIGNAL_HOST "signal.yd-ss.com"
/*
* 1:g_print / 关键步骤打 INFO(Release 下也可见);0:走 my_zlog_debug
* 编译 Debug:cmake -DCMAKE_BUILD_TYPE=Debug ..
*/
#define WEBRTCPUSH_DEBUG 0 #define WEBRTCPUSH_DEBUG 0
#endif #endif
...@@ -172,7 +172,7 @@ int webrtcpush_run_blocking(void) ...@@ -172,7 +172,7 @@ int webrtcpush_run_blocking(void)
s_app.prefer_mpp = TRUE; s_app.prefer_mpp = TRUE;
s_app.device_offerer = FALSE; s_app.device_offerer = FALSE;
s_app.verbose = WEBRTCPUSH_DEBUG ? TRUE : FALSE; s_app.verbose = WEBRTCPUSH_DEBUG ? TRUE : FALSE;
s_app.enable_audio = FALSE; /* Phase 1 validates the H.264 transport first. */ s_app.enable_audio = FALSE; /* 视频先行;音频后续 ALSA→Opus track */
s_app.loop = g_main_loop_new(NULL, FALSE); s_app.loop = g_main_loop_new(NULL, FALSE);
...@@ -198,9 +198,10 @@ int webrtcpush_run_blocking(void) ...@@ -198,9 +198,10 @@ int webrtcpush_run_blocking(void)
} }
s_running = TRUE; s_running = TRUE;
my_zlog_info("webrtcpush libdatachannel started dev=%s file=%s fps=%d bitrate=%u..%u", my_zlog_info("webrtcpush libdatachannel started dev=%s device=%s fps=%d bitrate=%u..%u",
s_app.dev_room ? s_app.dev_room : "?", s_app.dev_room ? s_app.dev_room : "?",
WEBRTCPUSH_H264_FILE, WEBRTCPUSH_H264_FPS, s_app.video_device ? s_app.video_device : WEBRTCPUSH_VIDEO_DEVICE,
WEBRTCPUSH_H264_FPS,
WEBRTCPUSH_INITIAL_BITRATE, WEBRTCPUSH_MAX_BITRATE); WEBRTCPUSH_INITIAL_BITRATE, WEBRTCPUSH_MAX_BITRATE);
g_main_loop_run(s_app.loop); g_main_loop_run(s_app.loop);
......
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_01","wifi_password":"pass0001"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_02","wifi_password":"pass0002"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_03","wifi_password":"pass0003"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_04","wifi_password":"pass0004"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_05","wifi_password":"pass0005"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_06","wifi_password":"pass0006"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_07","wifi_password":"pass0007"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_08","wifi_password":"pass0008"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_09","wifi_password":"pass0009"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_10","wifi_password":"pass0010"}}
{"head":{"message_type":2008},"body":{"wifi_ssid":"test_wifi_11","wifi_password":"pass0011"}}
...@@ -25,6 +25,7 @@ class RTC_CPP_EXPORT PacingHandler : public MediaHandler { ...@@ -25,6 +25,7 @@ class RTC_CPP_EXPORT PacingHandler : public MediaHandler {
public: 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();
void outgoing(message_vector &messages, const message_callback &send) override; void outgoing(message_vector &messages, const message_callback &send) override;
......
...@@ -430,6 +430,10 @@ RTC_C_EXPORT int rtcChainPacingHandler(int tr, unsigned int bitrate, ...@@ -430,6 +430,10 @@ RTC_C_EXPORT int rtcChainPacingHandler(int tr, unsigned int bitrate,
// Update a previously chained pacing handler without rebuilding the track // Update a previously chained pacing handler without rebuilding the track
RTC_C_EXPORT int rtcSetPacingBitrate(int tr, unsigned int bitrate); RTC_C_EXPORT int rtcSetPacingBitrate(int tr, unsigned int bitrate);
// Drop queued RTP packets before resynchronizing with a fresh key frame.
// Returns the number of dropped packets, or a negative RTC_ERR_* value.
RTC_C_EXPORT int rtcClearPacingQueue(int tr);
// 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);
......
...@@ -23,6 +23,7 @@ public: ...@@ -23,6 +23,7 @@ public:
static const size_t DefaultMaxSize = 512; static const size_t DefaultMaxSize = 512;
RtcpNackResponder(size_t maxSize = DefaultMaxSize); RtcpNackResponder(size_t maxSize = DefaultMaxSize);
void clear();
void incoming(message_vector &messages, const message_callback &send) override; void incoming(message_vector &messages, const message_callback &send) override;
void outgoing(message_vector &messages, const message_callback &send) override; void outgoing(message_vector &messages, const message_callback &send) override;
...@@ -64,6 +65,9 @@ private: ...@@ -64,6 +65,9 @@ private:
/// Stores packet /// Stores packet
/// @param packet Packet /// @param packet Packet
void store(message_ptr packet); void store(message_ptr packet);
/// Drops all packets retained for retransmission.
void clear();
}; };
const shared_ptr<Storage> mStorage; const shared_ptr<Storage> mStorage;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#include <limits>
#include <mutex> #include <mutex>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
...@@ -32,6 +33,7 @@ std::unordered_map<int, shared_ptr<Track>> trackMap; ...@@ -32,6 +33,7 @@ std::unordered_map<int, shared_ptr<Track>> trackMap;
std::unordered_map<int, shared_ptr<RtcpSrReporter>> rtcpSrReporterMap; std::unordered_map<int, shared_ptr<RtcpSrReporter>> rtcpSrReporterMap;
std::unordered_map<int, shared_ptr<RtpPacketizationConfig>> rtpConfigMap; std::unordered_map<int, shared_ptr<RtpPacketizationConfig>> rtpConfigMap;
std::unordered_map<int, shared_ptr<PacingHandler>> pacingHandlerMap; std::unordered_map<int, shared_ptr<PacingHandler>> pacingHandlerMap;
std::unordered_map<int, shared_ptr<RtcpNackResponder>> rtcpNackResponderMap;
#endif #endif
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
std::unordered_map<int, shared_ptr<WebSocket>> webSocketMap; std::unordered_map<int, shared_ptr<WebSocket>> webSocketMap;
...@@ -122,6 +124,7 @@ void eraseTrack(int tr) { ...@@ -122,6 +124,7 @@ void eraseTrack(int tr) {
rtcpSrReporterMap.erase(tr); rtcpSrReporterMap.erase(tr);
rtpConfigMap.erase(tr); rtpConfigMap.erase(tr);
pacingHandlerMap.erase(tr); pacingHandlerMap.erase(tr);
rtcpNackResponderMap.erase(tr);
#endif #endif
userPointerMap.erase(tr); userPointerMap.erase(tr);
} }
...@@ -133,10 +136,12 @@ size_t eraseAll() { ...@@ -133,10 +136,12 @@ size_t eraseAll() {
trackMap.clear(); trackMap.clear();
peerConnectionMap.clear(); peerConnectionMap.clear();
#if RTC_ENABLE_MEDIA #if RTC_ENABLE_MEDIA
count += rtcpSrReporterMap.size() + rtpConfigMap.size() + pacingHandlerMap.size(); count += rtcpSrReporterMap.size() + rtpConfigMap.size() + pacingHandlerMap.size() +
rtcpNackResponderMap.size();
rtcpSrReporterMap.clear(); rtcpSrReporterMap.clear();
rtpConfigMap.clear(); rtpConfigMap.clear();
pacingHandlerMap.clear(); pacingHandlerMap.clear();
rtcpNackResponderMap.clear();
#endif #endif
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
count += webSocketMap.size() + webSocketServerMap.size(); count += webSocketMap.size() + webSocketServerMap.size();
...@@ -172,6 +177,7 @@ void eraseChannel(int id) { ...@@ -172,6 +177,7 @@ void eraseChannel(int id) {
rtcpSrReporterMap.erase(id); rtcpSrReporterMap.erase(id);
rtpConfigMap.erase(id); rtpConfigMap.erase(id);
pacingHandlerMap.erase(id); pacingHandlerMap.erase(id);
rtcpNackResponderMap.erase(id);
#endif #endif
return; return;
} }
...@@ -1388,6 +1394,10 @@ int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) { ...@@ -1388,6 +1394,10 @@ int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
auto track = getTrack(tr); auto track = getTrack(tr);
auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount); auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
track->chainMediaHandler(responder); track->chainMediaHandler(responder);
{
std::lock_guard lock(mutex);
rtcpNackResponderMap[tr] = responder;
}
return RTC_ERR_SUCCESS; return RTC_ERR_SUCCESS;
}); });
} }
...@@ -1449,6 +1459,29 @@ int rtcSetPacingBitrate(int tr, unsigned int bitrate) { ...@@ -1449,6 +1459,29 @@ int rtcSetPacingBitrate(int tr, unsigned int bitrate) {
}); });
} }
int rtcClearPacingQueue(int tr) {
return wrap([&] {
shared_ptr<PacingHandler> handler;
shared_ptr<RtcpNackResponder> nackResponder;
{
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;
if (auto nackIt = rtcpNackResponderMap.find(tr);
nackIt != rtcpNackResponderMap.end())
nackResponder = nackIt->second;
}
const size_t count = handler->clear();
if (nackResponder)
nackResponder->clear();
return count > static_cast<size_t>(std::numeric_limits<int>::max())
? std::numeric_limits<int>::max()
: static_cast<int>(count);
});
}
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);
......
...@@ -24,6 +24,15 @@ void PacingHandler::setBitrate(double bitsPerSecond) { ...@@ -24,6 +24,15 @@ void PacingHandler::setBitrate(double bitsPerSecond) {
mBytesPerSecond.store(bitsPerSecond / 8); mBytesPerSecond.store(bitsPerSecond / 8);
} }
size_t PacingHandler::clear() {
std::lock_guard<std::mutex> lock(mMutex);
const size_t count = mRtpBuffer.size();
while (!mRtpBuffer.empty())
mRtpBuffer.pop();
mBudget = 0.;
return count;
}
void PacingHandler::schedule(const message_callback &send) { void PacingHandler::schedule(const message_callback &send) {
if (mHaveScheduled.exchange(true)) { if (mHaveScheduled.exchange(true)) {
return; return;
......
...@@ -20,6 +20,8 @@ namespace rtc { ...@@ -20,6 +20,8 @@ namespace rtc {
RtcpNackResponder::RtcpNackResponder(size_t maxSize) RtcpNackResponder::RtcpNackResponder(size_t maxSize)
: mStorage(std::make_shared<Storage>(maxSize)) {} : mStorage(std::make_shared<Storage>(maxSize)) {}
void RtcpNackResponder::clear() { mStorage->clear(); }
void RtcpNackResponder::incoming(message_vector &messages, const message_callback &send) { void RtcpNackResponder::incoming(message_vector &messages, const message_callback &send) {
for (const auto &message : messages) { for (const auto &message : messages) {
if (message->type != Message::Control) if (message->type != Message::Control)
...@@ -108,6 +110,13 @@ void RtcpNackResponder::Storage::store(message_ptr packet) { ...@@ -108,6 +110,13 @@ void RtcpNackResponder::Storage::store(message_ptr packet) {
} }
} }
void RtcpNackResponder::Storage::clear() {
std::lock_guard lock(mutex);
storage.clear();
oldest.reset();
newest.reset();
}
} // namespace rtc } // namespace rtc
#endif /* RTC_ENABLE_MEDIA */ #endif /* RTC_ENABLE_MEDIA */
...@@ -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-06-30.log"; millisecond my_log.* "/home/orangepi/car/master/log/log_2026-07-01.log"; millisecond
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment