1. 信号系统解析与实战应用
在嵌入式音视频开发中,信号处理是系统可靠性的重要保障机制。RV1106平台通过Linux标准信号机制实现了进程间通信和异常处理能力。信号本质上是一种软件中断,用于通知进程发生了某种事件。
1.1 信号处理原理
Linux系统提供了约30种标准信号(通过kill -l可查看完整列表),每种信号都有特定的用途。在音视频处理场景中,最常用的信号包括:
- SIGINT(2):终端中断信号,通常由Ctrl+C触发
- SIGTERM(15):终止请求信号,kill命令默认发送此信号
- SIGSEGV(11):段错误信号,访问非法内存时触发
- SIGPIPE(13):管道破裂信号,写入无读端的管道时触发
信号处理的核心在于注册回调函数。当信号发生时,系统会中断进程的当前执行流程,转而执行预先注册的信号处理函数。这种机制类似于硬件中断的ISR(中断服务例程)。
注意:信号处理函数应尽量简单,避免执行复杂操作。长时间阻塞可能导致信号丢失或系统不稳定。
1.2 信号注册实战代码分析
原始代码展示了典型的信号处理注册方式:
c复制static void sig_proc(int signo) {
LOG_INFO("received signo %d \n", signo);
}
int main(int argc, char *argv[]) {
// 注册信号处理函数
signal(SIGINT, sig_proc); // Ctrl+C 中断信号
signal(SIGTERM, sig_proc); // 终止信号 killall 命令
}
这段代码的关键点在于:
sig_proc是自定义的信号处理函数,参数signo表示接收到的信号编号signal()函数用于绑定信号与处理函数,第一个参数是信号宏,第二个是函数指针- 信号处理函数应声明为
static以避免命名冲突
在实际音视频项目中,我们通常需要更健壮的信号处理方案:
c复制#include <signal.h>
#include <stdlib.h>
static volatile sig_atomic_t g_signal_status = 0;
static void handle_signal(int sig) {
switch(sig) {
case SIGINT:
g_signal_status = 1; // 优雅退出标志
break;
case SIGTERM:
g_signal_status = 2;
break;
case SIGSEGV:
// 记录段错误信息
log_crash_info();
exit(EXIT_FAILURE);
}
}
void setup_signal_handlers() {
struct sigaction sa;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; // 自动重启被中断的系统调用
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
}
改进后的版本具有以下优势:
- 使用
sigaction替代signal,提供更精确的控制 - 添加了信号屏蔽(
sigemptyset),防止信号处理期间被其他信号中断 SA_RESTART标志确保慢速系统调用被信号中断后能自动重启- 使用
sig_atomic_t类型的全局变量实现主循环与信号处理的安全通信
1.3 信号处理的最佳实践
在音视频嵌入式开发中,信号处理需要特别注意以下几点:
-
线程安全性:信号处理函数中只能调用异步信号安全函数(如
write、_exit等),避免使用malloc、printf等非安全函数 -
资源清理:在退出前确保释放所有资源(关闭文件描述符、释放内存、停止编解码器等)
-
日志记录:关键信号应记录到持久化存储,便于后续问题排查
-
性能考量:频繁的信号处理会影响实时音视频处理的性能,应尽量减少信号使用
c复制// 安全日志记录示例
void safe_log(int fd, const char *msg) {
size_t len = strlen(msg);
write(fd, msg, len); // write是异步信号安全函数
}
2. 按键中断处理与线程模型
在RV1106音视频设备中,物理按键是常见的用户交互方式。不同于桌面环境,嵌入式设备通常通过输入子系统(Input Subsystem)来处理按键事件。
2.1 Linux输入子系统架构
Linux输入子系统将各类输入设备(键盘、鼠标、触摸屏等)抽象为统一接口,开发者可以通过/dev/input/eventX设备文件读取输入事件。每个事件包含以下关键信息:
- 时间戳(timeval结构体)
- 事件类型(type):EV_KEY表示按键事件
- 键值(code):如KEY_VOLUMEUP、KEY_VOLUMEDOWN
- 值(value):0表示释放,1表示按下,2表示长按
原始代码展示了如何监听按键事件:
c复制static void *wait_key_event(void *arg) {
int key_fd = open("/dev/input/event0", O_RDONLY);
struct input_event key_event;
while (g_main_run_) {
read(key_fd, &key_event, sizeof(key_event));
if (key_event.type == EV_KEY) {
// 处理按键事件
}
}
}
2.2 多线程事件处理优化
原始代码使用select系统调用实现非阻塞读取,这是正确的方向,但可以进一步优化:
c复制void *key_event_thread(void *arg) {
int key_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
struct input_event ev;
struct pollfd fds = {
.fd = key_fd,
.events = POLLIN
};
while (g_running) {
int ret = poll(&fds, 1, 100); // 100ms超时
if (ret > 0 && (fds.revents & POLLIN)) {
while (read(key_fd, &ev, sizeof(ev)) == sizeof(ev)) {
process_key_event(&ev);
}
}
}
close(key_fd);
return NULL;
}
优化点包括:
- 使用
O_NONBLOCK标志设置非阻塞模式 - 采用
poll替代select,更高效且无文件描述符数量限制 - 循环读取直到EAGAIN,确保处理所有待处理事件
- 分离事件读取和处理逻辑,提高代码可维护性
2.3 音视频按键功能实现
原始代码中展示了音量键控制音频播放的典型实现:
c复制if ((key_event.code == KEY_VOLUMEDOWN) && key_event.value) {
rkipc_ao_init();
FILE *fp = fopen("/oem/usr/share/speaker_test.wav", "rb");
// ...音频播放逻辑...
rkipc_ao_deinit();
}
在实际项目中,这种实现有几个需要改进的地方:
- 资源管理:应检查每次资源分配的结果,确保错误时能正确释放已分配资源
- 状态保护:添加互斥锁保护共享资源,防止多线程竞争
- 错误恢复:实现超时机制和错误计数,避免死锁
改进后的音频处理流程:
c复制pthread_mutex_t audio_mutex = PTHREAD_MUTEX_INITIALIZER;
void play_audio_sample(const char *path) {
if (pthread_mutex_trylock(&audio_mutex) != 0) {
LOG_WARN("Audio playback in progress, skipping");
return;
}
AUDIO_CTX *ctx = audio_init();
if (!ctx) goto cleanup;
FILE *fp = fopen(path, "rb");
if (!fp) goto cleanup;
char *buffer = malloc(AUDIO_BUF_SIZE);
if (!buffer) goto cleanup;
while (!feof(fp)) {
size_t read = fread(buffer, 1, AUDIO_BUF_SIZE, fp);
if (audio_write(ctx, buffer, read) != read) {
LOG_ERROR("Audio write failed");
break;
}
}
cleanup:
if (buffer) free(buffer);
if (fp) fclose(fp);
audio_deinit(ctx);
pthread_mutex_unlock(&audio_mutex);
}
3. 线程管理与同步机制
在音视频嵌入式系统中,合理的线程模型对保证实时性至关重要。RV1106平台采用POSIX线程(pthread)实现多任务处理。
3.1 线程创建与销毁
原始代码展示了基本的线程创建方式:
c复制pthread_t key_chk;
pthread_create(&key_chk, NULL, wait_key_event, NULL);
pthread_join(key_chk, NULL);
更完善的线程管理应包含以下要素:
- 线程属性设置:调整栈大小、调度策略等
- 错误检查:检查每个线程操作的返回值
- 取消点处理:安全地终止线程
c复制void create_key_thread(void) {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 16 * 1024); // 16KB栈空间
pthread_t tid;
int ret = pthread_create(&tid, &attr, key_thread_func, NULL);
if (ret != 0) {
LOG_ERROR("Thread creation failed: %s", strerror(ret));
return;
}
pthread_attr_destroy(&attr);
// 保存线程ID以便后续管理
g_key_thread = tid;
}
3.2 线程同步技术
音视频处理中常见的同步需求包括:
- 音频/视频帧同步:保证音画同步
- 资源访问控制:防止多个线程同时操作编解码器
- 状态一致性:确保系统状态在多线程环境下保持一致
常用的同步原语:
- 互斥锁(Mutex):保护临界区
- 条件变量(Condition Variable):线程间事件通知
- 信号量(Semaphore):资源计数
- 读写锁(RWLock):读多写少场景优化
c复制// 音频缓冲区同步示例
pthread_mutex_t audio_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t audio_cond = PTHREAD_COND_INITIALIZER;
AudioBuffer *buffer = NULL;
void producer_thread() {
while (running) {
AudioBuffer *new_buf = capture_audio();
pthread_mutex_lock(&audio_mutex);
if (buffer) free(buffer); // 丢弃旧数据
buffer = new_buf;
pthread_cond_signal(&audio_cond);
pthread_mutex_unlock(&audio_mutex);
}
}
void consumer_thread() {
while (running) {
pthread_mutex_lock(&audio_mutex);
while (!buffer) {
pthread_cond_wait(&audio_cond, &audio_mutex);
}
process_audio(buffer);
free(buffer);
buffer = NULL;
pthread_mutex_unlock(&audio_mutex);
}
}
3.3 线程优先级与调度
在实时音视频系统中,合理设置线程优先级至关重要:
c复制void set_thread_priority(pthread_t thread, int priority) {
struct sched_param param = {
.sched_priority = priority
};
int policy = SCHED_FIFO; // 实时调度策略
if (pthread_setschedparam(thread, policy, ¶m) != 0) {
LOG_WARN("Failed to set thread priority");
}
}
典型音视频线程优先级安排:
- 音频I/O线程(最高优先级)
- 视频采集线程
- 网络传输线程
- 用户界面线程(最低优先级)
注意:设置实时优先级(SCHED_FIFO/SCHED_RR)需要root权限,且不当使用可能导致系统不稳定。
4. 音视频中断处理实战问题
在实际RV1106音视频项目开发中,我们遇到了各种中断和信号相关的问题,以下是典型案例和解决方案。
4.1 音频播放中断问题
现象:当系统负载高时,音频播放会出现卡顿或中断。
排查过程:
- 检查音频线程优先级,确认已设置为较高优先级
- 使用
strace跟踪系统调用,发现音频线程有时会长时间阻塞在write系统调用 - 进一步分析发现是文件系统缓存不足导致
解决方案:
c复制// 增加音频缓冲区
#define AUDIO_BUF_SIZE (1024 * 16) // 从4KB增加到16KB
// 使用posix_fadvise预加载音频文件
posix_fadvise(fileno(fp), 0, file_size, POSIX_FADV_WILLNEED);
// 设置实时优先级
struct sched_param param = { .sched_priority = 50 };
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
4.2 信号处理导致的内存泄漏
现象:频繁发送SIGINT信号后,系统内存逐渐减少。
原因分析:信号处理函数中直接调用malloc和free,这些函数不是信号安全的,可能导致内存管理数据结构损坏。
解决方案:
- 在信号处理函数中仅设置标志位
- 在主循环中检查标志并执行资源释放
- 使用
sigprocmask保护关键代码段不被信号中断
c复制static volatile sig_atomic_t g_need_cleanup = 0;
void signal_handler(int sig) {
g_need_cleanup = 1;
}
void main_loop() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
while (1) {
// 保护资源分配代码
pthread_sigmask(SIG_BLOCK, &mask, NULL);
allocate_resources();
pthread_sigmask(SIG_UNBLOCK, &mask, NULL);
if (g_need_cleanup) {
safe_cleanup();
g_need_cleanup = 0;
}
}
}
4.3 按键响应延迟问题
现象:音量按键有时需要长按才能响应。
排查步骤:
- 使用
evtest工具确认硬件按键事件正常 - 分析线程调度日志,发现按键处理线程有时被CPU密集型任务抢占
- 检查系统负载,发现视频编码线程占用过高CPU
优化方案:
- 调整线程优先级:
c复制set_thread_priority(key_thread, 70); // 高于视频编码线程的60
- 实现按键去抖逻辑:
c复制#define DEBOUNCE_TIME 50 // 50ms防抖时间
static int is_debounced(struct input_event *ev) {
static struct timeval last_time = {0};
static int last_code = -1;
if (ev->code != last_code) {
last_code = ev->code;
last_time = ev->time;
return 1;
}
long elapsed = (ev->time.tv_sec - last_time.tv_sec) * 1000 +
(ev->time.tv_usec - last_time.tv_usec) / 1000;
if (elapsed > DEBOUNCE_TIME) {
last_time = ev->time;
return 1;
}
return 0;
}
- 优化事件处理流程,减少临界区持续时间
4.4 多线程资源竞争问题
现象:偶尔出现音频播放异常,日志显示音频设备被重复初始化。
原因:多个线程同时尝试操作音频设备,缺乏适当的同步机制。
解决方案:
- 使用双重检查锁定模式(Double-Checked Locking)优化资源初始化:
c复制static pthread_mutex_t audio_init_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile int audio_initialized = 0;
void safe_audio_init() {
if (!audio_initialized) {
pthread_mutex_lock(&audio_init_mutex);
if (!audio_initialized) {
do_audio_init();
audio_initialized = 1;
}
pthread_mutex_unlock(&audio_init_mutex);
}
}
- 为每个硬件资源添加引用计数:
c复制typedef struct {
pthread_mutex_t mutex;
int refcount;
void *hw_handle;
} AudioDevice;
AudioDevice *audio_device_acquire() {
pthread_mutex_lock(&global_audio_mutex);
if (!global_audio_device) {
global_audio_device = create_audio_device();
}
global_audio_device->refcount++;
pthread_mutex_unlock(&global_audio_mutex);
return global_audio_device;
}
void audio_device_release(AudioDevice *dev) {
pthread_mutex_lock(&global_audio_mutex);
if (--dev->refcount == 0) {
destroy_audio_device(dev);
global_audio_device = NULL;
}
pthread_mutex_unlock(&global_audio_mutex);
}