Commit 3585cc8c authored by 957dd's avatar 957dd

c语音推流使用cpu软件解码,硬件h264编码,提升流畅度

parent 4687e6c8
No preview for this file type
#include "common.h"
#include "audioplay.h"
#include "audio_sink.h" /* USB声卡排队锁 */
#include "device_identity.h"
#include "mqtt_init.h"
#include "http_config_mqtt.h"
#include "audiotts_play.h"
#include "wifi_autoconfig.h"
#include <stdio.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#define AUDIO_USB_ALSA_DEVICE "hw:2,0"
#define AUDIO_LOCAL_ALSA_DEVICE "plughw:2,0"
#define AUDIO_LOCAL_PLAY_TIMEOUT_SEC 8
static int s_audio_status=7;
static char s_urlbuf[512];
......@@ -21,6 +25,7 @@ static double s_audio_volume=0.8;
static int s_local_play_pending = 0;
static char s_local_filepath[512];
static pthread_mutex_t s_local_play_mutex = PTHREAD_MUTEX_INITIALIZER;
static int local_is_cn_lang(const char *lang) {
return lang != NULL && (strcmp(lang, AUDIO_LANG_ZH) == 0 || strcmp(lang, "cn") == 0);
......@@ -74,12 +79,16 @@ static int local_resolve_filepath(const char *filename, const char *language_ove
}
static void local_queue_play(const char *filename, const char *language) {
if (!local_resolve_filepath(filename, language, s_local_filepath, sizeof(s_local_filepath))) {
char resolved[512];
if (!local_resolve_filepath(filename, language, resolved, sizeof(resolved))) {
my_zlog_warn("2017 本地音频不存在: %s", filename);
return;
}
pthread_mutex_lock(&s_local_play_mutex);
snprintf(s_local_filepath, sizeof(s_local_filepath), "%s", resolved);
s_local_play_pending = 1;
my_zlog_info("2017 已排队本地音频: %s", s_local_filepath);
pthread_mutex_unlock(&s_local_play_mutex);
my_zlog_info("2017 已排队本地音频: %s", resolved);
}
void audioplay_local_mqtt_receive(cJSON *body) {
......@@ -143,6 +152,76 @@ static double audioplay_volume_clamp(double v) {
int audio_wheat_init();
static int audio_system_exit_code(int status) {
if (status == -1) {
return -1;
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
}
return -1;
}
static void shell_single_quote(char *out, size_t size, const char *in) {
size_t pos = 0;
if (!out || size == 0) {
return;
}
out[pos++] = '\'';
if (in) {
for (const char *p = in; *p && pos + 5 < size; p++) {
if (*p == '\'') {
const char *esc = "'\\''";
for (const char *e = esc; *e && pos + 1 < size; e++) {
out[pos++] = *e;
}
} else {
out[pos++] = *p;
}
}
}
if (pos + 1 < size) {
out[pos++] = '\'';
}
out[pos] = '\0';
}
static int play_local_audio_file(const char *filepath) {
char quoted_path[1024];
char command[2048];
int ret;
int exit_code;
shell_single_quote(quoted_path, sizeof(quoted_path), filepath);
/* 排队等待USB声卡: 如果audio_sink正在播放手机音频, 等它释放 */
audio_sink_lock_alsa();
snprintf(command, sizeof(command),
/* sync=true 防止 EOS 提前关 ALSA 丢尾部(只播前半段); channels=2 上混规避 USB 声卡单声道 ring_buffer CRITICAL */
"timeout %ds gst-launch-1.0 -q filesrc location=%s ! mpegaudioparse ! mpg123audiodec ! audioconvert ! audioresample ! audio/x-raw,channels=2 ! alsasink device=%s sync=true >/dev/null 2>&1",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path, AUDIO_LOCAL_ALSA_DEVICE);
ret = system(command);
exit_code = audio_system_exit_code(ret);
if (exit_code != 0) {
my_zlog_warn("本地音频 GStreamer 播放失败 exit=%d,尝试 ffplay: %s",
exit_code, filepath);
snprintf(command, sizeof(command),
"timeout %ds ffplay -nodisp -autoexit -loglevel warning %s",
AUDIO_LOCAL_PLAY_TIMEOUT_SEC, quoted_path);
ret = system(command);
exit_code = audio_system_exit_code(ret);
}
audio_sink_unlock_alsa();
return exit_code;
}
//接收音频播放
void audioplay_mqtt_receive(cJSON *json) {
// 解析"audioLink"字段(修正了原始JSON中的拼写错误)
......@@ -212,7 +291,7 @@ void audioplay_send_mqtt() {
//音频播放
void audioplay_cycle(){
char command[1024];
char command[2048];
int ret;
while(1){
if(s_audio_status==0){
......@@ -241,16 +320,25 @@ void audioplay_cycle(){
audioplay_send_mqtt();
}
char local_filepath[sizeof(s_local_filepath)];
int local_play_pending = 0;
pthread_mutex_lock(&s_local_play_mutex);
if (s_local_play_pending) {
s_local_play_pending = 0;
snprintf(command, sizeof(command),
"ffplay -nodisp -autoexit -loglevel quiet \"%s\"", s_local_filepath);
my_zlog_debug("播放本地音频: %s", s_local_filepath);
ret = system(command);
if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
my_zlog_debug("本地音频播放完成: %s", s_local_filepath);
snprintf(local_filepath, sizeof(local_filepath), "%s", s_local_filepath);
local_play_pending = 1;
}
pthread_mutex_unlock(&s_local_play_mutex);
if (local_play_pending) {
int exit_code;
my_zlog_info("播放本地音频: %s", local_filepath);
exit_code = play_local_audio_file(local_filepath);
if (exit_code == 0) {
my_zlog_debug("本地音频播放完成: %s", local_filepath);
} else {
my_zlog_warn("本地音频播放失败: %s", s_local_filepath);
my_zlog_warn("本地音频播放失败 exit=%d: %s", exit_code, local_filepath);
}
}
......@@ -374,7 +462,7 @@ int audio_speaker_init() {
int audio_init(){
delay_s(5);
delay_s(1);
audio_wheat_init();
delay_s(1);
audio_speaker_init();
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ pkg_check_modules(WEBRTCPUSH_GST REQUIRED
)
pkg_check_modules(WEBRTCPUSH_JSON REQUIRED json-glib-1.0)
pkg_check_modules(WEBRTCPUSH_SOUP REQUIRED libsoup-2.4)
pkg_check_modules(WEBRTCPUSH_JPEG REQUIRED libjpeg)
file(GLOB_RECURSE MODULES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/logger/*.c
......@@ -47,6 +48,7 @@ set(MODULES_INCLUDE_DIRS
${WEBRTCPUSH_GST_INCLUDE_DIRS}
${WEBRTCPUSH_JSON_INCLUDE_DIRS}
${WEBRTCPUSH_SOUP_INCLUDE_DIRS}
${WEBRTCPUSH_JPEG_INCLUDE_DIRS}
PARENT_SCOPE
)
......@@ -54,6 +56,7 @@ set(WEBRTCPUSH_LIBRARIES
${WEBRTCPUSH_GST_LIBRARIES}
${WEBRTCPUSH_JSON_LIBRARIES}
${WEBRTCPUSH_SOUP_LIBRARIES}
${WEBRTCPUSH_JPEG_LIBRARIES}
PARENT_SCOPE
)
......@@ -61,5 +64,6 @@ set(WEBRTCPUSH_CFLAGS
${WEBRTCPUSH_GST_CFLAGS_OTHER}
${WEBRTCPUSH_JSON_CFLAGS_OTHER}
${WEBRTCPUSH_SOUP_CFLAGS_OTHER}
${WEBRTCPUSH_JPEG_CFLAGS_OTHER}
PARENT_SCOPE
)
#include "audio_sink.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
/* USB声卡排队锁: audio_sink 和本地音频串行访问 plughw:2,0 */
static GMutex g_alsa_device_lock;
static AudioSink *g_audio_sink_singleton = NULL;
/*
* 手机→设备方向的音频播放管道(按键模式):
* appsrc(opus payload) → opusdec → audioconvert → audioresample → volume → alsasink
*
* 手机端是"按住说话"模式(最长 15s),不是持续推流,因此:
* - on_audio_message 收到包后入队,立即返回(不阻塞 libdatachannel 线程)
* - 独立线程消费队列,维护"按键会话"
* - 会话开始(首包到达):pipeline 切到 PLAYING
* - 会话结束(500ms 无包 或 15s 超时):flush appsrc + pipeline 切到 READY
* 切到 READY 释放 ALSA 设备,避免 alsasink 持续占用/空转
* 手机→设备方向的音频不再走 RTP:手机端 audio 为 recvonly(只收不发),
* 手机→设备的音频通过 DataChannel 发送 MP4,由 decodebin 播放。
* 因此本模块不再创建 GStreamer 接收管道,仅保留 USB 声卡全局锁,
* 供 DataChannel 播放线程与本地提示音串行访问 ALSA 设备使用。
*/
#define AUDIO_SINK_SESSION_TIMEOUT_MS 500 /* 500ms 无包认为按键结束 */
#define AUDIO_SINK_SESSION_MAX_MS 15000 /* 单次按键最长 15s */
typedef struct {
uint8_t *data;
size_t size;
} SinkPacket;
struct AudioSink {
GstElement *pipeline;
GstElement *appsrc;
GstElement *vol;
GAsyncQueue *queue; /* 待处理 Opus 包队列 */
GMutex lock; /* 保护 pipeline 状态切换 */
GThread *thread; /* 消费线程 */
gboolean quit; /* 退出标志 */
gint64 session_start_us; /* 当前按键会话开始时间(0=无会话) */
gint64 last_packet_us; /* 最后一个包到达时间 */
gboolean pipeline_playing; /* pipeline 当前是否 PLAYING */
GMutex lock;
};
static void flush_queue(AudioSink *src)
{
SinkPacket *pkt;
while ((pkt = g_async_queue_try_pop(src->queue)) != NULL) {
g_free(pkt->data);
g_free(pkt);
}
}
void audio_sink_lock_alsa(void) { g_mutex_lock(&g_alsa_device_lock); }
void audio_sink_unlock_alsa(void) { g_mutex_unlock(&g_alsa_device_lock); }
static void set_pipeline_state_locked(AudioSink *src, GstState state)
void audio_sink_interrupt(AudioSink *src)
{
if (!src->pipeline)
return;
/* 切到 READY 时 flush appsrc,避免旧数据残留导致下次会话首帧异常 */
if (state == GST_STATE_READY && src->appsrc) {
gst_element_send_event(src->pipeline,
gst_event_new_flush_start());
gst_element_send_event(src->pipeline,
gst_event_new_flush_stop(FALSE));
}
gst_element_set_state(src->pipeline, state);
src->pipeline_playing = (state == GST_STATE_PLAYING);
}
static gpointer audio_sink_thread(gpointer data)
{
AudioSink *src = data;
gint64 now;
GstBuffer *buf;
while (1) {
/* 等待包,超时 100ms 用于检查会话超时 */
SinkPacket *pkt = g_async_queue_timeout_pop(src->queue, 100 * 1000);
now = g_get_monotonic_time();
g_mutex_lock(&src->lock);
if (src->quit) {
g_mutex_unlock(&src->lock);
if (pkt) { g_free(pkt->data); g_free(pkt); }
break;
}
/* 会话超时检查 */
if (src->session_start_us > 0) {
gint64 idle_ms = (now - src->last_packet_us) / 1000;
gint64 sess_ms = (now - src->session_start_us) / 1000;
if (idle_ms >= AUDIO_SINK_SESSION_TIMEOUT_MS ||
sess_ms >= AUDIO_SINK_SESSION_MAX_MS) {
if (src->pipeline_playing) {
set_pipeline_state_locked(src, GST_STATE_READY);
my_zlog_info("audio_sink: session ended (idle=%lldms sess=%lldms)",
(long long)idle_ms, (long long)sess_ms);
}
src->session_start_us = 0;
flush_queue(src);
/* 丢弃超时后到达的旧包 */
if (pkt) {
g_free(pkt->data);
g_free(pkt);
pkt = NULL;
}
g_mutex_unlock(&src->lock);
continue;
}
}
if (pkt) {
/* 新会话开始 */
if (src->session_start_us == 0) {
src->session_start_us = now;
my_zlog_info("audio_sink: session start");
if (!src->pipeline_playing)
set_pipeline_state_locked(src, GST_STATE_PLAYING);
}
src->last_packet_us = now;
/* push 到 appsrc(pipeline PLAYING 状态) */
if (src->appsrc && src->pipeline_playing) {
buf = gst_buffer_new_wrapped(g_memdup2(pkt->data, pkt->size), pkt->size);
GST_BUFFER_DTS(buf) = GST_CLOCK_TIME_NONE;
GST_BUFFER_PTS(buf) = GST_CLOCK_TIME_NONE;
if (gst_app_src_push_buffer(GST_APP_SRC(src->appsrc), buf) != GST_FLOW_OK) {
my_zlog_warn("audio_sink: push_buffer failed");
}
}
g_free(pkt->data);
g_free(pkt);
}
g_mutex_unlock(&src->lock);
}
return NULL;
(void)src;
/* RTP 接收管道已移除,无会话需要中断;DataChannel 播放通过 lock/unlock 串行访问 ALSA。 */
}
AudioSink *audio_sink_start(const char *alsa_device, char **error_message)
{
AudioSink *src;
GstElement *pipe, *asrc, *dec, *conv, *resample, *vol, *sink;
GstCaps *caps;
GstStateChangeReturn ret;
if (!alsa_device || !alsa_device[0]) {
(void)alsa_device;
if (error_message)
*error_message = g_strdup("no ALSA device");
return NULL;
}
*error_message = NULL;
src = g_new0(AudioSink, 1);
pipe = gst_pipeline_new("audio-sink-pipe");
asrc = gst_element_factory_make("appsrc", "asrc");
dec = gst_element_factory_make("opusdec", "dec");
conv = gst_element_factory_make("audioconvert", "conv");
resample = gst_element_factory_make("audioresample", "resample");
vol = gst_element_factory_make("volume", "vol");
sink = gst_element_factory_make("alsasink", "sink");
if (!pipe || !asrc || !dec || !conv || !resample || !vol || !sink) {
if (error_message)
*error_message = g_strdup("failed to create audio sink GStreamer elements");
if (pipe)
gst_object_unref(pipe);
g_free(src);
return NULL;
}
caps = gst_caps_new_empty_simple("audio/x-opus");
g_object_set(asrc,
"caps", caps,
"format", GST_FORMAT_BYTES,
"is-live", TRUE,
"emit-signals", FALSE,
"min-latency", (gint64)0,
"max-bytes", (guint64)(1 * 1024 * 1024),
NULL);
gst_caps_unref(caps);
g_object_set(sink,
"device", alsa_device,
"buffer-time", (gint64)20000,
"latency-time", (gint64)10000,
"sync", FALSE,
NULL);
g_object_set(vol, "volume", 0.5, NULL);
gst_bin_add_many(GST_BIN(pipe), asrc, dec, conv, resample, vol, sink, NULL);
if (!gst_element_link_many(asrc, dec, conv, resample, vol, sink, NULL)) {
if (error_message)
*error_message = g_strdup("failed to link audio sink chain");
gst_object_unref(pipe);
g_free(src);
return NULL;
}
src->pipeline = pipe;
src->appsrc = asrc;
src->vol = vol;
src->queue = g_async_queue_new();
g_mutex_init(&src->lock);
src->session_start_us = 0;
src->pipeline_playing = FALSE;
/* 初始状态 READY(不占 ALSA 设备,等首包到来再 PLAYING) */
ret = gst_element_set_state(pipe, GST_STATE_READY);
if (ret == GST_STATE_CHANGE_FAILURE) {
if (error_message)
*error_message = g_strdup("audio sink pipeline failed to reach READY");
gst_element_set_state(pipe, GST_STATE_NULL);
gst_object_unref(pipe);
g_async_queue_unref(src->queue);
g_mutex_clear(&src->lock);
g_free(src);
return NULL;
}
src->thread = g_thread_new("audio-sink", audio_sink_thread, src);
if (!src->thread) {
if (error_message)
*error_message = g_strdup("failed to create audio sink thread");
audio_sink_stop(src);
return NULL;
}
my_zlog_info("audio_sink: started device=%s opus=%uch %uHz (push-to-talk)",
alsa_device, WEBRTCPUSH_OPUS_CHANNELS, WEBRTCPUSH_OPUS_CLOCKRATE);
g_audio_sink_singleton = src;
my_zlog_info("audio_sink: started (RTP receive removed, ALSA lock only)");
return src;
}
......@@ -233,45 +43,15 @@ void audio_sink_stop(AudioSink *src)
{
if (!src)
return;
if (src->thread) {
g_mutex_lock(&src->lock);
src->quit = TRUE;
g_mutex_unlock(&src->lock);
g_thread_join(src->thread);
src->thread = NULL;
}
if (src->pipeline) {
gst_element_set_state(src->pipeline, GST_STATE_NULL);
gst_object_unref(src->pipeline);
}
if (src->queue) {
flush_queue(src);
g_async_queue_unref(src->queue);
}
if (g_audio_sink_singleton == src)
g_audio_sink_singleton = NULL;
g_mutex_clear(&src->lock);
g_free(src);
}
gboolean audio_sink_push_opus(AudioSink *src, const uint8_t *data, size_t size)
{
SinkPacket *pkt;
if (!src || !src->queue || !data || size == 0)
return FALSE;
/* 入队,由消费线程处理(不阻塞 libdatachannel 回调线程) */
pkt = g_new0(SinkPacket, 1);
pkt->data = (uint8_t *)g_memdup2(data, size);
pkt->size = size;
g_async_queue_push(src->queue, pkt);
return TRUE;
}
void audio_sink_set_volume(AudioSink *src, double volume)
{
if (!src || !src->vol)
return;
if (volume < 0.0)
volume = 0.0;
if (volume > 1.0)
volume = 1.0;
g_object_set(src->vol, "volume", volume, NULL);
(void)src;
(void)volume;
/* 无 GStreamer 管道,音量设置为空操作(保留接口供 volume_control 调用)。 */
}
......@@ -17,4 +17,11 @@ gboolean audio_sink_push_opus(AudioSink *src, const uint8_t *data, size_t size);
/* 设置播放音量 0.0~1.0 */
void audio_sink_set_volume(AudioSink *src, double volume);
/* USB声卡排队锁: 本地音频和audio_sink串行访问同一个USB声卡 */
void audio_sink_lock_alsa(void);
void audio_sink_unlock_alsa(void);
/* 中断当前按键会话, 释放ALSA设备让DataChannel音频能立即播放 */
void audio_sink_interrupt(AudioSink *src);
#endif
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -18,36 +18,45 @@
/*
* 与 gst_webrtc_pipeline / jywy 浏览器推流对齐的码率策略。
* 首屏先用 900kbps,不像 1.4Mbps 那样猛冲,也不要低到一进来就糊。
* RTCP REMB 只作为码率趋势,下降也做阶梯平滑,避免 MPP 动态切码率时卡顿
* RTCP REMB 只作为码率趋势:小步慢降、慢升、带滞回,尽量接近浏览器的无感自适应
*/
#define WEBRTCPUSH_INITIAL_BITRATE 900000U
#define WEBRTCPUSH_MIN_BITRATE 500000U
#define WEBRTCPUSH_MAX_BITRATE 2800000U
#define WEBRTCPUSH_MIN_BITRATE 800000U
#define WEBRTCPUSH_MAX_BITRATE 2600000U
#define WEBRTCPUSH_REMB_UTIL_PERCENT 80U
#define WEBRTCPUSH_REMB_DOWN_MIN_STEP 100000U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS 4U
#define WEBRTCPUSH_REMB_DOWN_CONFIRMATIONS 8U
#define WEBRTCPUSH_REMB_SEVERE_CONFIRMATIONS 2U
#define WEBRTCPUSH_REMB_SEVERE_PERCENT 65U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 2500U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 1500U
#define WEBRTCPUSH_REMB_DOWN_STEP_PERCENT 15U
#define WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS 80000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS 180000U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 140U
#define WEBRTCPUSH_BITRATE_RAMP_UP_MS 1800U
#define WEBRTCPUSH_BITRATE_RAMP_DOWN_MS 2000U
#define WEBRTCPUSH_REMB_DOWN_STEP_PERCENT 10U
#define WEBRTCPUSH_REMB_DOWN_STEP_MIN_BPS 60000U
#define WEBRTCPUSH_REMB_DOWN_STEP_MAX_BPS 120000U
/* REMB 低估保护:pacing 不堵时,过低浏览器估计不直接压糊 720p。 */
#define WEBRTCPUSH_REMB_SANE_FLOOR_BPS 2200000U
#define WEBRTCPUSH_REMB_SANE_RAW_MAX_BPS 1200000U
#define WEBRTCPUSH_REMB_SANE_PACING_MAX_MS 100U
#define WEBRTCPUSH_PACING_HEADROOM_PERCENT 180U
#define WEBRTCPUSH_PACING_INTERVAL_MS 5U
#define WEBRTCPUSH_PACING_MAX_BITRATE 4000000U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 250U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 2000U
#define WEBRTCPUSH_PACING_MAX_QUEUE_MS 260U
#define WEBRTCPUSH_PACING_GUARD_COOLDOWN_MS 1000U
/* 首个/刚恢复的 IDR 允许短暂排队,避免首屏关键帧刚发出就被清队列 */
#define WEBRTCPUSH_PACING_IDR_GRACE_MS 800U
#define WEBRTCPUSH_PACING_IDR_GRACE_MS 250U
/* pacing 清队列后,若近期已有 IDR,不要反复强制 IDR 造成 I 帧风暴 */
#define WEBRTCPUSH_PACING_RESYNC_IDR_MS 5000U
/* RK MPP 运行中小幅改 bps 容易顿一下;小变化只调 pacing,少重配硬编。 */
#define WEBRTCPUSH_MPP_RECONFIG_MIN_DELTA_BPS 200000U
#define WEBRTCPUSH_MPP_RECONFIG_MIN_INTERVAL_MS 5000U
/* RTP 分片与 NACK(MTU=1200,留 SRTP/DTLS/FU 余量) */
#define WEBRTCPUSH_RTP_MAX_FRAGMENT 1050U
#define WEBRTCPUSH_NACK_PACKETS 512U
/* PLI/IDR 节流:避免频繁 force-key-unit 拉高瞬时码率 */
#define WEBRTCPUSH_PLI_IDR_THROTTLE_MS 500U
#define WEBRTCPUSH_PLI_IDR_THROTTLE_MS 2000U /* 500->2000: 避免IDR风暴, 每个IDR都加大pacing堆积 */
/* 周期性推流指标日志间隔 */
#define WEBRTCPUSH_STATS_LOG_INTERVAL_MS 8000U
......@@ -66,8 +75,7 @@
#define WEBRTCPUSH_MPP_POST_ENC_BUFFERS 1 /* 编码后保留 AU */
#define WEBRTCPUSH_APPSINK_MAX_BUFFERS 1
/* MJPEG 解压:1=GStreamer CPU jpegdec 优先;0=mppjpegdec 优先 */
#define WEBRTCPUSH_MJPEG_CPU_DECODE 1
/* native MJPEG 解压固定使用 libjpeg-turbo(jpeglib) 手动解码;旧 GStreamer 解码分支已删除 */
/* MPP 码控收紧:低 REMB 时实际 H264 不能长期高于目标太多 */
#define WEBRTCPUSH_MPP_BPS_MIN_PERCENT 60U
......@@ -96,6 +104,15 @@
/* 音频播放(手机->设备):ALSA 喇叭设备 */
#define WEBRTCPUSH_AUDIO_PLAYBACK_DEVICE "plughw:2,0"
/*
* 手机->设备喊话音量策略:
* 0:后端返回 0 就按 0 播放(静音)
* 1:后端返回 0 时按 WEBRTCPUSH_AUDIO_ZERO_VOLUME_MIN 播放,便于测试喊话链路
*/
#define WEBRTCPUSH_AUDIO_ZERO_VOLUME_AS_MIN 0
#define WEBRTCPUSH_AUDIO_ZERO_VOLUME_MIN 0.5
/* 手机喊话结束后短暂保温,避免每个短包都冷启动声卡;到时仍会释放喇叭。 */
#define WEBRTCPUSH_AUDIO_SINK_IDLE_TIMEOUT_MS 3000U
/* 后端音量控制接口 */
#define WEBRTCPUSH_VOLUME_API_BASE "https://fcrs-api.yd-ss.com/api/drive/use/status/"
......@@ -104,10 +121,11 @@
/*
* 设备侧 DataChannel(myDataChannel)与手机 createDataChannel('init') 争用 SCTP,
* 易触发 sctpenc association error 并导致管道闪断/进程崩溃。仅推流可不建。
* 设备侧 DataChannel(myDataChannel):
* - 前端 ondatachannel 后用 remoteChannel 发送 MP3 分片 + "EOF" 给设备播放;
* - 设备也通过该通道发送 Mbps 字符串,更新右上角网络显示。
*/
#define WEBRTCPUSH_ENABLE_DATACHANNEL 0
#define WEBRTCPUSH_ENABLE_DATACHANNEL 1
/* WebRTC 信令 WebSocket 主机(路径 /websocket?dev=设备号) */
#define WEBRTCPUSH_SIGNAL_HOST "signal.yd-ss.com"
......
......@@ -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-07-08.log"; millisecond
my_log.* "/home/orangepi/car/master/log/log_2026-07-10.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