3. QNX侧显示服务与Android SurfaceFlinger对接
任务说明
在QNX与Android双系统融合的座舱方案中,Android侧UI由SurfaceFlinger合成,而屏幕显示资源由QNX侧管理。本案例实现QNX侧显示服务与Android SurfaceFlinger的对接,使Android UI能够显示在QNX管理的物理屏幕上。核心思路是:QNX侧通过共享内存接收Android SurfaceFlinger输出的合成帧,并驱动显示控制器输出。
环境与依赖
- QNX 7.1或以上版本,支持共享内存驱动(
shm)和显示控制器接口 - Android侧需支持将SurfaceFlinger输出重定向到QNX共享内存(通过虚拟显示或HWC HAL扩展)
- 编译工具:QNX SDP中的
qcc编译器
实现方案
QNX侧显示服务通过以下步骤完成对接:
- 创建命名共享内存区域,用于接收Android侧SurfaceFlinger输出的帧数据
- 监听Android侧通过消息队列发送的帧就绪通知
- 从共享内存读取帧数据,通过显示控制器接口输出到屏幕
关键完整代码
// qnx_display_service.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <screen/screen.h>
#include <errno.h>
#define SHM_NAME "/android_fb"
#define MSG_QUEUE_NAME "/android_frame_ready"
#define FRAME_WIDTH 1920
#define FRAME_HEIGHT 1080
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT * 4) // RGBA8888
typedef struct {
uint64_t timestamp;
uint32_t width;
uint32_t height;
uint32_t stride;
} frame_info_t;
// 消息队列结构
typedef struct {
uint16_t type;
frame_info_t info;
} frame_msg_t;
static int shm_fd = -1;
static void *shm_ptr = NULL;
static screen_context_t screen_ctx;
static screen_window_t screen_win;
static screen_buffer_t screen_buffers[2];
static int current_buffer = 0;
// 初始化QNX Screen显示窗口
static int init_display(void) {
if (screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT) != 0) {
fprintf(stderr, "Failed to create screen context\n");
return -1;
}
screen_create_window(&screen_win, screen_ctx);
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, (int[]){SCREEN_FORMAT_RGBA8888});
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, (int[]){SCREEN_USAGE_NATIVE});
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, (int[]){FRAME_WIDTH, FRAME_HEIGHT});
// 创建双缓冲
screen_create_window_buffers(screen_win, 2);
screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_BUFFERS, (void **)screen_buffers);
// 设置窗口位置和大小
int pos[] = {0, 0};
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_POSITION, pos);
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, (int[]){FRAME_WIDTH, FRAME_HEIGHT});
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_VISIBLE, (int[]){1});
return 0;
}
// 将共享内存中的帧数据拷贝到Screen缓冲区并显示
static int display_frame(const frame_info_t *info) {
if (info->width != FRAME_WIDTH || info->height != FRAME_HEIGHT) {
fprintf(stderr, "Frame size mismatch: %ux%u\n", info->width, info->height);
return -1;
}
// 获取当前可写的Screen缓冲区
screen_buffer_t *buf = &screen_buffers[current_buffer];
void *buf_ptr;
screen_get_buffer_property_pv(*buf, SCREEN_PROPERTY_POINTER, (void **)&buf_ptr);
// 从共享内存拷贝帧数据到Screen缓冲区
memcpy(buf_ptr, shm_ptr, FRAME_SIZE);
// 提交缓冲区到屏幕
screen_post_window(screen_win, *buf, 0, NULL, 0);
screen_flush_context(screen_ctx, 0);
// 切换缓冲区
current_buffer = (current_buffer + 1) % 2;
return 0;
}
int main(int argc, char *argv[]) {
// 1. 初始化显示
if (init_display() != 0) {
return EXIT_FAILURE;
}
// 2. 创建/打开共享内存
shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
if (shm_fd == -1) {
perror("shm_open");
return EXIT_FAILURE;
}
ftruncate(shm_fd, FRAME_SIZE);
shm_ptr = mmap(NULL, FRAME_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (shm_ptr == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
// 3. 创建消息通道接收Android侧通知
name_attach_t *attach = name_attach(NULL, MSG_QUEUE_NAME, 0);
if (attach == NULL) {
perror("name_attach");
return EXIT_FAILURE;
}
printf("QNX display service started. Waiting for Android frames...\n");
// 4. 主循环:等待帧就绪消息
while (1) {
frame_msg_t msg;
int rcvid = MsgReceive(attach->chid, &msg, sizeof(msg), NULL);
if (rcvid == -1) {
perror("MsgReceive");
continue;
}
if (rcvid == 0) { // 脉冲消息
continue;
}
// 处理帧就绪通知
if (msg.type == 1) { // 帧就绪
display_frame(&msg.info);
MsgReply(rcvid, EOK, NULL, 0);
} else {
MsgReply(rcvid, ENOSYS, NULL, 0);
}
}
// 清理资源(实际运行中不会到达此处)
munmap(shm_ptr, FRAME_SIZE);
close(shm_fd);
name_detach(attach, 0);
return EXIT_SUCCESS;
}
编译与运行
# 在QNX SDP环境中编译
qcc -Vgcc_ntox86_64 -o qnx_display_service qnx_display_service.c -lscreen
# 运行QNX侧显示服务
./qnx_display_service
Android侧对接说明
Android侧需在HWC或SurfaceFlinger的VirtualDisplay输出路径中,将合成后的帧写入QNX共享内存/android_fb,并通过QNX消息队列/android_frame_ready发送帧就绪通知。Android侧通过libhardware的HAL接口实现与QNX共享内存的映射,关键步骤包括:
- 通过
ashmem或ion分配与QNX共享内存对应的物理内存 - 在HWC的
setOutputBuffer回调中将帧数据写入共享内存 - 通过QNX消息队列接口(
MsgSend)发送帧信息
验证方法
- 启动验证:运行QNX显示服务后,终端应输出
Waiting for Android frames...,且无错误信息 - 功能验证:Android侧启动任意应用(如Launcher),QNX屏幕上应实时显示Android UI画面
- 帧率验证:在Android侧滑动屏幕时,QNX屏幕应流畅跟随,无明显卡顿或撕裂
- 异常验证:Android侧停止发送帧后,QNX屏幕保持最后一帧画面,服务不崩溃
常见问题
- 共享内存打开失败:确认
/dev/shm目录存在且有写权限,或使用shm_open前先mkdir -p /dev/shm - 画面不显示:检查Screen窗口的
SCREEN_PROPERTY_VISIBLE是否设为1,以及窗口位置是否在屏幕范围内 - 帧数据错乱:确认Android侧写入的像素格式为RGBA8888,且stride与宽度一致(无行对齐填充)