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

未修改完第二次保存提交

parent edadce9a
......@@ -4,6 +4,9 @@ deviceback
Deviceld.txt
main
# 本地日报/笔记,不提交
docs/
# CMake / build 输出(默认全部忽略)
build/*
build/.ninja_*
......
......@@ -95,9 +95,4 @@ target_compile_options(main PRIVATE ${WEBRTCPUSH_CFLAGS})
# 安装规则
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()
......@@ -8,7 +8,8 @@ static char s_wifi_last_password[SSID_MAX_LEN]= {0} ;
//获取当前连接wifi
void get_current_wifi() {
//显示已连接代码
g_current_ssid[0] = '\0';
FILE *fp = popen("nmcli -t -f ACTIVE,SSID dev wifi", "r");
if (!fp) {
my_zlog_error("{\"error\": \"无法执行扫描命令\"}");
......@@ -413,7 +414,7 @@ void connect_wifi_jking() {
if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail");
}
my_zlog_debug("重启成功");
my_zlog_info("重启成功");
}
}
......@@ -500,7 +501,7 @@ void wifi_change_recmqtt(cJSON *body){
if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail");
}
my_zlog_debug("重启成功");
my_zlog_info("重启成功");
}
if(ret == 0){
if(can_access_internet()==0){
......@@ -508,7 +509,7 @@ void wifi_change_recmqtt(cJSON *body){
my_zlog_debug("ssid:%S",g_current_ssid);
if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail");
my_zlog_debug("重启成功");
my_zlog_info("重启成功");
}else {
delete_wifi_conf();
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){
if(system("sudo reboot")!=0){//重启香橙派
my_zlog_error("reboot fail");
}
my_zlog_debug("重启成功");
my_zlog_info("重启成功");
}
}
......
......@@ -3,98 +3,245 @@
#include "device_wifi_manager.h"
#include "modules_common.h"
#define MAX_WIFI_ENTRIES 11
#define MAX_WIFI_ENTRIES 32
#define MAX_SAVED_WIFI_SLOTS 10
#define SSID_MAX_LENGTH 64
#define WIFI_PSK_MAX_LENGTH 63
#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 {
char ssid[SSID_MAX_LENGTH];
time_t mtime;
} 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) {
return ssid != NULL && strcmp(ssid, default_SSID) == 0;
}
static int connection_name_from_filename(const char *filename, char *ssid_out, size_t ssid_size) {
const char *dot = strrchr(filename, '.');
if (dot && strcmp(dot, ".nmconnection") == 0) {
size_t name_len = (size_t)(dot - filename);
if (name_len == 0 || name_len >= ssid_size) {
static int entry_index_by_name(const saved_wifi_entry_t *entries, int count,
const char *name) {
for (int i = 0; i < count; i++) {
if (strcmp(entries[i].ssid, name) == 0)
return i;
}
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];
fp = fopen(filepath, "r");
if (!fp)
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, name_len);
ssid_out[name_len] = '\0';
return 0;
}
if (strlen(filename) >= ssid_size) {
fclose(fp);
return -1;
}
strncpy(ssid_out, filename, ssid_size - 1);
ssid_out[ssid_size - 1] = '\0';
return 0;
}
static int is_wifi_nmconnection_file(const char *filepath) {
FILE *fp = fopen(filepath, "r");
if (!fp) {
static int is_wifi_profile_file(const char *filepath) {
FILE *fp;
char line[256];
fp = fopen(filepath, "r");
if (!fp)
return 0;
}
char line[256];
int is_wifi = 0;
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "type=wifi", 9) == 0 ||
strncmp(line, "type=802-11-wireless", 20) == 0) {
is_wifi = 1;
break;
trim_line(line);
if (strcmp(line, "type=wifi") == 0 ||
strcmp(line, "type=802-11-wireless") == 0) {
fclose(fp);
return 1;
}
}
fclose(fp);
return is_wifi;
return 0;
}
static int wifi_connection_exists(const char *ssid) {
char path[512];
snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, ssid);
return access(path, F_OK) == 0;
static int is_wifi_connection_type(const char *type) {
return type && (strcmp(type, "802-11-wireless") == 0 || strcmp(type, "wifi") == 0);
}
static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_count) {
DIR *dir = opendir(NM_CONN_DIR);
if (!dir) {
my_zlog_error("无法打开NetworkManager配置目录");
/* 解析 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];
struct stat st;
DIR *dir;
struct dirent *entry;
int count = 0;
time_t oldest = 0;
size_t ssid_len;
while ((entry = readdir(dir)) != NULL && count < max_count) {
if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN) {
if (!ssid || !ssid[0])
return 0;
snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, ssid);
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;
}
char ssid[SSID_MAX_LENGTH];
if (connection_name_from_filename(entry->d_name, ssid, sizeof(ssid)) != 0) {
continue;
closedir(dir);
return oldest;
}
static void merge_wifi_entry(saved_wifi_entry_t *entries, int *count, int max_count,
const char *ssid, time_t mtime) {
int idx;
time_t mt;
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;
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s%s", NM_CONN_DIR, entry->d_name);
if (!is_wifi_nmconnection_file(filepath)) {
continue;
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;
int count = 0;
dir = opendir(NM_CONN_DIR);
if (!dir) {
my_zlog_error("无法打开NetworkManager配置目录");
return -1;
}
while ((entry = readdir(dir)) != NULL && count < max_count) {
char filepath[512];
char profile_id[SSID_MAX_LENGTH];
struct stat st;
if (stat(filepath, &st) != 0) {
if (entry->d_type != DT_REG && entry->d_type != DT_UNKNOWN)
continue;
if (!strstr(entry->d_name, ".nmconnection"))
continue;
snprintf(filepath, sizeof(filepath), "%s%s", NM_CONN_DIR, entry->d_name);
if (!is_wifi_profile_file(filepath))
continue;
if (read_profile_id(filepath, profile_id, sizeof(profile_id)) != 0)
continue;
if (stat(filepath, &st) != 0)
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].mtime = st.st_mtime;
count++;
......@@ -104,94 +251,431 @@ static int collect_saved_wifi_entries(saved_wifi_entry_t *entries, int max_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) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int count = collect_saved_wifi_entries(entries, MAX_WIFI_ENTRIES);
if (count < 0) {
if (count < 0)
return -1;
}
int non_default_count = 0;
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++;
}
}
return non_default_count;
}
static int find_oldest_non_default_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);
if (count <= 0) {
return -1;
static int connection_is_active(const char *name) {
FILE *fp;
char line[256];
char conn_name[SSID_MAX_LENGTH];
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;
time_t oldest_mtime = 0;
pclose(fp);
return 0;
}
for (int i = 0; i < count; i++) {
if (is_default_wifi_ssid(entries[i].ssid)) {
/* wlan0 当前绑定的连接配置名,与 .nmconnection 里 id= 一致 */
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;
value = line + 19;
if (!value[0] || strcmp(value, "--") == 0)
break;
strncpy(name_out, value, name_size - 1);
name_out[name_size - 1] = '\0';
break;
}
if (!found || entries[i].mtime < oldest_mtime) {
oldest_mtime = entries[i].mtime;
strncpy(ssid_out, entries[i].ssid, ssid_size - 1);
ssid_out[ssid_size - 1] = '\0';
found = 1;
pclose(fp);
}
static int wifi_profile_is_in_use(const char *ssid) {
char active_profile[SSID_MAX_LENGTH];
if (!ssid || !ssid[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;
}
return found ? 0 : -1;
closedir(dir);
return removed ? 0 : -1;
}
static int enforce_saved_wifi_limit(const char *new_ssid) {
if (is_default_wifi_ssid(new_ssid) || wifi_connection_exists(new_ssid)) {
return 0;
/* 同名连接在 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;
}
int count = count_non_default_saved_wifi();
if (count < 0) {
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;
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)
return -1;
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];
if (find_oldest_non_default_wifi(oldest_ssid, sizeof(oldest_ssid)) != 0) {
my_zlog_error("非默认WiFi已达上限,但未找到可删除的最旧配置");
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;
}
if (delete_wifi_by_ssid(oldest_ssid) != 0) {
my_zlog_error("删除最旧WiFi失败: %s", oldest_ssid);
rc = device_wifi_delete_saved_profile(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;
}
my_zlog_info("非默认WiFi已达%d个上限,删除最旧配置: %s", MAX_SAVED_WIFI_SLOTS, oldest_ssid);
count--;
my_zlog_info("非默认WiFi已满%d个,删除最旧未使用配置: %s",
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) {
char cmd[256];
snprintf(cmd, sizeof(cmd),
"nmcli connection modify \"%s\" wifi-sec.key-mgmt wpa-psk wifi-sec.psk \"%s\"",
ssid, password);
static void chmod_wifi_profile_files(const char *ssid) {
char path[512];
DIR *dir;
struct dirent *entry;
char profile_id[SSID_MAX_LENGTH];
if (system(cmd) != 0) {
my_zlog_error("更新WiFi密码失败: %s", ssid);
snprintf(path, sizeof(path), "%s%s.nmconnection", NM_CONN_DIR, 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;
}
}
my_zlog_info("已更新WiFi密码: %s", ssid);
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;
}
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;
}
/*成功发送3010:0保存成功,1删除成功,2保存失败,3默认WiFi不可删,4删除失败*/
void device_wifi_public_sendmqtt(int wifi_status) {
get_current_wifi();
cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject();
cJSON *head = cJSON_CreateObject();
......@@ -210,90 +694,126 @@ void device_wifi_public_sendmqtt(int wifi_status) {
cJSON_Delete(root);
}
void device_wifi_rec_delete(cJSON *body) {
cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid");
if (!cJSON_IsString(wifi_ssid)) {
return;
}
int get_saved_wifi_list(char ssid_list[][SSID_MAX_LENGTH], int max_count) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int limit = max_count < MAX_WIFI_ENTRIES ? max_count : MAX_WIFI_ENTRIES;
int count = collect_saved_wifi_entries(entries, limit);
if (count < 0)
return -1;
char *ssid = wifi_ssid->valuestring;
if (is_default_wifi_ssid(ssid)) {
my_zlog_warn("默认WiFi %s 不允许删除", default_SSID);
device_wifi_public_sendmqtt(3);
return;
for (int i = 0; i < count; i++) {
strncpy(ssid_list[i], entries[i].ssid, SSID_MAX_LENGTH - 1);
ssid_list[i][SSID_MAX_LENGTH - 1] = '\0';
}
if (delete_wifi_by_ssid(ssid) == 0) {
my_zlog_info("删除成功");
device_wifi_public_sendmqtt(1);
} else {
my_zlog_info("删除失败");
device_wifi_public_sendmqtt(4);
}
return count;
}
int orange_pi_save_wifi(const char *ssid, const char *password) {
if (!ssid || !password || strlen(ssid) == 0 || strlen(password) == 0) {
fprintf(stderr, "[ERROR] SSID或密码为空\n");
if (validate_wifi_credentials(ssid, password) != 0)
return -1;
}
if (strpbrk(ssid, "\"`$|") || strpbrk(password, "\"`$|")) {
fprintf(stderr, "[ERROR] 检测到非法字符\n");
if (!wifi_connection_exists(ssid) && enforce_saved_wifi_limit(ssid) != 0)
return -1;
if (save_wifi_via_nmcli(ssid, password) != 0) {
my_zlog_error("[ERROR] 保存配置失败: %s", ssid);
return -1;
}
if (wifi_connection_exists(ssid)) {
return update_wifi_password(ssid, password);
my_zlog_info("[SUCCESS] 已保存WiFi配置: %s", ssid);
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;
}
char cmd[512];
snprintf(cmd, sizeof(cmd),
"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);
if (thread_pool_add_task(s_wifi_pool, worker, arg) != 0) {
my_zlog_error("%s: thread_pool_add_task failed", what);
free(arg);
return -1;
}
my_zlog_info("[SUCCESS] 已保存WiFi配置: %s", ssid);
return 0;
}
void device_wifi_rec_sava(cJSON *body) {
cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid");
cJSON *wifi_password = cJSON_GetObjectItem(body, "wifi_password");
if (cJSON_IsString(wifi_ssid) && cJSON_IsString(wifi_password)) {
char *ssid = wifi_ssid->valuestring;
char *password = wifi_password->valuestring;
my_zlog_debug("接收到保存WiFi名称和密码");
if (orange_pi_save_wifi(ssid, password) == 0) {
device_wifi_public_sendmqtt(0);
} else {
wifi_save_task_t *task;
if (!cJSON_IsString(wifi_ssid) || !cJSON_IsString(wifi_password))
return;
task = calloc(1, sizeof(*task));
if (!task) {
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) {
saved_wifi_entry_t entries[MAX_WIFI_ENTRIES];
int limit = max_count < MAX_WIFI_ENTRIES ? max_count : MAX_WIFI_ENTRIES;
int count = collect_saved_wifi_entries(entries, limit);
if (count < 0) {
return -1;
}
void device_wifi_rec_delete(cJSON *body) {
cJSON *wifi_ssid = cJSON_GetObjectItem(body, "wifi_ssid");
wifi_delete_task_t *task;
for (int i = 0; i < count; i++) {
strncpy(ssid_list[i], entries[i].ssid, SSID_MAX_LENGTH - 1);
ssid_list[i][SSID_MAX_LENGTH - 1] = '\0';
}
if (!cJSON_IsString(wifi_ssid))
return;
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() {
......
......@@ -211,7 +211,11 @@ void *thread_open_browser(void *arg)
while (1) {
if (webrtcpush_should_use_mpp()) {
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);
while (s_webrtc_index == 1 && webrtcpush_should_use_mpp()) {
if (webrtcpush_is_running()) {
......
No preview for this file type
......@@ -303,7 +303,7 @@ int hardware_iic_config_init(){
}else if(res ==0){
delay_ms(200);
int ret=system("sudo reboot");//重启香橙派
if(ret==0) my_zlog_debug("重启成功");
if(ret==0) my_zlog_info("重启成功");
return 1;
}
}
......@@ -8,6 +8,7 @@
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
......@@ -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)
{
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)
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)) {
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;
}
snprintf(cmd, sizeof(cmd),
"pactl load-module %s device=%s >/dev/null 2>&1",
snprintf(cmd, sizeof(cmd), "pactl load-module %s device=%s 2>&1",
module_name, AUDIO_USB_ALSA_DEVICE);
int rc = system(cmd);
if (rc == 0) {
my_zlog_debug("%s 注册成功", label);
FILE *fp = popen(cmd, "r");
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;
}
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;
}
......
find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBRTCPUSH_GST REQUIRED
gstreamer-1.0>=1.18
gstreamer-app-1.0
gstreamer-webrtc-1.0
gstreamer-sdp-1.0
gstreamer-video-1.0
......
......@@ -429,9 +429,11 @@ int device_message_receive(cJSON *json)
my_zlog_debug("删除已保存wifi");
break;
case 2011:
int res = system("sudo reboot"); // 重启香橙派
{
int res = system("sudo reboot");
if (res == 0)
my_zlog_info("重启成功");
}
break;
case 2012:
refresh_cam();
......
......@@ -9,6 +9,7 @@ mqttclient g_clients_t[MAX_SERVERS];
static char s_uuid_mqtt_topic_id[MAX_SERVERS][56];
static bool s_mosquitto_lib_inited = false;
static pthread_mutex_t s_mqtt_publish_mutex = PTHREAD_MUTEX_INITIALIZER;
// struct mosquitto *mosq;
// 新增:用于向其他MQTT服务器发送通知的函数
......@@ -47,6 +48,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos)
return 0;
}
pthread_mutex_lock(&s_mqtt_publish_mutex);
for (int i = 0; i < total_count; i++) {
if (!g_clients_t[i].mosq || g_clients_t[i].permanently_failed) {
continue;
......@@ -55,6 +57,7 @@ int mqtt_publish_to_all(const char *topic, const char *payload, int qos)
sent_count++;
}
}
pthread_mutex_unlock(&s_mqtt_publish_mutex);
return sent_count;
}
......
......@@ -345,14 +345,9 @@ static int ensure_webrtc_stack(void)
static int ensure_libdatachannel_stack(void)
{
/* libdatachannel and its ICE/SRTP dependencies are linked into main. */
if (access(WEBRTCPUSH_H264_FILE, R_OK) != 0) {
my_zlog_error("runtime_deps: H264 test file is not readable: %s",
WEBRTCPUSH_H264_FILE);
if (ensure_webrtc_stack() != 0)
return -1;
}
my_zlog_info("runtime_deps: libdatachannel native sender ready, H264=%s",
WEBRTCPUSH_H264_FILE);
my_zlog_info("runtime_deps: libdatachannel native sender ready (MPP live encode)");
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 "h264_reader.h"
#include "mpp_h264_source.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include "ws_signaling.h"
......@@ -27,7 +27,7 @@ typedef struct {
GMutex lock;
GMutex send_lock;
GThread *sender_thread;
H264Reader *reader;
MppH264Source *h264_source;
gboolean stopping;
gboolean track_open;
gboolean need_idr;
......@@ -35,9 +35,13 @@ typedef struct {
int pc;
int track;
guint32 rtp_timestamp;
guint32 rtp_timestamp_base;
guint64 source_pts_base;
guint64 last_source_pts;
gboolean source_pts_valid;
guint target_bitrate;
guint remb_ceiling;
gint64 last_bitrate_raise_us;
gint64 last_bitrate_ramp_us;
gchar *remote_ice_ufrag;
gchar *video_mid;
gint video_mline_index;
......@@ -244,6 +248,7 @@ static void RTC_API on_state_change(int pc, rtcState state, void *ptr)
if (state == RTC_CONNECTED) {
g_mutex_lock(&client->lock);
client->need_idr = TRUE;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock);
}
}
......@@ -257,6 +262,7 @@ static void RTC_API on_track_open(int track, void *ptr)
if (client->track == track) {
client->track_open = TRUE;
client->need_idr = TRUE;
client->source_pts_valid = FALSE;
}
g_mutex_unlock(&client->lock);
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)
static void RTC_API on_pli(int track, void *ptr)
{
RtcClient *client = ptr;
(void)track;
int dropped = 0;
if (!client)
return;
g_mutex_lock(&client->lock);
client->need_idr = TRUE;
client->source_pts_valid = FALSE;
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)
......@@ -302,56 +314,162 @@ static guint clamp_bitrate(guint 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)
{
RtcClient *client = ptr;
guint current;
guint next = 0;
guint ceiling;
(void)track;
guint next = 0;
gboolean severe_down = FALSE;
int dropped = 0;
if (!client || bitrate == 0)
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);
current = client->target_bitrate;
client->remb_ceiling = ceiling;
if (ceiling < current) {
client->target_bitrate = 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);
if (next) {
rtcSetPacingBitrate(track, next);
my_zlog_info("libdatachannel: REMB=%u, pacing immediately down to %u bps",
bitrate, next);
g_mutex_lock(&client->send_lock);
if (client->track == track) {
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);
if (client->remb_ceiling > client->target_bitrate &&
now_us - client->last_bitrate_raise_us >= 2000000) {
guint raised = (guint)(((guint64)client->target_bitrate * 112) / 100);
if (raised > client->remb_ceiling)
raised = client->remb_ceiling;
client->target_bitrate = clamp_bitrate(raised);
client->last_bitrate_raise_us = now_us;
next = client->target_bitrate;
if (now_us - client->last_bitrate_ramp_us <
(gint64)WEBRTCPUSH_BITRATE_RAMP_MS * 1000) {
g_mutex_unlock(&client->lock);
return;
}
client->last_bitrate_ramp_us = now_us;
g_mutex_unlock(&client->lock);
if (next) {
g_mutex_lock(&client->send_lock);
if (client->track == track)
rtcSetPacingBitrate(track, next);
g_mutex_unlock(&client->send_lock);
my_zlog_info("libdatachannel: pacing slowly up to %u bps", next);
}
ramp_bitrate_toward_ceiling(client, track);
}
static guint32 pts_delta_to_rtp(guint64 delta_ns)
{
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)
......@@ -370,6 +488,7 @@ static gpointer sender_thread_main(gpointer data)
const guint8 *frame;
size_t frame_size;
gboolean is_idr;
guint64 source_pts = G_MAXUINT64;
gint64 now;
g_mutex_lock(&client->lock);
......@@ -377,8 +496,6 @@ static gpointer sender_thread_main(gpointer data)
open = client->track_open;
track = client->track;
force_idr = client->need_idr;
if (open && force_idr)
client->need_idr = FALSE;
timestamp = client->rtp_timestamp;
g_mutex_unlock(&client->lock);
......@@ -391,21 +508,46 @@ static gpointer sender_thread_main(gpointer data)
}
now = g_get_monotonic_time();
maybe_raise_bitrate(client, track, now);
if (next_frame_us == 0 || now - next_frame_us > frame_us * 4) {
maybe_ramp_bitrate(client, track, now);
if (next_frame_us == 0 || now - next_frame_us > frame_us * 4)
next_frame_us = now;
force_idr = TRUE;
}
if (now < next_frame_us) {
g_usleep((gulong)(next_frame_us - now));
continue;
}
if (!h264_reader_next(client->reader, force_idr, &frame, &frame_size, &is_idr)) {
my_zlog_error("libdatachannel: failed to get H264 frame");
g_usleep(100000);
{
gint pull_timeout = force_idr ? 4000 : 120;
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;
}
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);
if (!rtcIsOpen(track) ||
......@@ -419,7 +561,9 @@ static gpointer sender_thread_main(gpointer data)
g_mutex_unlock(&client->send_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);
next_frame_us += frame_us;
}
......@@ -496,6 +640,7 @@ static void destroy_peer(RtcClient *client)
client->track_open = FALSE;
client->answer_sent = FALSE;
client->need_idr = TRUE;
client->source_pts_valid = FALSE;
clear_local_ice(client);
g_clear_pointer(&client->remote_ice_ufrag, g_free);
g_clear_pointer(&client->video_mid, g_free);
......@@ -557,7 +702,8 @@ static gboolean setup_track(RtcClient *client, int pc,
rtcChainRtcpNackResponder(track, WEBRTCPUSH_NACK_PACKETS) < 0 ||
rtcChainPliHandler(track, on_pli) < 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) {
rtcDeleteTrack(track);
return FALSE;
......@@ -565,6 +711,8 @@ static gboolean setup_track(RtcClient *client, int pc,
g_mutex_lock(&client->lock);
client->rtp_timestamp = packetizer.timestamp;
client->rtp_timestamp_base = packetizer.timestamp;
client->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock);
*track_out = track;
return TRUE;
......@@ -591,24 +739,29 @@ int rtc_client_start(AppState *app)
{
RtcClient *client;
char *error_message = NULL;
const char *video_device = WEBRTCPUSH_VIDEO_DEVICE;
if (!app)
return -1;
if (app->video_device && app->video_device[0])
video_device = app->video_device;
client = g_new0(RtcClient, 1);
client->app = app;
client->pc = -1;
client->track = -1;
client->target_bitrate = 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_remote_ice = g_queue_new();
g_mutex_init(&client->lock);
g_mutex_init(&client->send_lock);
client->reader = h264_reader_open(WEBRTCPUSH_H264_FILE, &error_message);
if (!client->reader) {
my_zlog_error("libdatachannel: cannot open %s: %s",
WEBRTCPUSH_H264_FILE, error_message ? error_message : "unknown");
client->h264_source = mpp_h264_source_start(video_device, &error_message);
if (!client->h264_source) {
my_zlog_error("libdatachannel: live encoder start failed: %s",
error_message ? error_message : "unknown");
g_free(error_message);
g_queue_free(client->pending_local_ice);
g_queue_free(client->pending_remote_ice);
......@@ -617,10 +770,14 @@ int rtc_client_start(AppState *app)
g_free(client);
return -1;
}
app->rtc_client = client;
client->sender_thread = g_thread_new("rtc-h264-send", sender_thread_main, client);
my_zlog_info("libdatachannel: loaded %zu H264 frames from %s",
h264_reader_frame_count(client->reader), WEBRTCPUSH_H264_FILE);
mpp_h264_source_set_bitrate(client->h264_source, client->target_bitrate);
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;
}
......@@ -649,7 +806,7 @@ void rtc_client_stop(AppState *app)
clear_local_ice(client);
g_mutex_unlock(&client->lock);
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->lock);
app->rtc_client = NULL;
......@@ -714,8 +871,9 @@ int rtc_client_handle_offer(AppState *app, const char *sdp)
client->remote_ice_ufrag = g_strdup(video.ice_ufrag);
client->target_bitrate = 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->source_pts_valid = FALSE;
g_mutex_unlock(&client->lock);
my_zlog_info("libdatachannel: applying offer mid=%s pt=%d ufrag=%s",
......
......@@ -4,32 +4,36 @@
/*
* WEBRTCPUSH_USE_MPP — 推流方式(与 Chromium 浏览器互斥,共用 thread_open_browser 线程)
* 0:走 Chromium 浏览器推流(opencamsh,原有逻辑)
* 1:走 libdatachannel 原生推流,不打开浏览器
* 1:走 libdatachannel 原生推流(摄像头 → MPP/x264 → libdatachannel)
*
* WEBRTCPUSH_USE_MPP 是历史配置名。第一阶段原生分支从本地 Annex-B
* H.264 文件发送;验证通路后,只替换输入为 MPP 实时编码,入口仍是这一条
* 原生分支:v4l2 采集 → mpph264enc(或 x264enc 回退)→ libdatachannel 发送。
* 音频尚未接入,视频跑通后再接 ALSA→Opus track
*/
#define WEBRTCPUSH_USE_MPP 1
/* libdatachannel 第一阶段:720p Annex-B 测试流。 */
#define WEBRTCPUSH_H264_FILE PROJECT_ROOT_DIR "/testdata/rtc_test_720p.h264"
#define WEBRTCPUSH_H264_FPS 25
/* 正常固定 24fps;弱网以降码率为主,后续若启用动态帧率也不得低于 22fps。 */
#define WEBRTCPUSH_H264_FPS 24
#define WEBRTCPUSH_H264_MIN_FPS 22
#define WEBRTCPUSH_H264_GOP (WEBRTCPUSH_H264_FPS * 3)
/* REMB 慢升快降,最高码率按当前要求限制为 3.2 Mbps。 */
#define WEBRTCPUSH_INITIAL_BITRATE 1000000U
#define WEBRTCPUSH_MIN_BITRATE 250000U
/* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略 */
#define WEBRTCPUSH_INITIAL_BITRATE 1400000U /* 起播 1.4 Mbps */
#define WEBRTCPUSH_MIN_BITRATE 500000U
#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_HEADROOM_PERCENT 120U
#define WEBRTCPUSH_PACING_MAX_BITRATE 3750000U
#define WEBRTCPUSH_NACK_PACKETS 1024U
/* 视频采集:auto | /dev/video0 | test(测试图案) */
/* 视频采集:auto | /dev/videoN | test(videotestsrc 测试图案) */
#define WEBRTCPUSH_VIDEO_DEVICE "auto"
/*
* 麦克风采集(仅上行推流到手机,与车机喇叭播放 audioplay/tts 无关)。
* 留空表示不推音频;hw:X,Y 运行时会转为 plughw:X,Y,并优先经 Pulse 的 input 源采集。
*/
/* auto:读 /proc/asound/pcm 选第一个 capture;hw:X,Y 固定卡号 */
#define WEBRTCPUSH_ALSA_DEVICE "auto"
/*
......@@ -41,10 +45,6 @@
/* WebRTC 信令 WebSocket 主机(路径 /websocket?dev=设备号) */
#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
#endif
......@@ -172,7 +172,7 @@ int webrtcpush_run_blocking(void)
s_app.prefer_mpp = TRUE;
s_app.device_offerer = 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);
......@@ -198,9 +198,10 @@ int webrtcpush_run_blocking(void)
}
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 : "?",
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);
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 {
public:
PacingHandler(double bitsPerSecond, std::chrono::milliseconds sendInterval);
void setBitrate(double bitsPerSecond);
size_t clear();
void outgoing(message_vector &messages, const message_callback &send) override;
......
......@@ -430,6 +430,10 @@ RTC_C_EXPORT int rtcChainPacingHandler(int tr, unsigned int bitrate,
// Update a previously chained pacing handler without rebuilding the track
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
RTC_C_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
......
......@@ -23,6 +23,7 @@ public:
static const size_t DefaultMaxSize = 512;
RtcpNackResponder(size_t maxSize = DefaultMaxSize);
void clear();
void incoming(message_vector &messages, const message_callback &send) override;
void outgoing(message_vector &messages, const message_callback &send) override;
......@@ -64,6 +65,9 @@ private:
/// Stores packet
/// @param packet Packet
void store(message_ptr packet);
/// Drops all packets retained for retransmission.
void clear();
};
const shared_ptr<Storage> mStorage;
......
......@@ -14,6 +14,7 @@
#include <algorithm>
#include <chrono>
#include <exception>
#include <limits>
#include <mutex>
#include <type_traits>
#include <unordered_map>
......@@ -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<RtpPacketizationConfig>> rtpConfigMap;
std::unordered_map<int, shared_ptr<PacingHandler>> pacingHandlerMap;
std::unordered_map<int, shared_ptr<RtcpNackResponder>> rtcpNackResponderMap;
#endif
#if RTC_ENABLE_WEBSOCKET
std::unordered_map<int, shared_ptr<WebSocket>> webSocketMap;
......@@ -122,6 +124,7 @@ void eraseTrack(int tr) {
rtcpSrReporterMap.erase(tr);
rtpConfigMap.erase(tr);
pacingHandlerMap.erase(tr);
rtcpNackResponderMap.erase(tr);
#endif
userPointerMap.erase(tr);
}
......@@ -133,10 +136,12 @@ size_t eraseAll() {
trackMap.clear();
peerConnectionMap.clear();
#if RTC_ENABLE_MEDIA
count += rtcpSrReporterMap.size() + rtpConfigMap.size() + pacingHandlerMap.size();
count += rtcpSrReporterMap.size() + rtpConfigMap.size() + pacingHandlerMap.size() +
rtcpNackResponderMap.size();
rtcpSrReporterMap.clear();
rtpConfigMap.clear();
pacingHandlerMap.clear();
rtcpNackResponderMap.clear();
#endif
#if RTC_ENABLE_WEBSOCKET
count += webSocketMap.size() + webSocketServerMap.size();
......@@ -172,6 +177,7 @@ void eraseChannel(int id) {
rtcpSrReporterMap.erase(id);
rtpConfigMap.erase(id);
pacingHandlerMap.erase(id);
rtcpNackResponderMap.erase(id);
#endif
return;
}
......@@ -1388,6 +1394,10 @@ int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
auto track = getTrack(tr);
auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
track->chainMediaHandler(responder);
{
std::lock_guard lock(mutex);
rtcpNackResponderMap[tr] = responder;
}
return RTC_ERR_SUCCESS;
});
}
......@@ -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) {
return wrap([&] {
auto config = getRtpConfig(id);
......
......@@ -24,6 +24,15 @@ void PacingHandler::setBitrate(double bitsPerSecond) {
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) {
if (mHaveScheduled.exchange(true)) {
return;
......
......@@ -20,6 +20,8 @@ namespace rtc {
RtcpNackResponder::RtcpNackResponder(size_t maxSize)
: mStorage(std::make_shared<Storage>(maxSize)) {}
void RtcpNackResponder::clear() { mStorage->clear(); }
void RtcpNackResponder::incoming(message_vector &messages, const message_callback &send) {
for (const auto &message : messages) {
if (message->type != Message::Control)
......@@ -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
#endif /* RTC_ENABLE_MEDIA */
......@@ -9,4 +9,4 @@ file perms = 600
millisecond = "%d(%Y-%m-%d %H:%M:%S).%ms [%V] %m%n"
[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