在粤嵌GEC6818上玩转多线程:一边听歌一边显示图片的触摸交互程序
嵌入式开发中,资源受限设备的并发编程一直是开发者面临的挑战。如何在内存有限的GEC6818开发板上实现音频播放、图片轮播和触摸交互的协同运行?本文将带你从零构建一个多线程交互程序,不仅掌握pthread线程管理技巧,还能体验嵌入式系统资源调度的艺术。
1. 环境搭建与基础准备
开发板启动后,首先需要确认三个关键设备文件的访问权限:
ls -l /dev/fb0 /dev/input/event0 /bin/madplay典型输出应显示:
crw-rw---- 1 root video 29, 0 Jan 1 00:00 /dev/fb0 crw-rw---- 1 root input 13, 64 Jan 1 00:00 /dev/input/event0 -rwxr-xr-x 1 root root 286456 Jan 1 00:00 /bin/madplay若权限不足,可通过以下命令调整(需root权限):
chmod 666 /dev/fb0 chmod 666 /dev/input/event0关键头文件准备:
#include <pthread.h> // 线程操作 #include <sys/mman.h> // 内存映射 #include <linux/input.h>// 触摸事件 #include <sys/stat.h> // 文件状态2. 多线程架构设计
程序采用三层线程结构:
| 线程类型 | 功能描述 | 优先级 | 资源占用 |
|---|---|---|---|
| 主线程 | 触摸事件处理与线程调度 | 高 | CPU 20% |
| 音频线程 | MP3解码与播放 | 中 | CPU 35% |
| 显示线程 | BMP图片轮播与界面渲染 | 低 | CPU 45% |
线程创建示例:
pthread_t audio_tid, display_tid; void init_threads() { pthread_create(&audio_tid, NULL, audio_thread, NULL); pthread_create(&display_tid, NULL, display_thread, NULL); // 设置线程优先级 struct sched_param param; param.sched_priority = 10; pthread_setschedparam(audio_tid, SCHED_RR, ¶m); }3. 音频播放实现细节
采用system调用madplay的优化方案:
void* audio_thread(void* arg) { char cmd[256]; while(1) { snprintf(cmd, sizeof(cmd), "madplay -Q --sample-rate=44100 ./music/%d.mp3 &", current_song_index); int ret = system(cmd); if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) { perror("Audio playback failed"); } // 等待播放结束或中断 while(!audio_interrupt) { usleep(100000); } audio_interrupt = 0; } return NULL; }音频控制技巧:
- 使用
pkill -STOP madplay暂停播放 - 使用
pkill -CONT madplay恢复播放 - 通过
killall madplay终止当前播放
4. 图片轮播与触摸交互
BMP图片显示优化方案:
void show_bmp(const char* path) { int fd = open(path, O_RDONLY); lseek(fd, 0x36, SEEK_SET); unsigned char buf[800*480*3]; read(fd, buf, sizeof(buf)); for(int y=0; y<480; y++) { for(int x=0; x<800; x++) { int offset = ((479-y)*800 + x)*3; int color = (buf[offset+2]<<16) | (buf[offset+1]<<8) | buf[offset]; Lcd_Draw_Point(x, y, color); } } close(fd); }触摸事件处理核心逻辑:
struct TouchEvent { int x; int y; int pressure; }; struct TouchEvent get_touch() { struct input_event ev; static struct TouchEvent touch; while(1) { read(touch_fd, &ev, sizeof(ev)); if(ev.type == EV_ABS) { if(ev.code == ABS_X) touch.x = ev.value; if(ev.code == ABS_Y) touch.y = ev.value; if(ev.code == ABS_PRESSURE) touch.pressure = ev.value; } if(ev.type == EV_SYN && ev.code == SYN_REPORT) { return touch; } } }5. 线程间通信与同步
采用共享内存+信号量的通信方式:
// 共享数据结构 struct SharedData { pthread_mutex_t lock; int current_volume; int display_mode; char next_song[64]; }; // 初始化 pthread_mutex_init(&shared.lock, NULL); // 音频线程修改数据 void adjust_volume(int delta) { pthread_mutex_lock(&shared.lock); shared.current_volume += delta; char cmd[64]; snprintf(cmd, sizeof(cmd), "amixer set PCM %d%%", shared.current_volume); system(cmd); pthread_mutex_unlock(&shared.lock); }死锁预防策略:
- 按固定顺序获取多个锁
- 设置锁超时机制
- 避免在持有锁时调用外部命令
6. 性能优化技巧
针对GEC6818的特定优化:
内存优化:
// 使用内存池替代频繁malloc/free #define POOL_SIZE 5 static void* bmp_buffer_pool[POOL_SIZE]; void* get_bmp_buffer() { for(int i=0; i<POOL_SIZE; i++) { if(bmp_buffer_pool[i] != NULL) { void* ret = bmp_buffer_pool[i]; bmp_buffer_pool[i] = NULL; return ret; } } return malloc(800*480*3); }CPU负载均衡:
# 监控各线程CPU使用率 top -H -p $(pidof your_program)IO优化:
- 预加载下一张图片
- 使用内存文件系统存放临时音频文件
7. 常见问题排查
问题1:触摸响应延迟
- 检查
/dev/input/event0读取频率 - 优化事件处理循环,避免阻塞操作
问题2:音频播放卡顿
# 查看系统负载 cat /proc/loadavg # 检查内存使用 free -m问题3:图片显示错位
- 验证BMP文件头信息
- 检查颜色格式转换代码
- 确认屏幕映射方向
在项目调试过程中,发现当图片分辨率与屏幕不匹配时,采用双线性插值算法能显著改善显示效果。而音频线程的优先级设置过高会导致触摸响应迟钝,经过多次测试,将音频线程优先级设为15,显示线程设为10时能达到最佳平衡。